From f086fa3f21e56fb2a3f66f2c2bfd017811191c22 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 20 Jan 2023 10:38:14 +0100 Subject: [PATCH 001/180] Add syntax injections for Markdown fenced code blocks --- crates/zed/src/languages/markdown/injections.scm | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 crates/zed/src/languages/markdown/injections.scm diff --git a/crates/zed/src/languages/markdown/injections.scm b/crates/zed/src/languages/markdown/injections.scm new file mode 100644 index 0000000000..577054b404 --- /dev/null +++ b/crates/zed/src/languages/markdown/injections.scm @@ -0,0 +1,4 @@ +(fenced_code_block + (info_string + (language) @language) + (code_fence_content) @content) From c49dc8d6e573d70730248ee9d7ebc2e8ec8d7a83 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 20 Jan 2023 10:39:06 +0100 Subject: [PATCH 002/180] Rename `LanguageRegistry::get_language` to `language_for_name` --- crates/editor/src/hover_popover.rs | 2 +- crates/language/src/language.rs | 2 +- crates/language/src/syntax_map.rs | 7 ++++--- crates/zed/src/zed.rs | 10 +++++++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 6d003cae5d..f92b07da1d 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -331,7 +331,7 @@ impl InfoPopover { if let Some(language) = content .language .clone() - .and_then(|language| project.languages().get_language(&language)) + .and_then(|language| project.languages().language_for_name(&language)) { let runs = language .highlight_text(&content.text.as_str().into(), 0..content.text.len()); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 045e8dcd6f..06891d780d 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -466,7 +466,7 @@ impl LanguageRegistry { self.language_server_download_dir = Some(path.into()); } - pub fn get_language(&self, name: &str) -> Option> { + pub fn language_for_name(&self, name: &str) -> Option> { self.languages .read() .iter() diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 8d66730854..9ef4d82fd1 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -976,7 +976,7 @@ fn get_injections( combined_injection_ranges.clear(); for pattern in &config.patterns { if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) { - if let Some(language) = language_registry.get_language(language_name) { + if let Some(language) = language_registry.language_for_name(language_name) { combined_injection_ranges.insert(language, Vec::new()); } } @@ -1015,7 +1015,8 @@ fn get_injections( }); if let Some(language_name) = language_name { - if let Some(language) = language_registry.get_language(language_name.as_ref()) { + if let Some(language) = language_registry.language_for_name(language_name.as_ref()) + { result = true; let range = text.anchor_before(content_range.start) ..text.anchor_after(content_range.end); @@ -2254,7 +2255,7 @@ mod tests { registry.add(Arc::new(ruby_lang())); registry.add(Arc::new(html_lang())); registry.add(Arc::new(erb_lang())); - let language = registry.get_language(language_name).unwrap(); + let language = registry.language_for_name(language_name).unwrap(); let mut buffer = Buffer::new(0, 0, Default::default()); let mut mutated_syntax_map = SyntaxMap::new(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b6b00e7869..19c6bae6eb 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -226,7 +226,11 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext| { let content = to_string_pretty(&cx.debug_elements()).unwrap(); let project = workspace.project().clone(); - let json_language = project.read(cx).languages().get_language("JSON").unwrap(); + let json_language = project + .read(cx) + .languages() + .language_for_name("JSON") + .unwrap(); if project.read(cx).is_remote() { cx.propagate_action(); } else if let Some(buffer) = project @@ -624,7 +628,7 @@ fn open_telemetry_log_file( .update(cx, |project, cx| project.create_buffer("", None, cx)) .expect("creating buffers on a local workspace always succeeds"); buffer.update(cx, |buffer, cx| { - buffer.set_language(app_state.languages.get_language("JSON"), cx); + buffer.set_language(app_state.languages.language_for_name("JSON"), cx); buffer.edit( [( 0..0, @@ -670,7 +674,7 @@ fn open_bundled_config_file( let text = Assets::get(asset_path).unwrap().data; let text = str::from_utf8(text.as_ref()).unwrap(); project - .create_buffer(text, project.languages().get_language("JSON"), cx) + .create_buffer(text, project.languages().language_for_name("JSON"), cx) .expect("creating buffers on a local workspace always succeeds") }); let buffer = From 36e4dcef16f060f4d4fd063d982909bbe4160f19 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 20 Jan 2023 10:41:40 +0100 Subject: [PATCH 003/180] Avoid allocating a string to compare language names --- Cargo.lock | 1 + crates/language/Cargo.toml | 1 + crates/language/src/language.rs | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 06222d02a6..174965952f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3157,6 +3157,7 @@ dependencies = [ "tree-sitter-ruby", "tree-sitter-rust", "tree-sitter-typescript", + "unicase", "unindent", "util", ] diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 6c074a2d75..62de0c4e44 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -53,6 +53,7 @@ smol = "1.2" tree-sitter = "0.20" tree-sitter-rust = { version = "*", optional = true } tree-sitter-typescript = { version = "*", optional = true } +unicase = "2.6" [dev-dependencies] client = { path = "../client", features = ["test-support"] } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 06891d780d..046076a48e 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -41,6 +41,7 @@ use std::{ use syntax_map::SyntaxSnapshot; use theme::{SyntaxTheme, Theme}; use tree_sitter::{self, Query}; +use unicase::UniCase; use util::ResultExt; #[cfg(any(test, feature = "test-support"))] @@ -467,10 +468,11 @@ impl LanguageRegistry { } pub fn language_for_name(&self, name: &str) -> Option> { + let name = UniCase::new(name); self.languages .read() .iter() - .find(|language| language.name().to_lowercase() == name.to_lowercase()) + .find(|language| UniCase::new(language.name()) == name) .cloned() } From cb610f37f2dd0f6d449b1aa076e83a8fae808828 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 20 Jan 2023 10:56:20 +0100 Subject: [PATCH 004/180] WIP: Search language injections also by file extension There are still a few things left: 1. Add test to verify we can successfully locate a language by its extension 2. Add test to reproduce bug where changing the fenced code block language won't reparse the block with the new language 3. Reparse injections for which we couldn't find a language when the language registry changes. 4. Check why the markdown grammar considers the trailing triple backtick as `(code_block_content)`, as opposed to being part of the outer markdown. --- Cargo.lock | 1 + crates/language/Cargo.toml | 3 ++- crates/language/src/language.rs | 15 +++++++++++++++ crates/language/src/syntax_map.rs | 27 +++++++++++++++++++++++++-- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 174965952f..abdf8b8a55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3153,6 +3153,7 @@ dependencies = [ "tree-sitter-html", "tree-sitter-javascript", "tree-sitter-json 0.19.0", + "tree-sitter-markdown", "tree-sitter-python", "tree-sitter-ruby", "tree-sitter-rust", diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 62de0c4e44..64db58c847 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -66,12 +66,13 @@ util = { path = "../util", features = ["test-support"] } ctor = "0.1" env_logger = "0.9" rand = "0.8.3" +tree-sitter-embedded-template = "*" tree-sitter-html = "*" tree-sitter-javascript = "*" tree-sitter-json = "*" +tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } tree-sitter-rust = "*" tree-sitter-python = "*" tree-sitter-typescript = "*" tree-sitter-ruby = "*" -tree-sitter-embedded-template = "*" unindent = "0.1.7" diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 046076a48e..1ddd3e3939 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -476,6 +476,21 @@ impl LanguageRegistry { .cloned() } + pub fn language_for_extension(&self, extension: &str) -> Option> { + let extension = UniCase::new(extension); + self.languages + .read() + .iter() + .find(|language| { + language + .config + .path_suffixes + .iter() + .any(|suffix| UniCase::new(suffix) == extension) + }) + .cloned() + } + pub fn to_vec(&self) -> Vec> { self.languages.read().iter().cloned().collect() } diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 9ef4d82fd1..9707cf5471 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1015,8 +1015,10 @@ fn get_injections( }); if let Some(language_name) = language_name { - if let Some(language) = language_registry.language_for_name(language_name.as_ref()) - { + let language = language_registry + .language_for_name(&language_name) + .or_else(|| language_registry.language_for_extension(&language_name)); + if let Some(language) = language { result = true; let range = text.anchor_before(content_range.start) ..text.anchor_after(content_range.end); @@ -2255,6 +2257,7 @@ mod tests { registry.add(Arc::new(ruby_lang())); registry.add(Arc::new(html_lang())); registry.add(Arc::new(erb_lang())); + registry.add(Arc::new(markdown_lang())); let language = registry.language_for_name(language_name).unwrap(); let mut buffer = Buffer::new(0, 0, Default::default()); @@ -2393,6 +2396,26 @@ mod tests { .unwrap() } + fn markdown_lang() -> Language { + Language::new( + LanguageConfig { + name: "Markdown".into(), + path_suffixes: vec!["md".into()], + ..Default::default() + }, + Some(tree_sitter_markdown::language()), + ) + .with_injection_query( + r#" + (fenced_code_block + (info_string + (language) @language) + (code_fence_content) @content) + "#, + ) + .unwrap() + } + fn range_for_text(buffer: &Buffer, text: &str) -> Range { let start = buffer.as_rope().to_string().find(text).unwrap(); start..start + text.len() From 79cf6fb8b6b633f4c5a504ee050d93ff2ba0e6cd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 23 Jan 2023 09:45:36 +0100 Subject: [PATCH 005/180] WIP: Add test for dynamic language injection --- crates/language/src/syntax_map.rs | 56 +++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 9707cf5471..c0887809c2 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1596,6 +1596,56 @@ mod tests { ); } + #[gpui::test] + fn test_dynamic_language_injection() { + let registry = Arc::new(LanguageRegistry::test()); + let markdown = Arc::new(markdown_lang()); + registry.add(markdown.clone()); + registry.add(Arc::new(rust_lang())); + registry.add(Arc::new(ruby_lang())); + + let mut buffer = Buffer::new( + 0, + 0, + r#" + This is a code block: + + ```rs + fn foo() {} + ``` + "# + .unindent(), + ); + + let mut syntax_map = SyntaxMap::new(); + syntax_map.set_language_registry(registry.clone()); + syntax_map.reparse(markdown.clone(), &buffer); + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(3, 0)..Point::new(3, 0), + &[ + "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...", + "...(function_item name: (identifier) parameters: (parameters) body: (block)...", + ], + ); + + // Replace Rust with Ruby in code block. + let macro_name_range = range_for_text(&buffer, "rs"); + buffer.edit([(macro_name_range, "ruby")]); + syntax_map.interpolate(&buffer); + syntax_map.reparse(markdown.clone(), &buffer); + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(3, 0)..Point::new(3, 0), + &[ + "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...", + "...(call method: (identifier) arguments: (argument_list (call method: (identifier) arguments: (argument_list) block: (block)...", + ], + ); + } + #[gpui::test] fn test_typing_multiple_new_injections() { let (buffer, syntax_map) = test_edit_sequence( @@ -2408,9 +2458,9 @@ mod tests { .with_injection_query( r#" (fenced_code_block - (info_string - (language) @language) - (code_fence_content) @content) + (info_string + (language) @language) + (code_fence_content) @content) "#, ) .unwrap() From 8dabdd1baad7c2226d6ff85fd7a6da9ce2f3d087 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 23 Jan 2023 19:02:06 +0100 Subject: [PATCH 006/180] Ensure injection layer is recomputed when language changes Co-Authored-By: Max Brunsfeld --- crates/language/src/syntax_map.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index c0887809c2..275d05ba7f 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -5,7 +5,7 @@ use parking_lot::Mutex; use std::{ borrow::Cow, cell::RefCell, - cmp::{Ordering, Reverse}, + cmp::{self, Ordering, Reverse}, collections::BinaryHeap, ops::{Deref, DerefMut, Range}, sync::Arc, @@ -1004,15 +1004,21 @@ fn get_injections( prev_match = Some((mat.pattern_index, content_range.clone())); let combined = config.patterns[mat.pattern_index].combined; - let language_name = config.patterns[mat.pattern_index] - .language - .as_ref() - .map(|s| Cow::Borrowed(s.as_ref())) - .or_else(|| { - let ix = config.language_capture_ix?; - let node = mat.nodes_for_capture_index(ix).next()?; - Some(Cow::Owned(text.text_for_range(node.byte_range()).collect())) - }); + + let mut language_name = None; + let mut step_range = content_range.clone(); + if let Some(name) = config.patterns[mat.pattern_index].language.as_ref() { + language_name = Some(Cow::Borrowed(name.as_ref())) + } else if let Some(language_node) = config + .language_capture_ix + .and_then(|ix| mat.nodes_for_capture_index(ix).next()) + { + step_range.start = cmp::min(content_range.start, language_node.start_byte()); + step_range.end = cmp::max(content_range.end, language_node.end_byte()); + language_name = Some(Cow::Owned( + text.text_for_range(language_node.byte_range()).collect(), + )) + }; if let Some(language_name) = language_name { let language = language_registry @@ -1020,8 +1026,8 @@ fn get_injections( .or_else(|| language_registry.language_for_extension(&language_name)); if let Some(language) = language { result = true; - let range = text.anchor_before(content_range.start) - ..text.anchor_after(content_range.end); + let range = + text.anchor_before(step_range.start)..text.anchor_after(step_range.end); if combined { combined_injection_ranges .get_mut(&language.clone()) From 14c72cac5849d729ff8aea67daaf859efedfe4b0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 24 Jan 2023 12:25:12 +0100 Subject: [PATCH 007/180] Store syntax layers even if a language for the injection can't be found --- crates/language/src/syntax_map.rs | 349 ++++++++++++++++++------------ 1 file changed, 206 insertions(+), 143 deletions(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 275d05ba7f..7564d234e5 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -89,8 +89,34 @@ struct SyntaxMapMatchesLayer<'a> { struct SyntaxLayer { depth: usize, range: Range, - tree: tree_sitter::Tree, - language: Arc, + content: SyntaxLayerContent, +} + +#[derive(Clone)] +enum SyntaxLayerContent { + Parsed { + tree: tree_sitter::Tree, + language: Arc, + }, + Pending { + language_name: Arc, + }, +} + +impl SyntaxLayerContent { + fn language_id(&self) -> Option { + match self { + SyntaxLayerContent::Parsed { language, .. } => language.id(), + SyntaxLayerContent::Pending { .. } => None, + } + } + + fn tree(&self) -> Option<&Tree> { + match self { + SyntaxLayerContent::Parsed { tree, .. } => Some(tree), + SyntaxLayerContent::Pending { .. } => None, + } + } } #[derive(Debug)] @@ -130,12 +156,26 @@ struct SyntaxLayerPositionBeforeChange { struct ParseStep { depth: usize, - language: Arc, + language: ParseStepLanguage, range: Range, included_ranges: Vec, mode: ParseMode, } +enum ParseStepLanguage { + Loaded { language: Arc }, + Pending { name: Arc }, +} + +impl ParseStepLanguage { + fn id(&self) -> Option { + match self { + ParseStepLanguage::Loaded { language } => language.id(), + ParseStepLanguage::Pending { .. } => None, + } + } +} + enum ParseMode { Single, Combined { @@ -276,47 +316,49 @@ impl SyntaxSnapshot { } let mut layer = layer.clone(); - for (edit, edit_range) in &edits[first_edit_ix_for_depth..] { - // Ignore any edits that follow this layer. - if edit_range.start.cmp(&layer.range.end, text).is_ge() { - break; + if let SyntaxLayerContent::Parsed { tree, .. } = &mut layer.content { + for (edit, edit_range) in &edits[first_edit_ix_for_depth..] { + // Ignore any edits that follow this layer. + if edit_range.start.cmp(&layer.range.end, text).is_ge() { + break; + } + + // Apply any edits that intersect this layer to the layer's syntax tree. + let tree_edit = if edit_range.start.cmp(&layer.range.start, text).is_ge() { + tree_sitter::InputEdit { + start_byte: edit.new.start.0 - start_byte, + old_end_byte: edit.new.start.0 - start_byte + + (edit.old.end.0 - edit.old.start.0), + new_end_byte: edit.new.end.0 - start_byte, + start_position: (edit.new.start.1 - start_point).to_ts_point(), + old_end_position: (edit.new.start.1 - start_point + + (edit.old.end.1 - edit.old.start.1)) + .to_ts_point(), + new_end_position: (edit.new.end.1 - start_point).to_ts_point(), + } + } else { + let node = tree.root_node(); + tree_sitter::InputEdit { + start_byte: 0, + old_end_byte: node.end_byte(), + new_end_byte: 0, + start_position: Default::default(), + old_end_position: node.end_position(), + new_end_position: Default::default(), + } + }; + + tree.edit(&tree_edit); } - // Apply any edits that intersect this layer to the layer's syntax tree. - let tree_edit = if edit_range.start.cmp(&layer.range.start, text).is_ge() { - tree_sitter::InputEdit { - start_byte: edit.new.start.0 - start_byte, - old_end_byte: edit.new.start.0 - start_byte - + (edit.old.end.0 - edit.old.start.0), - new_end_byte: edit.new.end.0 - start_byte, - start_position: (edit.new.start.1 - start_point).to_ts_point(), - old_end_position: (edit.new.start.1 - start_point - + (edit.old.end.1 - edit.old.start.1)) - .to_ts_point(), - new_end_position: (edit.new.end.1 - start_point).to_ts_point(), - } - } else { - let node = layer.tree.root_node(); - tree_sitter::InputEdit { - start_byte: 0, - old_end_byte: node.end_byte(), - new_end_byte: 0, - start_position: Default::default(), - old_end_position: node.end_position(), - new_end_position: Default::default(), - } - }; - - layer.tree.edit(&tree_edit); + debug_assert!( + tree.root_node().end_byte() <= text.len(), + "tree's size {}, is larger than text size {}", + tree.root_node().end_byte(), + text.len(), + ); } - debug_assert!( - layer.tree.root_node().end_byte() <= text.len(), - "tree's size {}, is larger than text size {}", - layer.tree.root_node().end_byte(), - text.len(), - ); - layers.push(layer, text); cursor.next(text); } @@ -344,7 +386,9 @@ impl SyntaxSnapshot { let mut combined_injection_ranges = HashMap::default(); queue.push(ParseStep { depth: 0, - language: root_language.clone(), + language: ParseStepLanguage::Loaded { + language: root_language, + }, included_ranges: vec![tree_sitter::Range { start_byte: 0, end_byte: text.len(), @@ -415,12 +459,11 @@ impl SyntaxSnapshot { let (step_start_byte, step_start_point) = step.range.start.summary::<(usize, Point)>(text); let step_end_byte = step.range.end.to_offset(text); - let Some(grammar) = step.language.grammar.as_deref() else { continue }; let mut old_layer = cursor.item(); if let Some(layer) = old_layer { if layer.range.to_offset(text) == (step_start_byte..step_end_byte) - && layer.language.id() == step.language.id() + && layer.content.language_id() == step.language.id() { cursor.next(&text); } else { @@ -428,85 +471,99 @@ impl SyntaxSnapshot { } } - let tree; - let changed_ranges; - let mut included_ranges = step.included_ranges; - if let Some(old_layer) = old_layer { - if let ParseMode::Combined { - parent_layer_changed_ranges, - .. - } = step.mode - { - included_ranges = splice_included_ranges( - old_layer.tree.included_ranges(), - &parent_layer_changed_ranges, - &included_ranges, - ); - } + let content = match step.language { + ParseStepLanguage::Loaded { language } => { + let Some(grammar) = language.grammar() else { continue }; + let tree; + let changed_ranges; + let mut included_ranges = step.included_ranges; + if let Some(SyntaxLayerContent::Parsed { tree: old_tree, .. }) = + old_layer.map(|layer| &layer.content) + { + if let ParseMode::Combined { + parent_layer_changed_ranges, + .. + } = step.mode + { + included_ranges = splice_included_ranges( + old_tree.included_ranges(), + &parent_layer_changed_ranges, + &included_ranges, + ); + } - tree = parse_text( - grammar, - text.as_rope(), - step_start_byte, - step_start_point, - included_ranges, - Some(old_layer.tree.clone()), - ); - changed_ranges = join_ranges( - edits.iter().map(|e| e.new.clone()).filter(|range| { - range.start <= step_end_byte && range.end >= step_start_byte - }), - old_layer - .tree - .changed_ranges(&tree) - .map(|r| step_start_byte + r.start_byte..step_start_byte + r.end_byte), - ); - } else { - tree = parse_text( - grammar, - text.as_rope(), - step_start_byte, - step_start_point, - included_ranges, - None, - ); - changed_ranges = vec![step_start_byte..step_end_byte]; - } + tree = parse_text( + grammar, + text.as_rope(), + step_start_byte, + step_start_point, + included_ranges, + Some(old_tree.clone()), + ); + changed_ranges = join_ranges( + edits.iter().map(|e| e.new.clone()).filter(|range| { + range.start <= step_end_byte && range.end >= step_start_byte + }), + old_tree.changed_ranges(&tree).map(|r| { + step_start_byte + r.start_byte..step_start_byte + r.end_byte + }), + ); + } else { + tree = parse_text( + grammar, + text.as_rope(), + step_start_byte, + step_start_point, + included_ranges, + None, + ); + changed_ranges = vec![step_start_byte..step_end_byte]; + } + + if let (Some((config, registry)), false) = ( + grammar.injection_config.as_ref().zip(registry.as_ref()), + changed_ranges.is_empty(), + ) { + for range in &changed_ranges { + changed_regions.insert( + ChangedRegion { + depth: step.depth + 1, + range: text.anchor_before(range.start) + ..text.anchor_after(range.end), + }, + text, + ); + } + get_injections( + config, + text, + tree.root_node_with_offset( + step_start_byte, + step_start_point.to_ts_point(), + ), + registry, + step.depth + 1, + &changed_ranges, + &mut combined_injection_ranges, + &mut queue, + ); + } + + SyntaxLayerContent::Parsed { tree, language } + } + ParseStepLanguage::Pending { name } => SyntaxLayerContent::Pending { + language_name: name, + }, + }; layers.push( SyntaxLayer { depth: step.depth, range: step.range, - tree: tree.clone(), - language: step.language.clone(), + content, }, &text, ); - - if let (Some((config, registry)), false) = ( - grammar.injection_config.as_ref().zip(registry.as_ref()), - changed_ranges.is_empty(), - ) { - for range in &changed_ranges { - changed_regions.insert( - ChangedRegion { - depth: step.depth + 1, - range: text.anchor_before(range.start)..text.anchor_after(range.end), - }, - text, - ); - } - get_injections( - config, - text, - tree.root_node_with_offset(step_start_byte, step_start_point.to_ts_point()), - registry, - step.depth + 1, - &changed_ranges, - &mut combined_injection_ranges, - &mut queue, - ); - } } drop(cursor); @@ -586,20 +643,23 @@ impl SyntaxSnapshot { cursor.next(buffer); std::iter::from_fn(move || { - if let Some(layer) = cursor.item() { - let info = SyntaxLayerInfo { - language: &layer.language, - depth: layer.depth, - node: layer.tree.root_node_with_offset( - layer.range.start.to_offset(buffer), - layer.range.start.to_point(buffer).to_ts_point(), - ), - }; - cursor.next(buffer); - Some(info) - } else { - None + while let Some(layer) = cursor.item() { + if let SyntaxLayerContent::Parsed { tree, language } = &layer.content { + let info = SyntaxLayerInfo { + language, + depth: layer.depth, + node: tree.root_node_with_offset( + layer.range.start.to_offset(buffer), + layer.range.start.to_point(buffer).to_ts_point(), + ), + }; + cursor.next(buffer); + return Some(info); + } else { + cursor.next(buffer); + } } + None }) } } @@ -968,8 +1028,7 @@ fn get_injections( changed_ranges: &[Range], combined_injection_ranges: &mut HashMap, Vec>, queue: &mut BinaryHeap, -) -> bool { - let mut result = false; +) { let mut query_cursor = QueryCursorHandle::new(); let mut prev_match = None; @@ -1024,10 +1083,8 @@ fn get_injections( let language = language_registry .language_for_name(&language_name) .or_else(|| language_registry.language_for_extension(&language_name)); + let range = text.anchor_before(step_range.start)..text.anchor_after(step_range.end); if let Some(language) = language { - result = true; - let range = - text.anchor_before(step_range.start)..text.anchor_after(step_range.end); if combined { combined_injection_ranges .get_mut(&language.clone()) @@ -1036,12 +1093,22 @@ fn get_injections( } else { queue.push(ParseStep { depth, - language, + language: ParseStepLanguage::Loaded { language }, included_ranges: content_ranges, range, mode: ParseMode::Single, }); } + } else { + queue.push(ParseStep { + depth, + language: ParseStepLanguage::Pending { + name: language_name.into(), + }, + included_ranges: content_ranges, + range, + mode: ParseMode::Single, + }); } } } @@ -1052,7 +1119,7 @@ fn get_injections( let range = text.anchor_before(node.start_byte())..text.anchor_after(node.end_byte()); queue.push(ParseStep { depth, - language, + language: ParseStepLanguage::Loaded { language }, range, included_ranges, mode: ParseMode::Combined { @@ -1061,8 +1128,6 @@ fn get_injections( }, }) } - - result } fn splice_included_ranges( @@ -1361,7 +1426,7 @@ impl sum_tree::Item for SyntaxLayer { max_depth: self.depth, range: self.range.clone(), last_layer_range: self.range.clone(), - last_layer_language: self.language.id(), + last_layer_language: self.content.language_id(), } } } @@ -1371,7 +1436,7 @@ impl std::fmt::Debug for SyntaxLayer { f.debug_struct("SyntaxLayer") .field("depth", &self.depth) .field("range", &self.range) - .field("tree", &self.tree) + .field("tree", &self.content.tree()) .finish() } } @@ -2216,16 +2281,14 @@ mod tests { .zip(new_syntax_map.layers.iter()) { assert_eq!(old_layer.range, new_layer.range); + let Some(old_tree) = old_layer.content.tree() else { continue }; + let Some(new_tree) = new_layer.content.tree() else { continue }; let old_start_byte = old_layer.range.start.to_offset(old_buffer); let new_start_byte = new_layer.range.start.to_offset(new_buffer); let old_start_point = old_layer.range.start.to_point(old_buffer).to_ts_point(); let new_start_point = new_layer.range.start.to_point(new_buffer).to_ts_point(); - let old_node = old_layer - .tree - .root_node_with_offset(old_start_byte, old_start_point); - let new_node = new_layer - .tree - .root_node_with_offset(new_start_byte, new_start_point); + let old_node = old_tree.root_node_with_offset(old_start_byte, old_start_point); + let new_node = new_tree.root_node_with_offset(new_start_byte, new_start_point); check_node_edits( old_layer.depth, &old_layer.range, From f3509824e89b413713af9b60e3b5b7212abe4325 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 24 Jan 2023 12:55:49 +0100 Subject: [PATCH 008/180] WIP: Start on `SyntaxMapSnapshot::unknown_injection_languages` --- crates/language/src/syntax_map.rs | 50 ++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 7564d234e5..aa402bccd8 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -7,6 +7,7 @@ use std::{ cell::RefCell, cmp::{self, Ordering, Reverse}, collections::BinaryHeap, + iter, ops::{Deref, DerefMut, Range}, sync::Arc, }; @@ -133,6 +134,7 @@ struct SyntaxLayerSummary { range: Range, last_layer_range: Range, last_layer_language: Option, + contains_pending_layer: bool, } #[derive(Clone, Debug)] @@ -642,7 +644,7 @@ impl SyntaxSnapshot { }); cursor.next(buffer); - std::iter::from_fn(move || { + iter::from_fn(move || { while let Some(layer) = cursor.item() { if let SyntaxLayerContent::Parsed { tree, language } = &layer.content { let info = SyntaxLayerInfo { @@ -662,6 +664,27 @@ impl SyntaxSnapshot { None }) } + + pub fn unknown_injection_languages<'a>( + &'a self, + buffer: &'a BufferSnapshot, + ) -> impl 'a + Iterator> { + let mut cursor = self + .layers + .filter::<_, ()>(|summary| summary.contains_pending_layer); + cursor.next(buffer); + iter::from_fn(move || { + while let Some(layer) = cursor.item() { + if let SyntaxLayerContent::Pending { language_name } = &layer.content { + cursor.next(buffer); + return Some(language_name); + } else { + cursor.next(buffer); + } + } + None + }) + } } impl<'a> SyntaxMapCaptures<'a> { @@ -1356,6 +1379,7 @@ impl Default for SyntaxLayerSummary { range: Anchor::MAX..Anchor::MIN, last_layer_range: Anchor::MIN..Anchor::MAX, last_layer_language: None, + contains_pending_layer: false, } } } @@ -1377,6 +1401,7 @@ impl sum_tree::Summary for SyntaxLayerSummary { } self.last_layer_range = other.last_layer_range.clone(); self.last_layer_language = other.last_layer_language; + self.contains_pending_layer |= other.contains_pending_layer; } } @@ -1427,6 +1452,7 @@ impl sum_tree::Item for SyntaxLayer { range: self.range.clone(), last_layer_range: self.range.clone(), last_layer_language: self.content.language_id(), + contains_pending_layer: matches!(self.content, SyntaxLayerContent::Pending { .. }), } } } @@ -1715,6 +1741,28 @@ mod tests { "...(call method: (identifier) arguments: (argument_list (call method: (identifier) arguments: (argument_list) block: (block)...", ], ); + + // Replace Ruby with a language that hasn't been loaded yet. + let macro_name_range = range_for_text(&buffer, "ruby"); + buffer.edit([(macro_name_range, "erb")]); + syntax_map.interpolate(&buffer); + syntax_map.reparse(markdown.clone(), &buffer); + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(3, 0)..Point::new(3, 0), + &[ + "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter..." + ], + ); + assert_eq!( + syntax_map + .unknown_injection_languages(&buffer) + .collect::>(), + vec![&Arc::from("erb")] + ); + + registry.add(Arc::new(erb_lang())); } #[gpui::test] From c48e3f3d05a9bb19b8133de5ec7445ea623df560 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 24 Jan 2023 15:29:59 +0100 Subject: [PATCH 009/180] Reparse unknown injection ranges in buffer when adding a new language --- crates/language/src/buffer.rs | 33 ++++--- crates/language/src/language.rs | 7 ++ crates/language/src/syntax_map.rs | 151 ++++++++++++++++++------------ crates/project/src/project.rs | 14 ++- 4 files changed, 129 insertions(+), 76 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 110e10564c..78858e7d72 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -797,12 +797,16 @@ impl Buffer { self.parsing_in_background } + pub fn contains_unknown_injections(&self) -> bool { + self.syntax_map.lock().contains_unknown_injections() + } + #[cfg(test)] pub fn set_sync_parse_timeout(&mut self, timeout: Duration) { self.sync_parse_timeout = timeout; } - fn reparse(&mut self, cx: &mut ModelContext) { + pub fn reparse(&mut self, cx: &mut ModelContext) { if self.parsing_in_background { return; } @@ -819,13 +823,13 @@ impl Buffer { syntax_map.interpolate(&text); let language_registry = syntax_map.language_registry(); let mut syntax_snapshot = syntax_map.snapshot(); - let syntax_map_version = syntax_map.parsed_version(); drop(syntax_map); let parse_task = cx.background().spawn({ let language = language.clone(); + let language_registry = language_registry.clone(); async move { - syntax_snapshot.reparse(&syntax_map_version, &text, language_registry, language); + syntax_snapshot.reparse(&text, language_registry, language); syntax_snapshot } }); @@ -835,7 +839,7 @@ impl Buffer { .block_with_timeout(self.sync_parse_timeout, parse_task) { Ok(new_syntax_snapshot) => { - self.did_finish_parsing(new_syntax_snapshot, parsed_version, cx); + self.did_finish_parsing(new_syntax_snapshot, cx); return; } Err(parse_task) => { @@ -847,9 +851,15 @@ impl Buffer { this.language.as_ref().map_or(true, |current_language| { !Arc::ptr_eq(&language, current_language) }); - let parse_again = - this.version.changed_since(&parsed_version) || grammar_changed; - this.did_finish_parsing(new_syntax_map, parsed_version, cx); + let language_registry_changed = new_syntax_map + .contains_unknown_injections() + && language_registry.map_or(false, |registry| { + registry.version() != new_syntax_map.language_registry_version() + }); + let parse_again = language_registry_changed + || grammar_changed + || this.version.changed_since(&parsed_version); + this.did_finish_parsing(new_syntax_map, cx); this.parsing_in_background = false; if parse_again { this.reparse(cx); @@ -861,14 +871,9 @@ impl Buffer { } } - fn did_finish_parsing( - &mut self, - syntax_snapshot: SyntaxSnapshot, - version: clock::Global, - cx: &mut ModelContext, - ) { + fn did_finish_parsing(&mut self, syntax_snapshot: SyntaxSnapshot, cx: &mut ModelContext) { self.parse_count += 1; - self.syntax_map.lock().did_parse(syntax_snapshot, version); + self.syntax_map.lock().did_parse(syntax_snapshot); self.request_autoindent(cx); cx.emit(Event::Reparsed); cx.notify(); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 1ddd3e3939..6e1a120c81 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -422,6 +422,7 @@ pub struct LanguageRegistry { >, subscription: RwLock<(watch::Sender<()>, watch::Receiver<()>)>, theme: RwLock>>, + version: AtomicUsize, } impl LanguageRegistry { @@ -436,6 +437,7 @@ impl LanguageRegistry { lsp_binary_paths: Default::default(), subscription: RwLock::new(watch::channel()), theme: Default::default(), + version: Default::default(), } } @@ -449,6 +451,7 @@ impl LanguageRegistry { language.set_theme(&theme.editor.syntax); } self.languages.write().push(language); + self.version.fetch_add(1, SeqCst); *self.subscription.write().0.borrow_mut() = (); } @@ -456,6 +459,10 @@ impl LanguageRegistry { self.subscription.read().1.clone() } + pub fn version(&self) -> usize { + self.version.load(SeqCst) + } + pub fn set_theme(&self, theme: Arc) { *self.theme.write() = Some(theme.clone()); for language in self.languages.read().iter() { diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index aa402bccd8..ada981ec26 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -27,8 +27,6 @@ lazy_static! { #[derive(Default)] pub struct SyntaxMap { - parsed_version: clock::Global, - interpolated_version: clock::Global, snapshot: SyntaxSnapshot, language_registry: Option>, } @@ -36,6 +34,9 @@ pub struct SyntaxMap { #[derive(Clone, Default)] pub struct SyntaxSnapshot { layers: SumTree, + parsed_version: clock::Global, + interpolated_version: clock::Global, + language_registry_version: usize, } #[derive(Default)] @@ -134,7 +135,7 @@ struct SyntaxLayerSummary { range: Range, last_layer_range: Range, last_layer_language: Option, - contains_pending_layer: bool, + contains_unknown_injections: bool, } #[derive(Clone, Debug)] @@ -218,30 +219,17 @@ impl SyntaxMap { self.language_registry.clone() } - pub fn parsed_version(&self) -> clock::Global { - self.parsed_version.clone() - } - pub fn interpolate(&mut self, text: &BufferSnapshot) { - self.snapshot.interpolate(&self.interpolated_version, text); - self.interpolated_version = text.version.clone(); + self.snapshot.interpolate(text); } #[cfg(test)] pub fn reparse(&mut self, language: Arc, text: &BufferSnapshot) { - self.snapshot.reparse( - &self.parsed_version, - text, - self.language_registry.clone(), - language, - ); - self.parsed_version = text.version.clone(); - self.interpolated_version = text.version.clone(); + self.snapshot + .reparse(text, self.language_registry.clone(), language); } - pub fn did_parse(&mut self, snapshot: SyntaxSnapshot, version: clock::Global) { - self.interpolated_version = version.clone(); - self.parsed_version = version; + pub fn did_parse(&mut self, snapshot: SyntaxSnapshot) { self.snapshot = snapshot; } @@ -255,10 +243,12 @@ impl SyntaxSnapshot { self.layers.is_empty() } - pub fn interpolate(&mut self, from_version: &clock::Global, text: &BufferSnapshot) { + fn interpolate(&mut self, text: &BufferSnapshot) { let edits = text - .anchored_edits_since::<(usize, Point)>(&from_version) + .anchored_edits_since::<(usize, Point)>(&self.interpolated_version) .collect::>(); + self.interpolated_version = text.version().clone(); + if edits.is_empty() { return; } @@ -372,12 +362,53 @@ impl SyntaxSnapshot { pub fn reparse( &mut self, - from_version: &clock::Global, text: &BufferSnapshot, registry: Option>, root_language: Arc, ) { - let edits = text.edits_since::(from_version).collect::>(); + let edit_ranges = text + .edits_since::(&self.parsed_version) + .map(|edit| edit.new) + .collect::>(); + self.reparse_with_ranges(text, root_language.clone(), edit_ranges, registry.as_ref()); + + if let Some(registry) = registry { + if registry.version() != self.language_registry_version { + let mut resolved_injection_ranges = Vec::new(); + let mut cursor = self + .layers + .filter::<_, ()>(|summary| summary.contains_unknown_injections); + cursor.next(text); + while let Some(layer) = cursor.item() { + let SyntaxLayerContent::Pending { language_name } = &layer.content else { unreachable!() }; + if language_for_injection(language_name, ®istry).is_some() { + resolved_injection_ranges.push(layer.range.to_offset(text)); + } + + cursor.next(text); + } + drop(cursor); + + if !resolved_injection_ranges.is_empty() { + self.reparse_with_ranges( + text, + root_language, + resolved_injection_ranges, + Some(®istry), + ); + } + self.language_registry_version = registry.version(); + } + } + } + + fn reparse_with_ranges( + &mut self, + text: &BufferSnapshot, + root_language: Arc, + invalidated_ranges: Vec>, + registry: Option<&Arc>, + ) { let max_depth = self.layers.summary().max_depth; let mut cursor = self.layers.cursor::(); cursor.next(&text); @@ -503,7 +534,7 @@ impl SyntaxSnapshot { Some(old_tree.clone()), ); changed_ranges = join_ranges( - edits.iter().map(|e| e.new.clone()).filter(|range| { + invalidated_ranges.iter().cloned().filter(|range| { range.start <= step_end_byte && range.end >= step_start_byte }), old_tree.changed_ranges(&tree).map(|r| { @@ -570,6 +601,8 @@ impl SyntaxSnapshot { drop(cursor); self.layers = layers; + self.interpolated_version = text.version.clone(); + self.parsed_version = text.version.clone(); } pub fn single_tree_captures<'a>( @@ -665,25 +698,12 @@ impl SyntaxSnapshot { }) } - pub fn unknown_injection_languages<'a>( - &'a self, - buffer: &'a BufferSnapshot, - ) -> impl 'a + Iterator> { - let mut cursor = self - .layers - .filter::<_, ()>(|summary| summary.contains_pending_layer); - cursor.next(buffer); - iter::from_fn(move || { - while let Some(layer) = cursor.item() { - if let SyntaxLayerContent::Pending { language_name } = &layer.content { - cursor.next(buffer); - return Some(language_name); - } else { - cursor.next(buffer); - } - } - None - }) + pub fn contains_unknown_injections(&self) -> bool { + self.layers.summary().contains_unknown_injections + } + + pub fn language_registry_version(&self) -> usize { + self.language_registry_version } } @@ -1058,7 +1078,7 @@ fn get_injections( combined_injection_ranges.clear(); for pattern in &config.patterns { if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) { - if let Some(language) = language_registry.language_for_name(language_name) { + if let Some(language) = language_for_injection(language_name, language_registry) { combined_injection_ranges.insert(language, Vec::new()); } } @@ -1103,9 +1123,7 @@ fn get_injections( }; if let Some(language_name) = language_name { - let language = language_registry - .language_for_name(&language_name) - .or_else(|| language_registry.language_for_extension(&language_name)); + let language = language_for_injection(&language_name, language_registry); let range = text.anchor_before(step_range.start)..text.anchor_after(step_range.end); if let Some(language) = language { if combined { @@ -1153,6 +1171,15 @@ fn get_injections( } } +fn language_for_injection( + language_name: &str, + language_registry: &LanguageRegistry, +) -> Option> { + language_registry + .language_for_name(language_name) + .or_else(|| language_registry.language_for_extension(language_name)) +} + fn splice_included_ranges( mut ranges: Vec, changed_ranges: &[Range], @@ -1379,7 +1406,7 @@ impl Default for SyntaxLayerSummary { range: Anchor::MAX..Anchor::MIN, last_layer_range: Anchor::MIN..Anchor::MAX, last_layer_language: None, - contains_pending_layer: false, + contains_unknown_injections: false, } } } @@ -1401,7 +1428,7 @@ impl sum_tree::Summary for SyntaxLayerSummary { } self.last_layer_range = other.last_layer_range.clone(); self.last_layer_language = other.last_layer_language; - self.contains_pending_layer |= other.contains_pending_layer; + self.contains_unknown_injections |= other.contains_unknown_injections; } } @@ -1452,7 +1479,7 @@ impl sum_tree::Item for SyntaxLayer { range: self.range.clone(), last_layer_range: self.range.clone(), last_layer_language: self.content.language_id(), - contains_pending_layer: matches!(self.content, SyntaxLayerContent::Pending { .. }), + contains_unknown_injections: matches!(self.content, SyntaxLayerContent::Pending { .. }), } } } @@ -1744,7 +1771,7 @@ mod tests { // Replace Ruby with a language that hasn't been loaded yet. let macro_name_range = range_for_text(&buffer, "ruby"); - buffer.edit([(macro_name_range, "erb")]); + buffer.edit([(macro_name_range, "html")]); syntax_map.interpolate(&buffer); syntax_map.reparse(markdown.clone(), &buffer); assert_layers_for_range( @@ -1755,14 +1782,20 @@ mod tests { "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter..." ], ); - assert_eq!( - syntax_map - .unknown_injection_languages(&buffer) - .collect::>(), - vec![&Arc::from("erb")] - ); + assert!(syntax_map.contains_unknown_injections()); - registry.add(Arc::new(erb_lang())); + registry.add(Arc::new(html_lang())); + syntax_map.reparse(markdown.clone(), &buffer); + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(3, 0)..Point::new(3, 0), + &[ + "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...", + "(fragment (text))", + ], + ); + assert!(!syntax_map.contains_unknown_injections()); } #[gpui::test] diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 995a6514c5..f324865b5c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1765,10 +1765,14 @@ impl Project { if let Some(project) = project.upgrade(&cx) { project.update(&mut cx, |project, cx| { let mut buffers_without_language = Vec::new(); + let mut buffers_with_unknown_injections = Vec::new(); for buffer in project.opened_buffers.values() { - if let Some(buffer) = buffer.upgrade(cx) { - if buffer.read(cx).language().is_none() { - buffers_without_language.push(buffer); + if let Some(handle) = buffer.upgrade(cx) { + let buffer = &handle.read(cx); + if buffer.language().is_none() { + buffers_without_language.push(handle); + } else if buffer.contains_unknown_injections() { + buffers_with_unknown_injections.push(handle); } } } @@ -1777,6 +1781,10 @@ impl Project { project.assign_language_to_buffer(&buffer, cx); project.register_buffer_with_language_server(&buffer, cx); } + + for buffer in buffers_with_unknown_injections { + buffer.update(cx, |buffer, cx| buffer.reparse(cx)); + } }); } } From 4d73d4b1b92cb16ae09735cd8961ff491b44dcd2 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 24 Jan 2023 17:01:14 -0500 Subject: [PATCH 010/180] Insert macOS file association metadata during bundle process --- crates/zed/BundleDocumentTypes.plist | 62 ++++++++++++++++++++++++++++ script/bundle | 9 +++- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 crates/zed/BundleDocumentTypes.plist diff --git a/crates/zed/BundleDocumentTypes.plist b/crates/zed/BundleDocumentTypes.plist new file mode 100644 index 0000000000..459169afc8 --- /dev/null +++ b/crates/zed/BundleDocumentTypes.plist @@ -0,0 +1,62 @@ +CFBundleDocumentTypes + + + CFBundleTypeIconFile + Document + CFBundleTypeRole + Editor + LSHandlerRank + Default + LSItemContentTypes + + public.text + public.plain-text + public.utf8-plain-text + + + + CFBundleTypeIconFile + Document + CFBundleTypeName + Zed Text Document + CFBundleTypeRole + Editor + CFBundleTypeOSTypes + + **** + + LSHandlerRank + Default + CFBundleTypeExtensions + + Gemfile + c + c++ + cc + cpp + css + erb + ex + exs + go + h + h++ + hh + hpp + html + js + json + jsx + md + py + rb + rkt + rs + scm + toml + ts + tsx + txt + + + diff --git a/script/bundle b/script/bundle index 94efbdf0af..6fe93ed66c 100755 --- a/script/bundle +++ b/script/bundle @@ -22,7 +22,7 @@ cargo build --release --package cli --target x86_64-apple-darwin echo "Creating application bundle" pushd crates/zed -channel=$(cat RELEASE_CHANNEL) +channel=$(/{while(getline line<\"./crates/zed/BundleDocumentTypes.plist\"){print line}}1" \ + "${app_path}/Contents/WithoutDocumentTypes.plist" \ + > "${app_path}/Contents/Info.plist" +rm "${app_path}/Contents/WithoutDocumentTypes.plist" + if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then echo "Signing bundle with Apple-issued certificate" security create-keychain -p "$MACOS_CERTIFICATE_PASSWORD" zed.keychain || echo "" From 51984f0d39438b91865b07e7ff4400b3b702a593 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 24 Jan 2023 14:09:24 -0800 Subject: [PATCH 011/180] Fix feedback editor compile error due to LanguageRegistry API change --- crates/feedback/src/feedback_editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 8185fbad9a..ce0da1cf3c 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -123,7 +123,7 @@ impl FeedbackEditor { } fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { - let markdown_language = project.read(cx).languages().get_language("Markdown"); + let markdown_language = project.read(cx).languages().language_for_name("Markdown"); let buffer = project .update(cx, |project, cx| { From 588419492a63127eb2cb2b0af0eb665899fe144e Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 24 Jan 2023 17:18:36 -0500 Subject: [PATCH 012/180] Add upper character count limit --- crates/feedback/src/feedback_editor.rs | 37 +++++++++++++++----------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 8185fbad9a..4ad29f8e33 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -1,4 +1,7 @@ -use std::{ops::Range, sync::Arc}; +use std::{ + ops::{Range, RangeInclusive}, + sync::Arc, +}; use anyhow::bail; use client::{Client, ZED_SECRET_CLIENT_TOKEN}; @@ -32,11 +35,7 @@ lazy_static! { std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string()); } -const FEEDBACK_CHAR_COUNT_RANGE: Range = Range { - start: 10, - end: 1000, -}; - +const FEEDBACK_CHAR_LIMIT: RangeInclusive = 10..=5000; const FEEDBACK_PLACEHOLDER_TEXT: &str = "Thanks for spending time with Zed. Enter your feedback here as Markdown. Save the tab to submit your feedback."; const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = "Feedback failed to submit, see error log for details."; @@ -139,18 +138,24 @@ impl FeedbackEditor { _: gpui::ModelHandle, cx: &mut ViewContext, ) -> Task> { - let feedback_text_length = self.editor.read(cx).buffer().read(cx).len(cx); + let feedback_char_count = self.editor.read(cx).buffer().read(cx).len(cx); - if feedback_text_length <= FEEDBACK_CHAR_COUNT_RANGE.start { - cx.prompt( - PromptLevel::Critical, - &format!( - "Feedback must be longer than {} characters", - FEEDBACK_CHAR_COUNT_RANGE.start - ), - &["OK"], - ); + let error = if feedback_char_count < *FEEDBACK_CHAR_LIMIT.start() { + Some(format!( + "Feedback can't be shorter than {} characters.", + FEEDBACK_CHAR_LIMIT.start() + )) + } else if feedback_char_count > *FEEDBACK_CHAR_LIMIT.end() { + Some(format!( + "Feedback can't be longer than {} characters.", + FEEDBACK_CHAR_LIMIT.end() + )) + } else { + None + }; + if let Some(error) = error { + cx.prompt(PromptLevel::Critical, &error, &["OK"]); return Task::ready(Ok(())); } From 0414723a548a898777a5312f48141b612225c0c6 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 24 Jan 2023 18:48:15 -0500 Subject: [PATCH 013/180] Decode URL from `openURLs` to handle percent encoded paths Co-Authored-By: Nathan Sobo --- Cargo.lock | 1 + crates/gpui/src/platform/mac/platform.rs | 4 ++-- crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 7 +++++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f75fd3648b..f858a61aaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8310,6 +8310,7 @@ dependencies = [ "tree-sitter-typescript", "unindent", "url", + "urlencoding", "util", "vim", "workspace", diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 0638689cd4..37406858ec 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -853,8 +853,8 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { (0..urls.count()) .into_iter() .filter_map(|i| { - let path = urls.objectAtIndex(i); - match CStr::from_ptr(path.absoluteString().UTF8String() as *mut c_char).to_str() { + let url = urls.objectAtIndex(i); + match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() { Ok(string) => Some(string.to_string()), Err(err) => { log::error!("error converting path to string: {}", err); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d159271f99..c2b15c7cb5 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -110,6 +110,7 @@ tree-sitter-html = "0.19.0" tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"} tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} url = "2.2" +urlencoding = "2.1.2" [dev-dependencies] call = { path = "../call", features = ["test-support"] } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index fe7e95cf24..79183f3128 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -32,8 +32,8 @@ use settings::{ }; use simplelog::ConfigBuilder; use smol::process::Command; -use std::fs::OpenOptions; use std::{env, ffi::OsStr, panic, path::PathBuf, sync::Arc, thread, time::Duration}; +use std::{fs::OpenOptions, os::unix::prelude::OsStrExt}; use terminal_view::{get_working_directory, TerminalView}; use fs::RealFs; @@ -90,7 +90,10 @@ fn main() { let paths: Vec<_> = urls .iter() .flat_map(|url| url.strip_prefix("file://")) - .map(|path| PathBuf::from(path)) + .map(|url| { + let decoded = urlencoding::decode_binary(url.as_bytes()); + PathBuf::from(OsStr::from_bytes(decoded.as_ref())) + }) .collect(); open_paths_tx .unbounded_send(paths) From 3329b2bbd6994a4fcd47b29d950c591b07a48c16 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 24 Jan 2023 19:46:04 -0500 Subject: [PATCH 014/180] Remove `gpui::` prefix from parameters --- crates/feedback/src/feedback_editor.rs | 32 +++++++++----------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index b3322f50db..3a6bc744f2 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -12,7 +12,7 @@ use gpui::{ elements::{ChildView, Flex, Label, MouseEventHandler, ParentElement, Stack, Text}, serde_json, AnyViewHandle, AppContext, CursorStyle, Element, ElementBox, Entity, ModelHandle, MouseButton, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, - ViewHandle, + ViewHandle, WeakViewHandle, }; use isahc::Request; use language::Buffer; @@ -79,12 +79,7 @@ impl View for FeedbackButton { } impl StatusItemView for FeedbackButton { - fn set_active_pane_item( - &mut self, - _: Option<&dyn ItemHandle>, - _: &mut gpui::ViewContext, - ) { - } + fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext) {} } #[derive(Serialize)] @@ -135,7 +130,7 @@ impl FeedbackEditor { fn handle_save( &mut self, - _: gpui::ModelHandle, + _: ModelHandle, cx: &mut ViewContext, ) -> Task> { let feedback_char_count = self.editor.read(cx).buffer().read(cx).len(cx); @@ -269,12 +264,7 @@ impl Entity for FeedbackEditor { } impl Item for FeedbackEditor { - fn tab_content( - &self, - _: Option, - style: &theme::Tab, - _: &gpui::AppContext, - ) -> ElementBox { + fn tab_content(&self, _: Option, style: &theme::Tab, _: &AppContext) -> ElementBox { Flex::row() .with_child( Label::new("Feedback".to_string(), style.label.clone()) @@ -293,19 +283,19 @@ impl Item for FeedbackEditor { Vec::new() } - fn is_singleton(&self, _: &gpui::AppContext) -> bool { + fn is_singleton(&self, _: &AppContext) -> bool { true } fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} - fn can_save(&self, _: &gpui::AppContext) -> bool { + fn can_save(&self, _: &AppContext) -> bool { true } fn save( &mut self, - project: gpui::ModelHandle, + project: ModelHandle, cx: &mut ViewContext, ) -> Task> { self.handle_save(project, cx) @@ -313,7 +303,7 @@ impl Item for FeedbackEditor { fn save_as( &mut self, - project: gpui::ModelHandle, + project: ModelHandle, _: std::path::PathBuf, cx: &mut ViewContext, ) -> Task> { @@ -322,7 +312,7 @@ impl Item for FeedbackEditor { fn reload( &mut self, - _: gpui::ModelHandle, + _: ModelHandle, _: &mut ViewContext, ) -> Task> { unreachable!("reload should not have been called") @@ -356,8 +346,8 @@ impl Item for FeedbackEditor { } fn deserialize( - _: gpui::ModelHandle, - _: gpui::WeakViewHandle, + _: ModelHandle, + _: WeakViewHandle, _: workspace::WorkspaceId, _: workspace::ItemId, _: &mut ViewContext, From db978fcb6cf08ce3112547c22cf6c5bcd5b15d40 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 25 Jan 2023 13:00:20 +0200 Subject: [PATCH 015/180] Add an x mark icon to the list of contacts We want to be able to remove contacts from our list. This was not possible. This change add an icon and dispatches the RemoveContact action. Co-Authored-By: Antonio Scandurra --- crates/collab_ui/src/contact_list.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index 743b98adb0..e2f5e50cdb 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -1051,7 +1051,7 @@ impl ContactList { let user_id = contact.user.id; let initial_project = project.clone(); let mut element = - MouseEventHandler::::new(contact.user.id as usize, cx, |_, _| { + MouseEventHandler::::new(contact.user.id as usize, cx, |_, cx| { Flex::row() .with_children(contact.user.avatar.clone().map(|avatar| { let status_badge = if contact.online { @@ -1093,6 +1093,27 @@ impl ContactList { .flex(1., true) .boxed(), ) + .with_child( + MouseEventHandler::::new( + contact.user.id as usize, + cx, + |mouse_state, _| { + let button_style = + theme.contact_button.style_for(mouse_state, false); + render_icon_button(button_style, "icons/x_mark_8.svg") + .aligned() + .flex_float() + .boxed() + }, + ) + .with_padding(Padding::uniform(2.)) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(RemoveContact(user_id)) + }) + .flex_float() + .boxed(), + ) .with_children(if calling { Some( Label::new("Calling".to_string(), theme.calling_indicator.text.clone()) From 5d4eb2b7ae568132fd7f602e67f5208439d864b4 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 25 Jan 2023 13:04:33 +0200 Subject: [PATCH 016/180] Push responder and requester to remove_contacts When we ask the server to remove a contact we need to push the requester and responder ids to `remove_contacts` so that when the UI updates, the correct contacts will disappear from the list. Co-Authored-By: Antonio Scandurra --- crates/collab/src/rpc.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 92d4935b23..54ffd9a958 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1966,6 +1966,7 @@ async fn remove_contact( let pool = session.connection_pool().await; // Update outgoing contact requests of requester let mut update = proto::UpdateContacts::default(); + update.remove_contacts.push(responder_id.to_proto()); update .remove_outgoing_requests .push(responder_id.to_proto()); @@ -1975,6 +1976,7 @@ async fn remove_contact( // Update incoming contact requests of responder let mut update = proto::UpdateContacts::default(); + update.remove_contacts.push(requester_id.to_proto()); update .remove_incoming_requests .push(requester_id.to_proto()); From e928c1c61e4ac17213a2abdc18fdc3ec01988b09 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 25 Jan 2023 17:31:42 +0200 Subject: [PATCH 017/180] Test removing a contact Co-Authored-By: Antonio Scandurra --- crates/collab/src/tests/integration_tests.rs | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 3f2a777f87..a645d6dc71 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -5291,6 +5291,27 @@ async fn test_contacts( [("user_b".to_string(), "online", "free")] ); + // Test removing a contact + client_b + .user_store + .update(cx_b, |store, cx| { + store.remove_contact(client_c.user_id().unwrap(), cx) + }) + .await + .unwrap(); + deterministic.run_until_parked(); + assert_eq!( + contacts(&client_b, cx_b), + [ + ("user_a".to_string(), "offline", "free"), + ("user_d".to_string(), "online", "free") + ] + ); + assert_eq!( + contacts(&client_c, cx_c), + [("user_a".to_string(), "offline", "free"),] + ); + fn contacts( client: &TestClient, cx: &TestAppContext, From 35524db1361ec7a071f6baf62c9d5fed3dfabe95 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 25 Jan 2023 18:55:08 +0200 Subject: [PATCH 018/180] Add a confirmation prompt Co-Authored-By: Antonio Scandurra --- crates/collab_ui/src/contact_list.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index e2f5e50cdb..c4250c142b 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -1,22 +1,22 @@ -use std::{mem, sync::Arc}; - use crate::contacts_popover; use call::ActiveCall; use client::{proto::PeerId, Contact, User, UserStore}; use editor::{Cancel, Editor}; +use futures::StreamExt; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ elements::*, geometry::{rect::RectF, vector::vec2f}, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, - AppContext, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, - Subscription, View, ViewContext, ViewHandle, + AppContext, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, PromptLevel, + RenderContext, Subscription, View, ViewContext, ViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::Project; use serde::Deserialize; use settings::Settings; +use std::{mem, sync::Arc}; use theme::IconButton; use util::ResultExt; use workspace::{JoinProject, OpenSharedScreen}; @@ -299,9 +299,19 @@ impl ContactList { } fn remove_contact(&mut self, request: &RemoveContact, cx: &mut ViewContext) { - self.user_store - .update(cx, |store, cx| store.remove_contact(request.0, cx)) - .detach(); + let user_id = request.0; + let user_store = self.user_store.clone(); + let prompt_message = "Are you sure you want to remove this contact?"; + let mut answer = cx.prompt(PromptLevel::Warning, prompt_message, &["Remove", "Cancel"]); + cx.spawn(|_, mut cx| async move { + if answer.next().await == Some(0) { + user_store + .update(&mut cx, |store, cx| store.remove_contact(user_id, cx)) + .await + .unwrap(); + } + }) + .detach(); } fn respond_to_contact_request( From 426aeb7c5e919801360eb46ccd6354f34e12da65 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 20 Jan 2023 18:10:24 -0800 Subject: [PATCH 019/180] WIP - adds platform APIs for checking the top most window --- crates/gpui/src/app.rs | 7 +++ crates/gpui/src/platform.rs | 3 +- crates/gpui/src/platform/mac/platform.rs | 15 ++++- crates/gpui/src/platform/mac/status_item.rs | 4 ++ crates/gpui/src/platform/mac/window.rs | 30 ++++++++++ crates/gpui/src/platform/test.rs | 6 +- crates/gpui/src/presenter.rs | 9 ++- crates/gpui/src/presenter/event_dispatcher.rs | 1 + crates/workspace/src/workspace.rs | 56 ++++++++++++++++++- 9 files changed, 126 insertions(+), 5 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index ad1fad85b1..0e5697c30b 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -21,6 +21,7 @@ use std::{ use anyhow::{anyhow, Context, Result}; use lazy_static::lazy_static; use parking_lot::Mutex; +use pathfinder_geometry::vector::Vector2F; use postage::oneshot; use smallvec::SmallVec; use smol::prelude::*; @@ -865,6 +866,12 @@ impl MutableAppContext { } } + pub fn screen_position(&self, window_id: usize, view_position: &Vector2F) -> Option { + self.presenters_and_platform_windows + .get(&window_id) + .map(|(_, window)| window.screen_position(view_position)) + } + pub fn window_ids(&self) -> impl Iterator + '_ { self.cx.windows.keys().cloned() } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 99d607e407..0601fd9764 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -64,7 +64,7 @@ pub trait Platform: Send + Sync { fn read_credentials(&self, url: &str) -> Result)>>; fn delete_credentials(&self, url: &str) -> Result<()>; - fn set_cursor_style(&self, style: CursorStyle); + fn set_cursor_style(&self, style: CursorStyle, window_id: usize, screen_position: &Vector2F); fn should_auto_hide_scrollbars(&self) -> bool; fn local_timezone(&self) -> UtcOffset; @@ -145,6 +145,7 @@ pub trait Window { fn present_scene(&mut self, scene: Scene); fn appearance(&self) -> Appearance; fn on_appearance_changed(&mut self, callback: Box); + fn screen_position(&self, view_position: &Vector2F) -> Vector2F; } #[derive(Debug)] diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 37406858ec..f1b7f75c03 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -37,6 +37,7 @@ use objc::{ runtime::{Class, Object, Sel}, sel, sel_impl, }; +use pathfinder_geometry::vector::Vector2F; use postage::oneshot; use ptr::null_mut; use std::{ @@ -695,7 +696,19 @@ impl platform::Platform for MacPlatform { Ok(()) } - fn set_cursor_style(&self, style: CursorStyle) { + fn set_cursor_style(&self, style: CursorStyle, window_id: usize, screen_position: &Vector2F) { + let top_most = Window::window_id_under(screen_position); + if top_most.is_none() { + return; + } + if top_most.unwrap() != window_id { + return; + } + + dbg!(top_most.unwrap(), window_id); + + dbg!(style); + unsafe { let cursor: id = match style { CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor], diff --git a/crates/gpui/src/platform/mac/status_item.rs b/crates/gpui/src/platform/mac/status_item.rs index 33feb4808f..d05c559772 100644 --- a/crates/gpui/src/platform/mac/status_item.rs +++ b/crates/gpui/src/platform/mac/status_item.rs @@ -258,6 +258,10 @@ impl platform::Window for StatusItem { crate::Appearance::from_native(appearance) } } + + fn screen_position(&self, _view_position: &Vector2F) -> Vector2F { + unimplemented!() + } } impl StatusItemState { diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 6126533644..032b88507b 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -559,6 +559,26 @@ impl Window { } } } + + pub fn window_id_under(screen_position: &Vector2F) -> Option { + unsafe { + let app = NSApplication::sharedApplication(nil); + + let point = NSPoint::new(screen_position.x() as f64, screen_position.y() as f64); + let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:point belowWindowWithWindowNumber:0 as NSInteger]; + // For some reason this API doesn't work when our two windows are on top of each other + let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number]; + dbg!(top_most_window); + let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS]; + let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS]; + if is_panel | is_window { + let id = get_window_state(&*top_most_window).borrow().id; + Some(id) + } else { + None + } + } + } } impl Drop for Window { @@ -755,6 +775,16 @@ impl platform::Window for Window { fn on_appearance_changed(&mut self, callback: Box) { self.0.borrow_mut().appearance_changed_callback = Some(callback); } + + fn screen_position(&self, view_position: &Vector2F) -> Vector2F { + let self_borrow = self.0.borrow_mut(); + unsafe { + let point = NSPoint::new(view_position.x() as f64, view_position.y() as f64); + let screen_point: NSPoint = + msg_send![self_borrow.native_window, convertPointToScreen: point]; + vec2f(screen_point.x as f32, screen_point.y as f32) + } + } } impl WindowState { diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 00cd524c1d..7295bf525b 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -178,7 +178,7 @@ impl super::Platform for Platform { Ok(()) } - fn set_cursor_style(&self, style: CursorStyle) { + fn set_cursor_style(&self, style: CursorStyle, _window_id: usize, _position: &Vector2F) { *self.cursor.lock() = style; } @@ -332,6 +332,10 @@ impl super::Window for Window { } fn on_appearance_changed(&mut self, _: Box) {} + + fn screen_position(&self, view_position: &Vector2F) -> Vector2F { + view_position.clone() + } } pub fn platform() -> Platform { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 0909d95fd0..f74eef463a 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -316,7 +316,14 @@ impl Presenter { break; } } - cx.platform().set_cursor_style(style_to_assign); + + if let Some(screen_position) = cx.screen_position(self.window_id, position) { + cx.platform().set_cursor_style( + style_to_assign, + self.window_id, + &screen_position, + ); + } if !event_reused { if pressed_button.is_some() { diff --git a/crates/gpui/src/presenter/event_dispatcher.rs b/crates/gpui/src/presenter/event_dispatcher.rs index 4c72334910..960c565bd4 100644 --- a/crates/gpui/src/presenter/event_dispatcher.rs +++ b/crates/gpui/src/presenter/event_dispatcher.rs @@ -209,6 +209,7 @@ impl EventDispatcher { break; } } + cx.platform().set_cursor_style(style_to_assign); if !event_reused { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ec7ba8fae0..71c5ef97a2 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,8 +31,9 @@ use futures::{ }; use gpui::{ actions, + color::Color, elements::*, - geometry::vector::Vector2F, + geometry::vector::{vec2f, Vector2F}, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, @@ -263,6 +264,59 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { client.add_view_request_handler(Workspace::handle_follow); client.add_view_message_handler(Workspace::handle_unfollow); client.add_view_message_handler(Workspace::handle_update_followers); + + // REMEMBER TO DELETE THE SHOW NOTIF + cx.add_action( + |_workspace: &mut Workspace, _: &ShowNotif, cx: &mut ViewContext| { + struct DummyView; + + impl Entity for DummyView { + type Event = (); + } + + impl View for DummyView { + fn ui_name() -> &'static str { + "DummyView" + } + fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { + MouseEventHandler::::new(0, cx, |state, _cx| { + Empty::new() + .contained() + .with_background_color(if state.hovered() { + Color::red() + } else { + Color::blue() + }) + .constrained() + .with_width(200.) + .with_height(200.) + .boxed() + }) + .on_click(MouseButton::Left, |_, _| { + println!("click"); + }) + .with_cursor_style(CursorStyle::ResizeUpDown) + .boxed() + } + } + + cx.add_window( + WindowOptions { + bounds: gpui::WindowBounds::Fixed(gpui::geometry::rect::RectF::new( + vec2f(400., 200.), + vec2f(300., 200.), + )), + titlebar: None, + center: false, + focus: false, + kind: gpui::WindowKind::PopUp, + is_movable: true, + screen: None, + }, + |_cx| DummyView, + ) + }, + ) } type ProjectItemBuilders = HashMap< From 27a80a1c94c56b2c9f23cd26acf83982e1d40c41 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 23 Jan 2023 10:45:31 -0800 Subject: [PATCH 020/180] WIP --- crates/gpui/src/platform/mac/event.rs | 2 ++ crates/gpui/src/platform/mac/platform.rs | 35 ++++++++++-------------- crates/gpui/src/platform/mac/window.rs | 12 +++++--- crates/gpui/src/presenter.rs | 3 ++ 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index 2f29898c26..6882721372 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -125,6 +125,7 @@ impl Event { button, position: vec2f( native_event.locationInWindow().x as f32, + // MacOS screen coordinates are relative to bottom left window_height - native_event.locationInWindow().y as f32, ), modifiers: read_modifiers(native_event), @@ -150,6 +151,7 @@ impl Event { button, position: vec2f( native_event.locationInWindow().x as f32, + // MacOS view coordinates are relative to bottom left window_height - native_event.locationInWindow().y as f32, ), modifiers: read_modifiers(native_event), diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index f1b7f75c03..ae9db6a396 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -697,27 +697,20 @@ impl platform::Platform for MacPlatform { } fn set_cursor_style(&self, style: CursorStyle, window_id: usize, screen_position: &Vector2F) { - let top_most = Window::window_id_under(screen_position); - if top_most.is_none() { - return; - } - if top_most.unwrap() != window_id { - return; - } - - dbg!(top_most.unwrap(), window_id); - - dbg!(style); - - unsafe { - let cursor: id = match style { - CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor], - CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor], - CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor], - CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor], - CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor], - }; - let _: () = msg_send![cursor, set]; + if Some(window_id) == Window::window_id_under(screen_position) { + dbg!(screen_position, style); + unsafe { + let cursor: id = match style { + CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor], + CursorStyle::ResizeLeftRight => { + msg_send![class!(NSCursor), resizeLeftRightCursor] + } + CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor], + CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor], + CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor], + }; + let _: () = msg_send![cursor, set]; + } } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 032b88507b..067dd69115 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -52,6 +52,8 @@ use std::{ time::Duration, }; +use super::geometry::Vector2FExt; + const WINDOW_STATE_IVAR: &str = "windowState"; static mut WINDOW_CLASS: *const Class = ptr::null(); @@ -564,11 +566,13 @@ impl Window { unsafe { let app = NSApplication::sharedApplication(nil); - let point = NSPoint::new(screen_position.x() as f64, screen_position.y() as f64); - let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:point belowWindowWithWindowNumber:0 as NSInteger]; + let point = screen_position.to_ns_point(); + let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:point belowWindowWithWindowNumber:0]; + // For some reason this API doesn't work when our two windows are on top of each other let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number]; - dbg!(top_most_window); + + // dbg!(top_most_window); let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS]; let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS]; if is_panel | is_window { @@ -779,7 +783,7 @@ impl platform::Window for Window { fn screen_position(&self, view_position: &Vector2F) -> Vector2F { let self_borrow = self.0.borrow_mut(); unsafe { - let point = NSPoint::new(view_position.x() as f64, view_position.y() as f64); + let point = view_position.to_ns_point(); let screen_point: NSPoint = msg_send![self_borrow.native_window, convertPointToScreen: point]; vec2f(screen_point.x as f32, screen_point.y as f32) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index f74eef463a..1b16574cb8 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -317,6 +317,9 @@ impl Presenter { } } + dbg!("*******"); + dbg!(position); + dbg!(event_reused); if let Some(screen_position) = cx.screen_position(self.window_id, position) { cx.platform().set_cursor_style( style_to_assign, From 45e4e3354e4729578d935bbd65da98b48f9f8e0f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 25 Jan 2023 08:58:41 -0800 Subject: [PATCH 021/180] Changed the presenter to only send 'set_cursor_style' on the topmost window co-authored-by: Antonio --- crates/gpui/src/app.rs | 6 ++- crates/gpui/src/platform.rs | 4 +- crates/gpui/src/platform/mac/platform.rs | 28 +++++----- crates/gpui/src/platform/mac/status_item.rs | 4 +- crates/gpui/src/platform/mac/window.rs | 59 ++++++++++----------- crates/gpui/src/platform/test.rs | 6 +-- crates/gpui/src/presenter.rs | 12 ++--- 7 files changed, 54 insertions(+), 65 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 0e5697c30b..2e7378fc16 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -866,10 +866,12 @@ impl MutableAppContext { } } - pub fn screen_position(&self, window_id: usize, view_position: &Vector2F) -> Option { + pub fn is_topmost_window_for_position(&self, window_id: usize, position: Vector2F) -> bool { self.presenters_and_platform_windows .get(&window_id) - .map(|(_, window)| window.screen_position(view_position)) + .map_or(false, |(_, window)| { + window.is_topmost_for_position(position) + }) } pub fn window_ids(&self) -> impl Iterator + '_ { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 0601fd9764..05ba61a9ad 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -64,7 +64,7 @@ pub trait Platform: Send + Sync { fn read_credentials(&self, url: &str) -> Result)>>; fn delete_credentials(&self, url: &str) -> Result<()>; - fn set_cursor_style(&self, style: CursorStyle, window_id: usize, screen_position: &Vector2F); + fn set_cursor_style(&self, style: CursorStyle); fn should_auto_hide_scrollbars(&self) -> bool; fn local_timezone(&self) -> UtcOffset; @@ -145,7 +145,7 @@ pub trait Window { fn present_scene(&mut self, scene: Scene); fn appearance(&self) -> Appearance; fn on_appearance_changed(&mut self, callback: Box); - fn screen_position(&self, view_position: &Vector2F) -> Vector2F; + fn is_topmost_for_position(&self, position: Vector2F) -> bool; } #[derive(Debug)] diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index ae9db6a396..dbb1a01f31 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -37,7 +37,6 @@ use objc::{ runtime::{Class, Object, Sel}, sel, sel_impl, }; -use pathfinder_geometry::vector::Vector2F; use postage::oneshot; use ptr::null_mut; use std::{ @@ -696,21 +695,18 @@ impl platform::Platform for MacPlatform { Ok(()) } - fn set_cursor_style(&self, style: CursorStyle, window_id: usize, screen_position: &Vector2F) { - if Some(window_id) == Window::window_id_under(screen_position) { - dbg!(screen_position, style); - unsafe { - let cursor: id = match style { - CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor], - CursorStyle::ResizeLeftRight => { - msg_send![class!(NSCursor), resizeLeftRightCursor] - } - CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor], - CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor], - CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor], - }; - let _: () = msg_send![cursor, set]; - } + fn set_cursor_style(&self, style: CursorStyle) { + unsafe { + let cursor: id = match style { + CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor], + CursorStyle::ResizeLeftRight => { + msg_send![class!(NSCursor), resizeLeftRightCursor] + } + CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor], + CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor], + CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor], + }; + let _: () = msg_send![cursor, set]; } } diff --git a/crates/gpui/src/platform/mac/status_item.rs b/crates/gpui/src/platform/mac/status_item.rs index d05c559772..2da7caab7e 100644 --- a/crates/gpui/src/platform/mac/status_item.rs +++ b/crates/gpui/src/platform/mac/status_item.rs @@ -259,8 +259,8 @@ impl platform::Window for StatusItem { } } - fn screen_position(&self, _view_position: &Vector2F) -> Vector2F { - unimplemented!() + fn is_topmost_for_position(&self, _: Vector2F) -> bool { + true } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 067dd69115..191ab77058 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -17,9 +17,9 @@ use crate::{ use block::ConcreteBlock; use cocoa::{ appkit::{ - CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, - NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior, - NSWindowStyleMask, + CGFloat, CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, + NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, + NSWindowCollectionBehavior, NSWindowStyleMask, }, base::{id, nil}, foundation::{ @@ -52,8 +52,6 @@ use std::{ time::Duration, }; -use super::geometry::Vector2FExt; - const WINDOW_STATE_IVAR: &str = "windowState"; static mut WINDOW_CLASS: *const Class = ptr::null(); @@ -561,28 +559,6 @@ impl Window { } } } - - pub fn window_id_under(screen_position: &Vector2F) -> Option { - unsafe { - let app = NSApplication::sharedApplication(nil); - - let point = screen_position.to_ns_point(); - let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:point belowWindowWithWindowNumber:0]; - - // For some reason this API doesn't work when our two windows are on top of each other - let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number]; - - // dbg!(top_most_window); - let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS]; - let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS]; - if is_panel | is_window { - let id = get_window_state(&*top_most_window).borrow().id; - Some(id) - } else { - None - } - } - } } impl Drop for Window { @@ -780,13 +756,34 @@ impl platform::Window for Window { self.0.borrow_mut().appearance_changed_callback = Some(callback); } - fn screen_position(&self, view_position: &Vector2F) -> Vector2F { - let self_borrow = self.0.borrow_mut(); + fn is_topmost_for_position(&self, position: Vector2F) -> bool { + let window_bounds = self.bounds(); + let self_borrow = self.0.borrow(); + let self_id = self_borrow.id; + unsafe { - let point = view_position.to_ns_point(); + let app = NSApplication::sharedApplication(nil); + + // Convert back to bottom-left coordinates + let point = NSPoint::new( + position.x() as CGFloat, + (window_bounds.height() - position.y()) as CGFloat, + ); + let screen_point: NSPoint = msg_send![self_borrow.native_window, convertPointToScreen: point]; - vec2f(screen_point.x as f32, screen_point.y as f32) + let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0]; + let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number]; + + let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS]; + let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS]; + if is_panel | is_window { + let topmost_window_id = get_window_state(&*top_most_window).borrow().id; + topmost_window_id == self_id + } else { + // Someone else's window is on top + false + } } } } diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 7295bf525b..d33e3e2fca 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -178,7 +178,7 @@ impl super::Platform for Platform { Ok(()) } - fn set_cursor_style(&self, style: CursorStyle, _window_id: usize, _position: &Vector2F) { + fn set_cursor_style(&self, style: CursorStyle) { *self.cursor.lock() = style; } @@ -333,8 +333,8 @@ impl super::Window for Window { fn on_appearance_changed(&mut self, _: Box) {} - fn screen_position(&self, view_position: &Vector2F) -> Vector2F { - view_position.clone() + fn is_topmost_for_position(&self, _position: Vector2F) -> bool { + true } } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 1b16574cb8..5bff7e7bb1 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -31,6 +31,7 @@ use std::{ ops::{Deref, DerefMut, Range}, sync::Arc, }; +use time::Instant; pub struct Presenter { window_id: usize, @@ -317,15 +318,8 @@ impl Presenter { } } - dbg!("*******"); - dbg!(position); - dbg!(event_reused); - if let Some(screen_position) = cx.screen_position(self.window_id, position) { - cx.platform().set_cursor_style( - style_to_assign, - self.window_id, - &screen_position, - ); + if cx.is_topmost_window_for_position(self.window_id, *position) { + cx.platform().set_cursor_style(style_to_assign); } if !event_reused { From 1fc6276eaba51b1544f130cfa4c0f5537e129fea Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 25 Jan 2023 09:09:59 -0800 Subject: [PATCH 022/180] Remove debug wiring --- crates/gpui/src/presenter.rs | 9 +++-- crates/workspace/src/workspace.rs | 57 +------------------------------ 2 files changed, 8 insertions(+), 58 deletions(-) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 5bff7e7bb1..1fff5c50b5 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -31,7 +31,6 @@ use std::{ ops::{Deref, DerefMut, Range}, sync::Arc, }; -use time::Instant; pub struct Presenter { window_id: usize, @@ -318,8 +317,14 @@ impl Presenter { } } - if cx.is_topmost_window_for_position(self.window_id, *position) { + let t0 = std::time::Instant::now(); + let is_topmost_window = + cx.is_topmost_window_for_position(self.window_id, *position); + println!("is_topmost_window => {:?}", t0.elapsed()); + if is_topmost_window { + let t1 = std::time::Instant::now(); cx.platform().set_cursor_style(style_to_assign); + println!("set_cursor_style => {:?}", t1.elapsed()); } if !event_reused { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 71c5ef97a2..b0abe09070 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -31,9 +31,8 @@ use futures::{ }; use gpui::{ actions, - color::Color, elements::*, - geometry::vector::{vec2f, Vector2F}, + geometry::vector::Vector2F, impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, @@ -101,7 +100,6 @@ actions!( NewTerminal, NewSearch, Feedback, - ShowNotif, ] ); @@ -264,59 +262,6 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { client.add_view_request_handler(Workspace::handle_follow); client.add_view_message_handler(Workspace::handle_unfollow); client.add_view_message_handler(Workspace::handle_update_followers); - - // REMEMBER TO DELETE THE SHOW NOTIF - cx.add_action( - |_workspace: &mut Workspace, _: &ShowNotif, cx: &mut ViewContext| { - struct DummyView; - - impl Entity for DummyView { - type Event = (); - } - - impl View for DummyView { - fn ui_name() -> &'static str { - "DummyView" - } - fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { - MouseEventHandler::::new(0, cx, |state, _cx| { - Empty::new() - .contained() - .with_background_color(if state.hovered() { - Color::red() - } else { - Color::blue() - }) - .constrained() - .with_width(200.) - .with_height(200.) - .boxed() - }) - .on_click(MouseButton::Left, |_, _| { - println!("click"); - }) - .with_cursor_style(CursorStyle::ResizeUpDown) - .boxed() - } - } - - cx.add_window( - WindowOptions { - bounds: gpui::WindowBounds::Fixed(gpui::geometry::rect::RectF::new( - vec2f(400., 200.), - vec2f(300., 200.), - )), - titlebar: None, - center: false, - focus: false, - kind: gpui::WindowKind::PopUp, - is_movable: true, - screen: None, - }, - |_cx| DummyView, - ) - }, - ) } type ProjectItemBuilders = HashMap< From ecb7d1072ffd21a2de5bbd7d2d65170375c87277 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 25 Jan 2023 09:33:07 -0800 Subject: [PATCH 023/180] Fixes a broken conditional that is only caught on darwin systems --- crates/gpui/src/platform/mac/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 191ab77058..bef6f65e42 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -777,7 +777,7 @@ impl platform::Window for Window { let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS]; let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS]; - if is_panel | is_window { + if is_panel == YES || is_window == YES { let topmost_window_id = get_window_state(&*top_most_window).borrow().id; topmost_window_id == self_id } else { From 160870c9de053d5ccca7ba12bdd467ef8f2eafca Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 25 Jan 2023 19:46:51 +0200 Subject: [PATCH 024/180] Improve user notification The message is not really true. When one declines, the other person can notice that the contact request is not pending any more. They will know. Switching to not alerted is closer to what is really happening. --- crates/collab_ui/src/contact_notification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab_ui/src/contact_notification.rs b/crates/collab_ui/src/contact_notification.rs index 6f0cfc68c7..f05cca00bf 100644 --- a/crates/collab_ui/src/contact_notification.rs +++ b/crates/collab_ui/src/contact_notification.rs @@ -48,7 +48,7 @@ impl View for ContactNotification { ContactEventKind::Requested => render_user_notification( self.user.clone(), "wants to add you as a contact", - Some("They won't know if you decline."), + Some("They won't be alerted if you decline."), Dismiss(self.user.id), vec![ ( From 3d8dbee76a3eee2fcf51d164769000afe200285a Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 25 Jan 2023 13:37:04 -0500 Subject: [PATCH 025/180] Clean up some debug printing --- crates/gpui/src/presenter.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 1fff5c50b5..499e0df93e 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -317,14 +317,8 @@ impl Presenter { } } - let t0 = std::time::Instant::now(); - let is_topmost_window = - cx.is_topmost_window_for_position(self.window_id, *position); - println!("is_topmost_window => {:?}", t0.elapsed()); - if is_topmost_window { - let t1 = std::time::Instant::now(); + if cx.is_topmost_window_for_position(self.window_id, *position) { cx.platform().set_cursor_style(style_to_assign); - println!("set_cursor_style => {:?}", t1.elapsed()); } if !event_reused { From 7003a475a7545c7eee7969be08f6a02925b26dc6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 25 Jan 2023 10:44:15 -0800 Subject: [PATCH 026/180] Assign the language registry to all buffers in the project --- crates/project/src/project.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f324865b5c..34117cf39e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1480,6 +1480,10 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Result<()> { + buffer.update(cx, |buffer, _| { + buffer.set_language_registry(self.languages.clone()) + }); + let remote_id = buffer.read(cx).remote_id(); let open_buffer = if self.is_remote() || self.is_shared() { OpenBuffer::Strong(buffer.clone()) @@ -1803,7 +1807,6 @@ impl Project { if buffer.language().map_or(true, |old_language| { !Arc::ptr_eq(old_language, &new_language) }) { - buffer.set_language_registry(self.languages.clone()); buffer.set_language(Some(new_language.clone()), cx); } }); From f68f9f37ab61cdccdc93be044e0c375a63cec50a Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 25 Jan 2023 14:20:23 -0500 Subject: [PATCH 027/180] Add cursor position to feedback editor Co-Authored-By: Mikayla Maki Co-Authored-By: Max Brunsfeld --- crates/editor/src/items.rs | 2 +- crates/feedback/src/feedback_editor.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 7e7f44e514..501306aa19 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1094,7 +1094,7 @@ impl StatusItemView for CursorPosition { active_pane_item: Option<&dyn ItemHandle>, cx: &mut ViewContext, ) { - if let Some(editor) = active_pane_item.and_then(|item| item.downcast::()) { + if let Some(editor) = active_pane_item.and_then(|item| item.act_as::(cx)) { self._observe_active_editor = Some(cx.observe(&editor, Self::update_position)); self.update_position(editor, cx); } else { diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 3a6bc744f2..a3318d3efe 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -1,4 +1,5 @@ use std::{ + any::TypeId, ops::{Range, RangeInclusive}, sync::Arc, }; @@ -358,6 +359,21 @@ impl Item for FeedbackEditor { fn as_searchable(&self, handle: &ViewHandle) -> Option> { Some(Box::new(handle.clone())) } + + fn act_as_type( + &self, + type_id: TypeId, + self_handle: &ViewHandle, + _: &AppContext, + ) -> Option { + if type_id == TypeId::of::() { + Some(self_handle.into()) + } else if type_id == TypeId::of::() { + Some((&self.editor).into()) + } else { + None + } + } } impl SearchableItem for FeedbackEditor { From 7f3d9379380e621e825d35bd9d419d6261386f8c Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 25 Jan 2023 14:20:40 -0500 Subject: [PATCH 028/180] Count chars --- crates/feedback/src/feedback_editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index a3318d3efe..5398a8900e 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -134,7 +134,7 @@ impl FeedbackEditor { _: ModelHandle, cx: &mut ViewContext, ) -> Task> { - let feedback_char_count = self.editor.read(cx).buffer().read(cx).len(cx); + let feedback_char_count = self.editor.read(cx).text(cx).chars().count(); let error = if feedback_char_count < *FEEDBACK_CHAR_LIMIT.start() { Some(format!( From 328b779185f796216c95134a57582efcf77ea06e Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 25 Jan 2023 14:20:58 -0500 Subject: [PATCH 029/180] Clean up construction of FeedbackEditor --- crates/feedback/src/feedback_editor.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 5398a8900e..e034da7db2 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -113,8 +113,7 @@ impl FeedbackEditor { cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone())) .detach(); - let this = Self { editor, project }; - this + Self { editor, project } } fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { From 15799f7af63cad57d7f23f5b0d37e608406e1857 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 20 Jan 2023 12:15:21 -0800 Subject: [PATCH 030/180] wip --- crates/collab/src/tests.rs | 2 +- crates/collab_ui/src/collab_ui.rs | 2 +- crates/editor/src/persistence.rs | 2 +- crates/gpui/src/app.rs | 109 ++++++++- crates/gpui/src/platform.rs | 4 +- crates/gpui/src/platform/mac/status_item.rs | 2 + crates/gpui/src/platform/mac/window.rs | 23 ++ crates/gpui/src/platform/test.rs | 8 +- crates/gpui/src/presenter.rs | 3 +- crates/settings/src/settings.rs | 3 +- crates/sqlez/src/bindable.rs | 240 ++++++++++++-------- crates/sqlez/src/statement.rs | 11 +- crates/workspace/src/dock.rs | 2 + crates/workspace/src/persistence.rs | 121 ++++++++-- crates/workspace/src/persistence/model.rs | 33 ++- crates/workspace/src/workspace.rs | 49 ++-- crates/zed/src/zed.rs | 15 +- 17 files changed, 481 insertions(+), 148 deletions(-) diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index f257d95493..fb018a9017 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -196,7 +196,7 @@ impl TestServer { languages: Arc::new(LanguageRegistry::new(Task::ready(()))), themes: ThemeRegistry::new((), cx.font_cache()), fs: fs.clone(), - build_window_options: Default::default, + build_window_options: |_| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), dock_default_item_factory: |_, _| unimplemented!(), }); diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index b19bc92455..2d6b9489d8 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -54,7 +54,7 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { }) .await?; - let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| { + let (_, workspace) = cx.add_window((app_state.build_window_options)(None), |cx| { let mut workspace = Workspace::new( Default::default(), 0, diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index 2d8d1a74fd..6e37735c13 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -6,7 +6,7 @@ use db::{define_connection, query}; use workspace::{ItemId, WorkspaceDb, WorkspaceId}; define_connection!( - // Current table shape using pseudo-rust syntax: + // Current schema shape using pseudo-rust syntax: // editors( // item_id: usize, // workspace_id: usize, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 2e7378fc16..79695004d2 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -43,6 +43,7 @@ use crate::{ util::post_inc, Appearance, AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, KeyUpEvent, ModifiersChangedEvent, MouseButton, MouseRegionId, PathPromptOptions, TextLayoutCache, + WindowBounds, }; pub trait Entity: 'static { @@ -594,6 +595,7 @@ type ReleaseObservationCallback = Box; type WindowActivationCallback = Box bool>; type WindowFullscreenCallback = Box bool>; +type WindowBoundsCallback = Box bool>; type KeystrokeCallback = Box< dyn FnMut(&Keystroke, &MatchResult, Option<&Box>, &mut MutableAppContext) -> bool, >; @@ -624,6 +626,7 @@ pub struct MutableAppContext { action_dispatch_observations: CallbackCollection<(), ActionObservationCallback>, window_activation_observations: CallbackCollection, window_fullscreen_observations: CallbackCollection, + window_bounds_observations: CallbackCollection, keystroke_observations: CallbackCollection, #[allow(clippy::type_complexity)] @@ -681,6 +684,7 @@ impl MutableAppContext { global_observations: Default::default(), window_activation_observations: Default::default(), window_fullscreen_observations: Default::default(), + window_bounds_observations: Default::default(), keystroke_observations: Default::default(), action_dispatch_observations: Default::default(), presenters_and_platform_windows: Default::default(), @@ -1240,6 +1244,23 @@ impl MutableAppContext { ) } + fn observe_window_bounds(&mut self, window_id: usize, callback: F) -> Subscription + where + F: 'static + FnMut(WindowBounds, &mut MutableAppContext) -> bool, + { + let subscription_id = post_inc(&mut self.next_subscription_id); + self.pending_effects + .push_back(Effect::WindowBoundsObservation { + window_id, + subscription_id, + callback: Box::new(callback), + }); + Subscription::WindowBoundsObservation( + self.window_bounds_observations + .subscribe(window_id, subscription_id), + ) + } + pub fn observe_keystrokes(&mut self, window_id: usize, callback: F) -> Subscription where F: 'static @@ -1774,6 +1795,13 @@ impl MutableAppContext { })); } + { + let mut app = self.upgrade(); + window.on_moved(Box::new(move || { + app.update(|cx| cx.window_was_moved(window_id)) + })); + } + { let mut app = self.upgrade(); window.on_fullscreen(Box::new(move |is_fullscreen| { @@ -2071,6 +2099,11 @@ impl MutableAppContext { .invalidation .get_or_insert(WindowInvalidation::default()); } + self.handle_window_moved(window_id); + } + + Effect::MoveWindow { window_id } => { + self.handle_window_moved(window_id); } Effect::WindowActivationObservation { @@ -2103,6 +2136,16 @@ impl MutableAppContext { is_fullscreen, } => self.handle_fullscreen_effect(window_id, is_fullscreen), + Effect::WindowBoundsObservation { + window_id, + subscription_id, + callback, + } => self.window_bounds_observations.add_callback( + window_id, + subscription_id, + callback, + ), + Effect::RefreshWindows => { refreshing = true; } @@ -2197,6 +2240,11 @@ impl MutableAppContext { .push_back(Effect::ResizeWindow { window_id }); } + fn window_was_moved(&mut self, window_id: usize) { + self.pending_effects + .push_back(Effect::MoveWindow { window_id }); + } + fn window_was_fullscreen_changed(&mut self, window_id: usize, is_fullscreen: bool) { self.pending_effects.push_back(Effect::FullscreenWindow { window_id, @@ -2510,6 +2558,20 @@ impl MutableAppContext { } } + fn handle_window_moved(&mut self, window_id: usize) { + let bounds = if self.window_is_fullscreen(window_id) { + WindowBounds::Fullscreen + } else { + WindowBounds::Fixed(self.window_bounds(window_id)) + }; + self.window_bounds_observations + .clone() + .emit(window_id, self, move |callback, this| { + callback(bounds, this); + true + }); + } + pub fn focus(&mut self, window_id: usize, view_id: Option) { self.pending_effects .push_back(Effect::Focus { window_id, view_id }); @@ -2887,9 +2949,8 @@ pub enum Effect { ResizeWindow { window_id: usize, }, - FullscreenWindow { + MoveWindow { window_id: usize, - is_fullscreen: bool, }, ActivateWindow { window_id: usize, @@ -2900,11 +2961,20 @@ pub enum Effect { subscription_id: usize, callback: WindowActivationCallback, }, + FullscreenWindow { + window_id: usize, + is_fullscreen: bool, + }, WindowFullscreenObservation { window_id: usize, subscription_id: usize, callback: WindowFullscreenCallback, }, + WindowBoundsObservation { + window_id: usize, + subscription_id: usize, + callback: WindowBoundsCallback, + }, Keystroke { window_id: usize, keystroke: Keystroke, @@ -3015,6 +3085,10 @@ impl Debug for Effect { .debug_struct("Effect::RefreshWindow") .field("window_id", window_id) .finish(), + Effect::MoveWindow { window_id } => f + .debug_struct("Effect::MoveWindow") + .field("window_id", window_id) + .finish(), Effect::WindowActivationObservation { window_id, subscription_id, @@ -3049,6 +3123,16 @@ impl Debug for Effect { .field("window_id", window_id) .field("subscription_id", subscription_id) .finish(), + + Effect::WindowBoundsObservation { + window_id, + subscription_id, + callback: _, + } => f + .debug_struct("Effect::WindowBoundsObservation") + .field("window_id", window_id) + .field("subscription_id", subscription_id) + .finish(), Effect::RefreshWindows => f.debug_struct("Effect::FullViewRefresh").finish(), Effect::WindowShouldCloseSubscription { window_id, .. } => f .debug_struct("Effect::WindowShouldCloseSubscription") @@ -3928,6 +4012,24 @@ impl<'a, T: View> ViewContext<'a, T> { ) } + pub fn observe_window_bounds(&mut self, mut callback: F) -> Subscription + where + F: 'static + FnMut(&mut T, WindowBounds, &mut ViewContext), + { + let observer = self.weak_handle(); + self.app + .observe_window_bounds(self.window_id(), move |bounds, cx| { + if let Some(observer) = observer.upgrade(cx) { + observer.update(cx, |observer, cx| { + callback(observer, bounds, cx); + }); + true + } else { + false + } + }) + } + pub fn emit(&mut self, payload: T::Event) { self.app.pending_effects.push_back(Effect::Event { entity_id: self.view_id, @@ -5092,6 +5194,7 @@ pub enum Subscription { FocusObservation(callback_collection::Subscription), WindowActivationObservation(callback_collection::Subscription), WindowFullscreenObservation(callback_collection::Subscription), + WindowBoundsObservation(callback_collection::Subscription), KeystrokeObservation(callback_collection::Subscription), ReleaseObservation(callback_collection::Subscription), ActionObservation(callback_collection::Subscription<(), ActionObservationCallback>), @@ -5107,6 +5210,7 @@ impl Subscription { Subscription::FocusObservation(subscription) => subscription.id(), Subscription::WindowActivationObservation(subscription) => subscription.id(), Subscription::WindowFullscreenObservation(subscription) => subscription.id(), + Subscription::WindowBoundsObservation(subscription) => subscription.id(), Subscription::KeystrokeObservation(subscription) => subscription.id(), Subscription::ReleaseObservation(subscription) => subscription.id(), Subscription::ActionObservation(subscription) => subscription.id(), @@ -5123,6 +5227,7 @@ impl Subscription { Subscription::KeystrokeObservation(subscription) => subscription.detach(), Subscription::WindowActivationObservation(subscription) => subscription.detach(), Subscription::WindowFullscreenObservation(subscription) => subscription.detach(), + Subscription::WindowBoundsObservation(subscription) => subscription.detach(), Subscription::ReleaseObservation(subscription) => subscription.detach(), Subscription::ActionObservation(subscription) => subscription.detach(), } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 05ba61a9ad..1db1fe62b0 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -126,6 +126,7 @@ pub trait Window { fn on_active_status_change(&mut self, callback: Box); fn on_resize(&mut self, callback: Box); fn on_fullscreen(&mut self, callback: Box); + fn on_moved(&mut self, callback: Box); fn on_should_close(&mut self, callback: Box bool>); fn on_close(&mut self, callback: Box); fn set_input_handler(&mut self, input_handler: Box); @@ -186,8 +187,9 @@ pub enum WindowKind { PopUp, } -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] pub enum WindowBounds { + Fullscreen, Maximized, Fixed(RectF), } diff --git a/crates/gpui/src/platform/mac/status_item.rs b/crates/gpui/src/platform/mac/status_item.rs index 2da7caab7e..8ac9dbea71 100644 --- a/crates/gpui/src/platform/mac/status_item.rs +++ b/crates/gpui/src/platform/mac/status_item.rs @@ -183,6 +183,8 @@ impl platform::Window for StatusItem { fn on_resize(&mut self, _: Box) {} + fn on_moved(&mut self, _: Box) {} + fn on_fullscreen(&mut self, _: Box) {} fn on_should_close(&mut self, _: Box bool>) {} diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index bef6f65e42..c4cd7d0e27 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -295,6 +295,10 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C sel!(windowWillExitFullScreen:), window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(windowDidMove:), + window_did_move as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(windowDidBecomeKey:), window_did_change_key_status as extern "C" fn(&Object, Sel, id), @@ -333,6 +337,7 @@ struct WindowState { activate_callback: Option>, resize_callback: Option>, fullscreen_callback: Option>, + moved_callback: Option>, should_close_callback: Option bool>>, close_callback: Option>, appearance_changed_callback: Option>, @@ -405,6 +410,9 @@ impl Window { let screen = native_window.screen(); match options.bounds { + WindowBounds::Fullscreen => { + native_window.toggleFullScreen_(nil); + } WindowBounds::Maximized => { native_window.setFrame_display_(screen.visibleFrame(), YES); } @@ -441,6 +449,7 @@ impl Window { close_callback: None, activate_callback: None, fullscreen_callback: None, + moved_callback: None, appearance_changed_callback: None, input_handler: None, pending_key_down: None, @@ -588,6 +597,10 @@ impl platform::Window for Window { self.0.as_ref().borrow_mut().resize_callback = Some(callback); } + fn on_moved(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().moved_callback = Some(callback); + } + fn on_fullscreen(&mut self, callback: Box) { self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback); } @@ -1137,6 +1150,16 @@ fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) { } } +extern "C" fn window_did_move(this: &Object, _: Sel, _: id) { + let window_state = unsafe { get_window_state(this) }; + let mut window_state_borrow = window_state.as_ref().borrow_mut(); + if let Some(mut callback) = window_state_borrow.moved_callback.take() { + drop(window_state_borrow); + callback(); + window_state.borrow_mut().moved_callback = Some(callback); + } +} + extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; let window_state_borrow = window_state.borrow(); diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index d33e3e2fca..f3e1ce4055 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -40,6 +40,7 @@ pub struct Window { current_scene: Option, event_handlers: Vec bool>>, pub(crate) resize_handlers: Vec>, + pub(crate) moved_handlers: Vec>, close_handlers: Vec>, fullscreen_handlers: Vec>, pub(crate) active_status_change_handlers: Vec>, @@ -143,7 +144,7 @@ impl super::Platform for Platform { _executor: Rc, ) -> Box { Box::new(Window::new(match options.bounds { - WindowBounds::Maximized => vec2f(1024., 768.), + WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.), WindowBounds::Fixed(rect) => rect.size(), })) } @@ -225,6 +226,7 @@ impl Window { size, event_handlers: Default::default(), resize_handlers: Default::default(), + moved_handlers: Default::default(), close_handlers: Default::default(), should_close_handler: Default::default(), active_status_change_handlers: Default::default(), @@ -273,6 +275,10 @@ impl super::Window for Window { self.resize_handlers.push(callback); } + fn on_moved(&mut self, callback: Box) { + self.moved_handlers.push(callback); + } + fn on_close(&mut self, callback: Box) { self.close_handlers.push(callback); } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 499e0df93e..9d2cd1336d 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -23,7 +23,7 @@ use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; use smallvec::SmallVec; use sqlez::{ - bindable::{Bind, Column}, + bindable::{Bind, Column, StaticRowComponent}, statement::Statement, }; use std::{ @@ -932,6 +932,7 @@ impl ToJson for Axis { } } +impl StaticRowComponent for Axis {} impl Bind for Axis { fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { match self { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 8b2c12a59b..92236bad61 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -15,7 +15,7 @@ use schemars::{ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use sqlez::{ - bindable::{Bind, Column}, + bindable::{Bind, Column, StaticRowComponent}, statement::Statement, }; use std::{collections::HashMap, fmt::Write as _, num::NonZeroU32, str, sync::Arc}; @@ -253,6 +253,7 @@ pub enum DockAnchor { Expanded, } +impl StaticRowComponent for DockAnchor {} impl Bind for DockAnchor { fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { match self { diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 62212d8f18..51d4f5d4a3 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -9,14 +9,37 @@ use anyhow::{Context, Result}; use crate::statement::{SqlType, Statement}; -pub trait Bind { +pub trait StaticRowComponent { + fn static_column_count() -> usize { + 1 + } +} + +pub trait RowComponent { + fn column_count(&self) -> usize; +} + +impl RowComponent for T { + fn column_count(&self) -> usize { + T::static_column_count() + } +} + +impl StaticRowComponent for &T { + fn static_column_count() -> usize { + T::static_column_count() + } +} + +pub trait Bind: RowComponent { fn bind(&self, statement: &Statement, start_index: i32) -> Result; } -pub trait Column: Sized { +pub trait Column: Sized + RowComponent { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)>; } +impl StaticRowComponent for bool {} impl Bind for bool { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -33,6 +56,7 @@ impl Column for bool { } } +impl StaticRowComponent for &[u8] {} impl Bind for &[u8] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -42,6 +66,7 @@ impl Bind for &[u8] { } } +impl StaticRowComponent for &[u8; C] {} impl Bind for &[u8; C] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -51,6 +76,7 @@ impl Bind for &[u8; C] { } } +impl StaticRowComponent for Vec {} impl Bind for Vec { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -70,6 +96,7 @@ impl Column for Vec { } } +impl StaticRowComponent for f64 {} impl Bind for f64 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -89,6 +116,7 @@ impl Column for f64 { } } +impl StaticRowComponent for f32 {} impl Bind for f32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -109,6 +137,7 @@ impl Column for f32 { } } +impl StaticRowComponent for i32 {} impl Bind for i32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -126,6 +155,7 @@ impl Column for i32 { } } +impl StaticRowComponent for i64 {} impl Bind for i64 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -142,6 +172,7 @@ impl Column for i64 { } } +impl StaticRowComponent for u32 {} impl Bind for u32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) @@ -157,6 +188,7 @@ impl Column for u32 { } } +impl StaticRowComponent for usize {} impl Bind for usize { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) @@ -172,6 +204,7 @@ impl Column for usize { } } +impl StaticRowComponent for &str {} impl Bind for &str { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self)?; @@ -179,6 +212,7 @@ impl Bind for &str { } } +impl StaticRowComponent for Arc {} impl Bind for Arc { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self.as_ref())?; @@ -186,6 +220,7 @@ impl Bind for Arc { } } +impl StaticRowComponent for String {} impl Bind for String { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self)?; @@ -207,28 +242,41 @@ impl Column for String { } } -impl Bind for Option { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { +impl StaticRowComponent for Option { + fn static_column_count() -> usize { + T::static_column_count() + } +} +impl Bind for Option { + fn bind(&self, statement: &Statement, mut start_index: i32) -> Result { if let Some(this) = self { this.bind(statement, start_index) } else { - statement.bind_null(start_index)?; - Ok(start_index + 1) + for _ in 0..T::static_column_count() { + statement.bind_null(start_index)?; + start_index += 1; + } + Ok(start_index) } } } -impl Column for Option { +impl Column for Option { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { if let SqlType::Null = statement.column_type(start_index)? { - Ok((None, start_index + 1)) + Ok((None, start_index + T::static_column_count() as i32)) } else { T::column(statement, start_index).map(|(result, next_index)| (Some(result), next_index)) } } } -impl Bind for [T; COUNT] { +impl StaticRowComponent for [T; COUNT] { + fn static_column_count() -> usize { + T::static_column_count() * COUNT + } +} +impl Bind for [T; COUNT] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let mut current_index = start_index; for binding in self { @@ -239,7 +287,7 @@ impl Bind for [T; COUNT] { } } -impl Column for [T; COUNT] { +impl Column for [T; COUNT] { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let mut array = [Default::default(); COUNT]; let mut current_index = start_index; @@ -250,40 +298,21 @@ impl Column for [T; COUNT] { } } -impl Bind for Vec { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let mut current_index = start_index; - for binding in self.iter() { - current_index = binding.bind(statement, current_index)? - } - - Ok(current_index) - } -} - -impl Bind for &[T] { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let mut current_index = start_index; - for binding in *self { - current_index = binding.bind(statement, current_index)? - } - - Ok(current_index) - } -} - +impl StaticRowComponent for &Path {} impl Bind for &Path { fn bind(&self, statement: &Statement, start_index: i32) -> Result { self.as_os_str().as_bytes().bind(statement, start_index) } } +impl StaticRowComponent for Arc {} impl Bind for Arc { fn bind(&self, statement: &Statement, start_index: i32) -> Result { self.as_ref().bind(statement, start_index) } } +impl StaticRowComponent for PathBuf {} impl Bind for PathBuf { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (self.as_ref() as &Path).bind(statement, start_index) @@ -301,6 +330,11 @@ impl Column for PathBuf { } } +impl StaticRowComponent for () { + fn static_column_count() -> usize { + 0 + } +} /// Unit impls do nothing. This simplifies query macros impl Bind for () { fn bind(&self, _statement: &Statement, start_index: i32) -> Result { @@ -314,74 +348,80 @@ impl Column for () { } } -impl Bind for (T1, T2) { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = self.0.bind(statement, start_index)?; - self.1.bind(statement, next_index) +macro_rules! impl_tuple_row_traits { + ( $($local:ident: $type:ident),+ ) => { + impl<$($type: RowComponent),+> RowComponent for ($($type,)+) { + fn column_count(&self) -> usize { + let ($($local,)+) = self; + let mut count = 0; + $(count += $local.column_count();)+ + count + } + } + + impl<$($type: Bind),+> Bind for ($($type,)+) { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let mut next_index = start_index; + let ($($local,)+) = self; + $(next_index = $local.bind(statement, next_index)?;)+ + Ok(next_index) + } + } + + impl<$($type: Column),+> Column for ($($type,)+) { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let mut next_index = start_index; + Ok(( + ( + $({ + let value; + (value, next_index) = $type::column(statement, next_index)?; + value + },)+ + ), + next_index, + )) + } + } } } -impl Column for (T1, T2) { - fn column<'a>(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let (first, next_index) = T1::column(statement, start_index)?; - let (second, next_index) = T2::column(statement, next_index)?; - Ok(((first, second), next_index)) - } -} - -impl Bind for (T1, T2, T3) { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = self.0.bind(statement, start_index)?; - let next_index = self.1.bind(statement, next_index)?; - self.2.bind(statement, next_index) - } -} - -impl Column for (T1, T2, T3) { - fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let (first, next_index) = T1::column(statement, start_index)?; - let (second, next_index) = T2::column(statement, next_index)?; - let (third, next_index) = T3::column(statement, next_index)?; - Ok(((first, second, third), next_index)) - } -} - -impl Bind for (T1, T2, T3, T4) { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = self.0.bind(statement, start_index)?; - let next_index = self.1.bind(statement, next_index)?; - let next_index = self.2.bind(statement, next_index)?; - self.3.bind(statement, next_index) - } -} - -impl Column for (T1, T2, T3, T4) { - fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let (first, next_index) = T1::column(statement, start_index)?; - let (second, next_index) = T2::column(statement, next_index)?; - let (third, next_index) = T3::column(statement, next_index)?; - let (fourth, next_index) = T4::column(statement, next_index)?; - Ok(((first, second, third, fourth), next_index)) - } -} - -impl Bind for (T1, T2, T3, T4, T5) { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = self.0.bind(statement, start_index)?; - let next_index = self.1.bind(statement, next_index)?; - let next_index = self.2.bind(statement, next_index)?; - let next_index = self.3.bind(statement, next_index)?; - self.4.bind(statement, next_index) - } -} - -impl Column for (T1, T2, T3, T4, T5) { - fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let (first, next_index) = T1::column(statement, start_index)?; - let (second, next_index) = T2::column(statement, next_index)?; - let (third, next_index) = T3::column(statement, next_index)?; - let (fourth, next_index) = T4::column(statement, next_index)?; - let (fifth, next_index) = T5::column(statement, next_index)?; - Ok(((first, second, third, fourth, fifth), next_index)) - } -} +impl_tuple_row_traits!(t1: T1, t2: T2); +impl_tuple_row_traits!(t1: T1, t2: T2, t3: T3); +impl_tuple_row_traits!(t1: T1, t2: T2, t3: T3, t4: T4); +impl_tuple_row_traits!(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5); +impl_tuple_row_traits!(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6); +impl_tuple_row_traits!(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7); +impl_tuple_row_traits!( + t1: T1, + t2: T2, + t3: T3, + t4: T4, + t5: T5, + t6: T6, + t7: T7, + t8: T8 +); +impl_tuple_row_traits!( + t1: T1, + t2: T2, + t3: T3, + t4: T4, + t5: T5, + t6: T6, + t7: T7, + t8: T8, + t9: T9 +); +impl_tuple_row_traits!( + t1: T1, + t2: T2, + t3: T3, + t4: T4, + t5: T5, + t6: T6, + t7: T7, + t8: T8, + t9: T9, + t10: T10 +); diff --git a/crates/sqlez/src/statement.rs b/crates/sqlez/src/statement.rs index f3ec6d4854..da4f2388d9 100644 --- a/crates/sqlez/src/statement.rs +++ b/crates/sqlez/src/statement.rs @@ -238,11 +238,14 @@ impl<'a> Statement<'a> { pub fn bind(&self, value: T, index: i32) -> Result { debug_assert!(index > 0); - value.bind(self, index) + let after = value.bind(self, index)?; + debug_assert_eq!((after - index) as usize, value.column_count()); + Ok(after) } pub fn column(&mut self) -> Result { - let (result, _) = T::column(self, 0)?; + let (result, after) = T::column(self, 0)?; + debug_assert_eq!(T::column_count(&result), after as usize); Ok(result) } @@ -260,7 +263,9 @@ impl<'a> Statement<'a> { } pub fn with_bindings(&mut self, bindings: impl Bind) -> Result<&mut Self> { - self.bind(bindings, 1)?; + let column_count = bindings.column_count(); + let after = self.bind(bindings, 1)?; + debug_assert_eq!((after - 1) as usize, column_count); Ok(self) } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 747541f87d..7a31d02433 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -534,6 +534,8 @@ mod tests { }], }, left_sidebar_open: false, + fullscreen: false, + bounds: Default::default(), }; let fs = FakeFs::new(cx.background()); diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 03a866f2f6..1e8e72a3d2 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -6,7 +6,7 @@ use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; -use gpui::Axis; +use gpui::{Axis, geometry::{rect::RectF, vector::Vector2F}}; use util::{unzip_option, ResultExt}; @@ -19,6 +19,51 @@ use model::{ }; define_connection! { + // Current schema shape using pseudo-rust syntax: + // + // workspaces( + // workspace_id: usize, // Primary key for workspaces + // workspace_location: Bincode>, + // dock_visible: bool, + // dock_anchor: DockAnchor, // 'Bottom' / 'Right' / 'Expanded' + // dock_pane: Option, // PaneId + // left_sidebar_open: boolean, + // timestamp: String, // UTC YYYY-MM-DD HH:MM:SS + // fullscreen bool, // Boolean + // window_x: f32, + // window_y: f32, + // window_width: f32, + // window_height: f32, + // ) + // + // pane_groups( + // group_id: usize, // Primary key for pane_groups + // workspace_id: usize, // References workspaces table + // parent_group_id: Option, // None indicates that this is the root node + // position: Optiopn, // None indicates that this is the root node + // axis: Option, // 'Vertical', 'Horizontal' + // ) + // + // panes( + // pane_id: usize, // Primary key for panes + // workspace_id: usize, // References workspaces table + // active: bool, + // ) + // + // center_panes( + // pane_id: usize, // Primary key for center_panes + // parent_group_id: Option, // References pane_groups. If none, this is the root + // position: Option, // None indicates this is the root + // ) + // + // CREATE TABLE items( + // item_id: usize, // This is the item's view id, so this is not unique + // workspace_id: usize, // References workspaces table + // pane_id: usize, // References panes table + // kind: String, // Indicates which view this connects to. This is the key in the item_deserializers global + // position: usize, // Position of the item in the parent pane. This is equivalent to panes' position column + // active: bool, // Indicates if this item is the active one in the pane + // ) pub static ref DB: WorkspaceDb<()> = &[sql!( CREATE TABLE workspaces( @@ -39,8 +84,8 @@ define_connection! { position INTEGER, // NULL indicates that this is a root node axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal' FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE, + ON DELETE CASCADE + ON UPDATE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; @@ -49,8 +94,8 @@ define_connection! { workspace_id INTEGER NOT NULL, active INTEGER NOT NULL, // Boolean FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE + ON DELETE CASCADE + ON UPDATE CASCADE ) STRICT; CREATE TABLE center_panes( @@ -58,7 +103,7 @@ define_connection! { parent_group_id INTEGER, // NULL means that this is a root pane position INTEGER, // NULL means that this is a root pane FOREIGN KEY(pane_id) REFERENCES panes(pane_id) - ON DELETE CASCADE, + ON DELETE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; @@ -70,12 +115,19 @@ define_connection! { position INTEGER NOT NULL, active INTEGER NOT NULL, FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE, + ON DELETE CASCADE + ON UPDATE CASCADE, FOREIGN KEY(pane_id) REFERENCES panes(pane_id) - ON DELETE CASCADE, + ON DELETE CASCADE, PRIMARY KEY(item_id, workspace_id) ) STRICT; + ), + sql!( + ALTER TABLE workspaces ADD COLUMN fullscreen INTEGER; // Boolean + ALTER TABLE workspaces ADD COLUMN window_x REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_y REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_width REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_height REAL; // Null means set to whatever )]; } @@ -91,14 +143,26 @@ impl WorkspaceDb { // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, dock_position): ( + let (workspace_id, workspace_location, left_sidebar_open, dock_position, fullscreen, window_x, window_y, window_width, window_height): ( WorkspaceId, WorkspaceLocation, bool, DockPosition, - ) = - self.select_row_bound(sql!{ - SELECT workspace_id, workspace_location, left_sidebar_open, dock_visible, dock_anchor + bool, + f32, f32, f32, f32 + ) = self + .select_row_bound(sql! { + SELECT + workspace_id, + workspace_location, + left_sidebar_open, + dock_visible, + dock_anchor, + fullscreen, + window_x, + window_y, + window_width, + window_height FROM workspaces WHERE workspace_location = ? }) @@ -120,6 +184,11 @@ impl WorkspaceDb { .log_err()?, dock_position, left_sidebar_open, + fullscreen, + bounds: Some(RectF::new( + Vector2F::new(window_x, window_y), + Vector2F::new(window_width, window_height) + )) }) } @@ -410,6 +479,18 @@ impl WorkspaceDb { WHERE workspace_id = ? } } + + query! { + pub async fn set_bounds(workspace_id: WorkspaceId, fullscreen: bool, x: f32, y: f32, width: f32, height: f32) -> Result<()> { + UPDATE workspaces + SET fullscreen = ?2, + window_x = ?3, + window_y = ?4, + window_width = ?5, + window_height = ?6 + WHERE workspace_id = ?1 + } + } } #[cfg(test)] @@ -499,6 +580,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, + fullscreen: false, + bounds: Default::default(), }; let mut workspace_2 = SerializedWorkspace { @@ -508,6 +591,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, + fullscreen: false, + bounds: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -614,6 +699,8 @@ mod tests { center_group, dock_pane, left_sidebar_open: true, + fullscreen: false, + bounds: Default::default(), }; db.save_workspace(workspace.clone()).await; @@ -642,6 +729,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, + fullscreen: false, + bounds: Default::default(), }; let mut workspace_2 = SerializedWorkspace { @@ -651,6 +740,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, + fullscreen: false, + bounds: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -687,6 +778,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, + fullscreen: false, + bounds: Default::default(), }; db.save_workspace(workspace_3.clone()).await; @@ -722,6 +815,8 @@ mod tests { center_group: center_group.clone(), dock_pane, left_sidebar_open: true, + fullscreen: false, + bounds: Default::default(), } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index b264114fb6..4bc8881bba 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -6,10 +6,12 @@ use std::{ use anyhow::{Context, Result}; use async_recursion::async_recursion; -use gpui::{AsyncAppContext, Axis, ModelHandle, Task, ViewHandle}; +use gpui::{ + geometry::rect::RectF, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WindowBounds, +}; use db::sqlez::{ - bindable::{Bind, Column}, + bindable::{Bind, Column, StaticRowComponent}, statement::Statement, }; use project::Project; @@ -40,6 +42,7 @@ impl, T: IntoIterator> From for WorkspaceLocation { } } +impl StaticRowComponent for WorkspaceLocation {} impl Bind for &WorkspaceLocation { fn bind(&self, statement: &Statement, start_index: i32) -> Result { bincode::serialize(&self.0) @@ -58,7 +61,7 @@ impl Column for WorkspaceLocation { } } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Clone)] pub struct SerializedWorkspace { pub id: WorkspaceId, pub location: WorkspaceLocation, @@ -66,6 +69,20 @@ pub struct SerializedWorkspace { pub center_group: SerializedPaneGroup, pub dock_pane: SerializedPane, pub left_sidebar_open: bool, + pub fullscreen: bool, + pub bounds: Option, +} + +impl SerializedWorkspace { + pub fn bounds(&self) -> WindowBounds { + if self.fullscreen { + WindowBounds::Fullscreen + } else if let Some(bounds) = self.bounds { + WindowBounds::Fixed(bounds) + } else { + WindowBounds::Maximized + } + } } #[derive(Debug, PartialEq, Eq, Clone)] @@ -237,6 +254,11 @@ impl Default for SerializedItem { } } +impl StaticRowComponent for SerializedItem { + fn static_column_count() -> usize { + 3 + } +} impl Bind for &SerializedItem { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let next_index = statement.bind(self.kind.clone(), start_index)?; @@ -261,6 +283,11 @@ impl Column for SerializedItem { } } +impl StaticRowComponent for DockPosition { + fn static_column_count() -> usize { + 2 + } +} impl Bind for DockPosition { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let next_index = statement.bind(self.is_visible(), start_index)?; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b0abe09070..addbb9b017 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -38,7 +38,7 @@ use gpui::{ platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, + Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -339,7 +339,7 @@ pub struct AppState { pub client: Arc, pub user_store: ModelHandle, pub fs: Arc, - pub build_window_options: fn() -> WindowOptions<'static>, + pub build_window_options: fn(Option) -> WindowOptions<'static>, pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), pub dock_default_item_factory: DockDefaultItemFactory, } @@ -366,7 +366,7 @@ impl AppState { languages, user_store, initialize_workspace: |_, _, _| {}, - build_window_options: Default::default, + build_window_options: |_| Default::default(), dock_default_item_factory: |_, _| unimplemented!(), }) } @@ -682,17 +682,36 @@ impl Workspace { }; // Use the serialized workspace to construct the new window - let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| { - let mut workspace = Workspace::new( - serialized_workspace, - workspace_id, - project_handle, - app_state.dock_default_item_factory, - cx, - ); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - workspace - }); + let (_, workspace) = cx.add_window( + (app_state.build_window_options)( + serialized_workspace.as_ref().map(|sw| sw.bounds()), + ), + |cx| { + let mut workspace = Workspace::new( + serialized_workspace, + workspace_id, + project_handle, + app_state.dock_default_item_factory, + cx, + ); + (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + cx.observe_window_bounds(move |_, bounds, cx| { + let fullscreen = cx.window_is_fullscreen(cx.window_id()); + cx.background() + .spawn(DB.set_bounds( + workspace_id, + fullscreen, + bounds.min_x(), + bounds.min_y(), + bounds.width(), + bounds.height(), + )) + .detach_and_log_err(cx); + }) + .detach(); + workspace + }, + ); notify_if_database_failed(&workspace, &mut cx); @@ -2327,6 +2346,8 @@ impl Workspace { dock_pane, center_group, left_sidebar_open: self.left_sidebar.read(cx).is_open(), + fullscreen: false, + bounds: Default::default(), }; cx.background() diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c9f8b2d408..3529e1c6dc 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -369,12 +369,15 @@ pub fn initialize_workspace( }); } -pub fn build_window_options() -> WindowOptions<'static> { - let bounds = if let Some((position, size)) = ZED_WINDOW_POSITION.zip(*ZED_WINDOW_SIZE) { - WindowBounds::Fixed(RectF::new(position, size)) - } else { - WindowBounds::Maximized - }; +pub fn build_window_options(bounds: Option) -> WindowOptions<'static> { + let bounds = bounds + .or_else(|| { + ZED_WINDOW_POSITION + .zip(*ZED_WINDOW_SIZE) + .map(|(position, size)| WindowBounds::Fixed(RectF::new(position, size))) + }) + .unwrap_or(WindowBounds::Maximized); + WindowOptions { bounds, titlebar: Some(TitlebarOptions { From a581d0c5b82087c3e6bfe93c0f4f8204d4196f00 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sun, 22 Jan 2023 20:33:21 -0800 Subject: [PATCH 031/180] wip --- crates/gpui/src/app.rs | 13 +- crates/gpui/src/presenter.rs | 4 +- crates/settings/src/settings.rs | 4 +- crates/sqlez/src/bindable.rs | 92 ++--- crates/sqlez/src/statement.rs | 12 +- crates/workspace/src/persistence.rs | 450 +++++++++++----------- crates/workspace/src/persistence/model.rs | 12 +- crates/workspace/src/workspace.rs | 25 +- 8 files changed, 301 insertions(+), 311 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 79695004d2..df62580c4d 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2377,11 +2377,20 @@ impl MutableAppContext { let window = this.cx.windows.get_mut(&window_id)?; window.is_fullscreen = is_fullscreen; - let mut observations = this.window_fullscreen_observations.clone(); - observations.emit(window_id, this, |callback, this| { + let mut fullscreen_observations = this.window_fullscreen_observations.clone(); + fullscreen_observations.emit(window_id, this, |callback, this| { callback(is_fullscreen, this) }); + let bounds = if this.window_is_fullscreen(window_id) { + WindowBounds::Fullscreen + } else { + WindowBounds::Fixed(this.window_bounds(window_id)) + }; + + let mut bounds_observations = this.window_bounds_observations.clone(); + bounds_observations.emit(window_id, this, |callback, this| callback(bounds, this)); + Some(()) }); } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 9d2cd1336d..66c991f0a8 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -23,7 +23,7 @@ use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; use smallvec::SmallVec; use sqlez::{ - bindable::{Bind, Column, StaticRowComponent}, + bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; use std::{ @@ -932,7 +932,7 @@ impl ToJson for Axis { } } -impl StaticRowComponent for Axis {} +impl StaticColumnCount for Axis {} impl Bind for Axis { fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { match self { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 92236bad61..6e25222d96 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -15,7 +15,7 @@ use schemars::{ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use sqlez::{ - bindable::{Bind, Column, StaticRowComponent}, + bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; use std::{collections::HashMap, fmt::Write as _, num::NonZeroU32, str, sync::Arc}; @@ -253,7 +253,7 @@ pub enum DockAnchor { Expanded, } -impl StaticRowComponent for DockAnchor {} +impl StaticColumnCount for DockAnchor {} impl Bind for DockAnchor { fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { match self { diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 51d4f5d4a3..9d5d0c8b2f 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -9,37 +9,21 @@ use anyhow::{Context, Result}; use crate::statement::{SqlType, Statement}; -pub trait StaticRowComponent { - fn static_column_count() -> usize { +pub trait StaticColumnCount { + fn column_count() -> usize { 1 } } -pub trait RowComponent { - fn column_count(&self) -> usize; -} - -impl RowComponent for T { - fn column_count(&self) -> usize { - T::static_column_count() - } -} - -impl StaticRowComponent for &T { - fn static_column_count() -> usize { - T::static_column_count() - } -} - -pub trait Bind: RowComponent { +pub trait Bind { fn bind(&self, statement: &Statement, start_index: i32) -> Result; } -pub trait Column: Sized + RowComponent { +pub trait Column: Sized { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)>; } -impl StaticRowComponent for bool {} +impl StaticColumnCount for bool {} impl Bind for bool { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -56,7 +40,7 @@ impl Column for bool { } } -impl StaticRowComponent for &[u8] {} +impl StaticColumnCount for &[u8] {} impl Bind for &[u8] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -66,7 +50,7 @@ impl Bind for &[u8] { } } -impl StaticRowComponent for &[u8; C] {} +impl StaticColumnCount for &[u8; C] {} impl Bind for &[u8; C] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -76,7 +60,7 @@ impl Bind for &[u8; C] { } } -impl StaticRowComponent for Vec {} +impl StaticColumnCount for Vec {} impl Bind for Vec { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -96,7 +80,7 @@ impl Column for Vec { } } -impl StaticRowComponent for f64 {} +impl StaticColumnCount for f64 {} impl Bind for f64 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -116,7 +100,7 @@ impl Column for f64 { } } -impl StaticRowComponent for f32 {} +impl StaticColumnCount for f32 {} impl Bind for f32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -137,7 +121,7 @@ impl Column for f32 { } } -impl StaticRowComponent for i32 {} +impl StaticColumnCount for i32 {} impl Bind for i32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -155,7 +139,7 @@ impl Column for i32 { } } -impl StaticRowComponent for i64 {} +impl StaticColumnCount for i64 {} impl Bind for i64 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -172,7 +156,7 @@ impl Column for i64 { } } -impl StaticRowComponent for u32 {} +impl StaticColumnCount for u32 {} impl Bind for u32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) @@ -188,7 +172,7 @@ impl Column for u32 { } } -impl StaticRowComponent for usize {} +impl StaticColumnCount for usize {} impl Bind for usize { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) @@ -204,7 +188,7 @@ impl Column for usize { } } -impl StaticRowComponent for &str {} +impl StaticColumnCount for &str {} impl Bind for &str { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self)?; @@ -212,7 +196,7 @@ impl Bind for &str { } } -impl StaticRowComponent for Arc {} +impl StaticColumnCount for Arc {} impl Bind for Arc { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self.as_ref())?; @@ -220,7 +204,7 @@ impl Bind for Arc { } } -impl StaticRowComponent for String {} +impl StaticColumnCount for String {} impl Bind for String { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self)?; @@ -242,17 +226,18 @@ impl Column for String { } } -impl StaticRowComponent for Option { - fn static_column_count() -> usize { - T::static_column_count() +impl StaticColumnCount for Option { + fn column_count() -> usize { + T::column_count() } } -impl Bind for Option { +impl Bind for Option { fn bind(&self, statement: &Statement, mut start_index: i32) -> Result { if let Some(this) = self { this.bind(statement, start_index) } else { - for _ in 0..T::static_column_count() { + for i in 0..T::column_count() { + dbg!(i); statement.bind_null(start_index)?; start_index += 1; } @@ -261,22 +246,22 @@ impl Bind for Option { } } -impl Column for Option { +impl Column for Option { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { if let SqlType::Null = statement.column_type(start_index)? { - Ok((None, start_index + T::static_column_count() as i32)) + Ok((None, start_index + T::column_count() as i32)) } else { T::column(statement, start_index).map(|(result, next_index)| (Some(result), next_index)) } } } -impl StaticRowComponent for [T; COUNT] { - fn static_column_count() -> usize { - T::static_column_count() * COUNT +impl StaticColumnCount for [T; COUNT] { + fn column_count() -> usize { + T::column_count() * COUNT } } -impl Bind for [T; COUNT] { +impl Bind for [T; COUNT] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let mut current_index = start_index; for binding in self { @@ -287,7 +272,7 @@ impl Bind for [T; COUNT] { } } -impl Column for [T; COUNT] { +impl Column for [T; COUNT] { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let mut array = [Default::default(); COUNT]; let mut current_index = start_index; @@ -298,21 +283,21 @@ impl Column } } -impl StaticRowComponent for &Path {} +impl StaticColumnCount for &Path {} impl Bind for &Path { fn bind(&self, statement: &Statement, start_index: i32) -> Result { self.as_os_str().as_bytes().bind(statement, start_index) } } -impl StaticRowComponent for Arc {} +impl StaticColumnCount for Arc {} impl Bind for Arc { fn bind(&self, statement: &Statement, start_index: i32) -> Result { self.as_ref().bind(statement, start_index) } } -impl StaticRowComponent for PathBuf {} +impl StaticColumnCount for PathBuf {} impl Bind for PathBuf { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (self.as_ref() as &Path).bind(statement, start_index) @@ -330,8 +315,8 @@ impl Column for PathBuf { } } -impl StaticRowComponent for () { - fn static_column_count() -> usize { +impl StaticColumnCount for () { + fn column_count() -> usize { 0 } } @@ -350,11 +335,10 @@ impl Column for () { macro_rules! impl_tuple_row_traits { ( $($local:ident: $type:ident),+ ) => { - impl<$($type: RowComponent),+> RowComponent for ($($type,)+) { - fn column_count(&self) -> usize { - let ($($local,)+) = self; + impl<$($type: StaticColumnCount),+> StaticColumnCount for ($($type,)+) { + fn column_count() -> usize { let mut count = 0; - $(count += $local.column_count();)+ + $(count += $type::column_count();)+ count } } diff --git a/crates/sqlez/src/statement.rs b/crates/sqlez/src/statement.rs index da4f2388d9..69d5685ba0 100644 --- a/crates/sqlez/src/statement.rs +++ b/crates/sqlez/src/statement.rs @@ -238,15 +238,11 @@ impl<'a> Statement<'a> { pub fn bind(&self, value: T, index: i32) -> Result { debug_assert!(index > 0); - let after = value.bind(self, index)?; - debug_assert_eq!((after - index) as usize, value.column_count()); - Ok(after) + Ok(value.bind(self, index)?) } pub fn column(&mut self) -> Result { - let (result, after) = T::column(self, 0)?; - debug_assert_eq!(T::column_count(&result), after as usize); - Ok(result) + Ok(T::column(self, 0)?.0) } pub fn column_type(&mut self, index: i32) -> Result { @@ -263,9 +259,7 @@ impl<'a> Statement<'a> { } pub fn with_bindings(&mut self, bindings: impl Bind) -> Result<&mut Self> { - let column_count = bindings.column_count(); - let after = self.bind(bindings, 1)?; - debug_assert_eq!((after - 1) as usize, column_count); + self.bind(bindings, 1)?; Ok(self) } diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 1e8e72a3d2..7711ee5141 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -65,70 +65,70 @@ define_connection! { // active: bool, // Indicates if this item is the active one in the pane // ) pub static ref DB: WorkspaceDb<()> = - &[sql!( - CREATE TABLE workspaces( - workspace_id INTEGER PRIMARY KEY, - workspace_location BLOB UNIQUE, - dock_visible INTEGER, // Boolean - dock_anchor TEXT, // Enum: 'Bottom' / 'Right' / 'Expanded' - dock_pane INTEGER, // NULL indicates that we don't have a dock pane yet - left_sidebar_open INTEGER, //Boolean - timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, - FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) - ) STRICT; - - CREATE TABLE pane_groups( - group_id INTEGER PRIMARY KEY, - workspace_id INTEGER NOT NULL, - parent_group_id INTEGER, // NULL indicates that this is a root node - position INTEGER, // NULL indicates that this is a root node - axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal' - FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE, - FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE - ) STRICT; - - CREATE TABLE panes( - pane_id INTEGER PRIMARY KEY, - workspace_id INTEGER NOT NULL, - active INTEGER NOT NULL, // Boolean - FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE - ) STRICT; - - CREATE TABLE center_panes( - pane_id INTEGER PRIMARY KEY, - parent_group_id INTEGER, // NULL means that this is a root pane - position INTEGER, // NULL means that this is a root pane - FOREIGN KEY(pane_id) REFERENCES panes(pane_id) - ON DELETE CASCADE, - FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE - ) STRICT; - - CREATE TABLE items( - item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique - workspace_id INTEGER NOT NULL, - pane_id INTEGER NOT NULL, - kind TEXT NOT NULL, - position INTEGER NOT NULL, - active INTEGER NOT NULL, - FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE, - FOREIGN KEY(pane_id) REFERENCES panes(pane_id) - ON DELETE CASCADE, - PRIMARY KEY(item_id, workspace_id) - ) STRICT; - ), - sql!( - ALTER TABLE workspaces ADD COLUMN fullscreen INTEGER; // Boolean - ALTER TABLE workspaces ADD COLUMN window_x REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_y REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_width REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_height REAL; // Null means set to whatever - )]; + &[sql!( + CREATE TABLE workspaces( + workspace_id INTEGER PRIMARY KEY, + workspace_location BLOB UNIQUE, + dock_visible INTEGER, // Boolean + dock_anchor TEXT, // Enum: 'Bottom' / 'Right' / 'Expanded' + dock_pane INTEGER, // NULL indicates that we don't have a dock pane yet + left_sidebar_open INTEGER, //Boolean + timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) + ) STRICT; + + CREATE TABLE pane_groups( + group_id INTEGER PRIMARY KEY, + workspace_id INTEGER NOT NULL, + parent_group_id INTEGER, // NULL indicates that this is a root node + position INTEGER, // NULL indicates that this is a root node + axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal' + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE + ) STRICT; + + CREATE TABLE panes( + pane_id INTEGER PRIMARY KEY, + workspace_id INTEGER NOT NULL, + active INTEGER NOT NULL, // Boolean + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ON UPDATE CASCADE + ) STRICT; + + CREATE TABLE center_panes( + pane_id INTEGER PRIMARY KEY, + parent_group_id INTEGER, // NULL means that this is a root pane + position INTEGER, // NULL means that this is a root pane + FOREIGN KEY(pane_id) REFERENCES panes(pane_id) + ON DELETE CASCADE, + FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE + ) STRICT; + + CREATE TABLE items( + item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique + workspace_id INTEGER NOT NULL, + pane_id INTEGER NOT NULL, + kind TEXT NOT NULL, + position INTEGER NOT NULL, + active INTEGER NOT NULL, + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY(pane_id) REFERENCES panes(pane_id) + ON DELETE CASCADE, + PRIMARY KEY(item_id, workspace_id) + ) STRICT; + ), + sql!( + ALTER TABLE workspaces ADD COLUMN fullscreen INTEGER; // Boolean + ALTER TABLE workspaces ADD COLUMN window_x REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_y REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_width REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_height REAL; // Null means set to whatever + )]; } impl WorkspaceDb { @@ -140,16 +140,16 @@ impl WorkspaceDb { worktree_roots: &[P], ) -> Option { let workspace_location: WorkspaceLocation = worktree_roots.into(); - + // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, dock_position, fullscreen, window_x, window_y, window_width, window_height): ( + let (workspace_id, workspace_location, left_sidebar_open, dock_position, fullscreen, window_region): ( WorkspaceId, WorkspaceLocation, bool, DockPosition, bool, - f32, f32, f32, f32 + Option<(f32, f32, f32, f32)> ) = self .select_row_bound(sql! { SELECT @@ -170,7 +170,7 @@ impl WorkspaceDb { .context("No workspaces found") .warn_on_err() .flatten()?; - + Some(SerializedWorkspace { id: workspace_id, location: workspace_location.clone(), @@ -185,13 +185,13 @@ impl WorkspaceDb { dock_position, left_sidebar_open, fullscreen, - bounds: Some(RectF::new( - Vector2F::new(window_x, window_y), - Vector2F::new(window_width, window_height) + bounds: dbg!(window_region).map(|(x, y, width, height)| RectF::new( + Vector2F::new(x, y), + Vector2F::new(width, height) )) }) } - + /// Saves a workspace using the worktree roots. Will garbage collect any workspaces /// that used this workspace previously pub async fn save_workspace(&self, workspace: SerializedWorkspace) { @@ -203,30 +203,30 @@ impl WorkspaceDb { DELETE FROM pane_groups WHERE workspace_id = ?1; DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id) .expect("Clearing old panes"); - + conn.exec_bound(sql!( DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ? ))?((&workspace.location, workspace.id.clone())) .context("clearing out old locations")?; - + // Upsert conn.exec_bound(sql!( - INSERT INTO workspaces( - workspace_id, - workspace_location, - left_sidebar_open, - dock_visible, - dock_anchor, - timestamp - ) - VALUES (?1, ?2, ?3, ?4, ?5, CURRENT_TIMESTAMP) - ON CONFLICT DO - UPDATE SET - workspace_location = ?2, - left_sidebar_open = ?3, - dock_visible = ?4, - dock_anchor = ?5, - timestamp = CURRENT_TIMESTAMP + INSERT INTO workspaces( + workspace_id, + workspace_location, + left_sidebar_open, + dock_visible, + dock_anchor, + timestamp + ) + VALUES (?1, ?2, ?3, ?4, ?5, CURRENT_TIMESTAMP) + ON CONFLICT DO + UPDATE SET + workspace_location = ?2, + left_sidebar_open = ?3, + dock_visible = ?4, + dock_anchor = ?5, + timestamp = CURRENT_TIMESTAMP ))?(( workspace.id, &workspace.location, @@ -234,35 +234,35 @@ impl WorkspaceDb { workspace.dock_position, )) .context("Updating workspace")?; - + // Save center pane group and dock pane Self::save_pane_group(conn, workspace.id, &workspace.center_group, None) .context("save pane group in save workspace")?; - + let dock_id = Self::save_pane(conn, workspace.id, &workspace.dock_pane, None, true) .context("save pane in save workspace")?; - + // Complete workspace initialization conn.exec_bound(sql!( UPDATE workspaces SET dock_pane = ? - WHERE workspace_id = ? + WHERE workspace_id = ? ))?((dock_id, workspace.id)) .context("Finishing initialization with dock pane")?; - + Ok(()) }) .log_err(); }) .await; } - + query! { pub async fn next_id() -> Result { INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id } } - + query! { fn recent_workspaces() -> Result> { SELECT workspace_id, workspace_location @@ -271,14 +271,14 @@ impl WorkspaceDb { ORDER BY timestamp DESC } } - + query! { async fn delete_stale_workspace(id: WorkspaceId) -> Result<()> { DELETE FROM workspaces WHERE workspace_id IS ? } } - + // Returns the recent locations which are still valid on disk and deletes ones which no longer // exist. pub async fn recent_workspaces_on_disk(&self) -> Result> { @@ -286,18 +286,18 @@ impl WorkspaceDb { let mut delete_tasks = Vec::new(); for (id, location) in self.recent_workspaces()? { if location.paths().iter().all(|path| path.exists()) - && location.paths().iter().any(|path| path.is_dir()) + && location.paths().iter().any(|path| path.is_dir()) { result.push((id, location)); } else { delete_tasks.push(self.delete_stale_workspace(id)); } } - + futures::future::join_all(delete_tasks).await; Ok(result) } - + pub async fn last_workspace(&self) -> Result> { Ok(self .recent_workspaces_on_disk() @@ -306,7 +306,7 @@ impl WorkspaceDb { .next() .map(|(_, location)| location)) } - + fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result { Ok(self .get_pane_group(workspace_id, None)? @@ -319,7 +319,7 @@ impl WorkspaceDb { }) })) } - + fn get_pane_group( &self, workspace_id: WorkspaceId, @@ -330,27 +330,27 @@ impl WorkspaceDb { self.select_bound::(sql!( SELECT group_id, axis, pane_id, active FROM (SELECT - group_id, - axis, - NULL as pane_id, - NULL as active, - position, - parent_group_id, - workspace_id - FROM pane_groups - UNION - SELECT - NULL, - NULL, - center_panes.pane_id, - panes.active as active, - position, - parent_group_id, - panes.workspace_id as workspace_id - FROM center_panes - JOIN panes ON center_panes.pane_id = panes.pane_id) - WHERE parent_group_id IS ? AND workspace_id = ? - ORDER BY position + group_id, + axis, + NULL as pane_id, + NULL as active, + position, + parent_group_id, + workspace_id + FROM pane_groups + UNION + SELECT + NULL, + NULL, + center_panes.pane_id, + panes.active as active, + position, + parent_group_id, + panes.workspace_id as workspace_id + FROM center_panes + JOIN panes ON center_panes.pane_id = panes.pane_id) + WHERE parent_group_id IS ? AND workspace_id = ? + ORDER BY position ))?((group_id, workspace_id))? .into_iter() .map(|(group_id, axis, pane_id, active)| { @@ -376,7 +376,7 @@ impl WorkspaceDb { }) .collect::>() } - + fn save_pane_group( conn: &Connection, workspace_id: WorkspaceId, @@ -386,18 +386,18 @@ impl WorkspaceDb { match pane_group { SerializedPaneGroup::Group { axis, children } => { let (parent_id, position) = unzip_option(parent); - + let group_id = conn.select_row_bound::<_, i64>(sql!( - INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) - VALUES (?, ?, ?, ?) - RETURNING group_id + INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) + VALUES (?, ?, ?, ?) + RETURNING group_id ))?((workspace_id, parent_id, position, *axis))? .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; - + for (position, group) in children.iter().enumerate() { Self::save_pane_group(conn, workspace_id, group, Some((group_id, position)))? } - + Ok(()) } SerializedPaneGroup::Pane(pane) => { @@ -406,7 +406,7 @@ impl WorkspaceDb { } } } - + fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result { let (pane_id, active) = self.select_row_bound(sql!( SELECT pane_id, active @@ -414,13 +414,13 @@ impl WorkspaceDb { WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?) ))?(workspace_id)? .context("No dock pane for workspace")?; - + Ok(SerializedPane::new( self.get_items(pane_id).context("Reading items")?, active, )) } - + fn save_pane( conn: &Connection, workspace_id: WorkspaceId, @@ -434,7 +434,7 @@ impl WorkspaceDb { RETURNING pane_id ))?((workspace_id, pane.active))? .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?; - + if !dock { let (parent_id, order) = unzip_option(parent); conn.exec_bound(sql!( @@ -442,20 +442,20 @@ impl WorkspaceDb { VALUES (?, ?, ?) ))?((pane_id, parent_id, order))?; } - + Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?; - + Ok(pane_id) } - + fn get_items(&self, pane_id: PaneId) -> Result> { Ok(self.select_bound(sql!( SELECT kind, item_id, active FROM items WHERE pane_id = ? - ORDER BY position + ORDER BY position ))?(pane_id)?) } - + fn save_items( conn: &Connection, workspace_id: WorkspaceId, @@ -468,10 +468,10 @@ impl WorkspaceDb { for (position, item) in items.iter().enumerate() { insert((workspace_id, pane_id, position, item))?; } - + Ok(()) } - + query! { pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> { UPDATE workspaces @@ -479,9 +479,9 @@ impl WorkspaceDb { WHERE workspace_id = ? } } - + query! { - pub async fn set_bounds(workspace_id: WorkspaceId, fullscreen: bool, x: f32, y: f32, width: f32, height: f32) -> Result<()> { + pub async fn set_bounds(workspace_id: WorkspaceId, fullscreen: bool, bounds: Option<(f32, f32, f32, f32)>) -> Result<()> { UPDATE workspaces SET fullscreen = ?2, window_x = ?3, @@ -495,20 +495,20 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - + use std::sync::Arc; - + use db::open_test_db; use settings::DockAnchor; - + use super::*; - + #[gpui::test] async fn test_next_id_stability() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_next_id_stability").await); - + db.write(|conn| { conn.migrate( "test_table", @@ -517,14 +517,14 @@ mod tests { text TEXT, workspace_id INTEGER, FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE + ON DELETE CASCADE ) STRICT; )], ) .unwrap(); }) .await; - + let id = db.next_id().await.unwrap(); // Assert the empty row got inserted assert_eq!( @@ -535,28 +535,28 @@ mod tests { .unwrap()(id) .unwrap() ); - + db.write(move |conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-1", id)) - .unwrap() + .unwrap() }) .await; - + let test_text_1 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(1) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_1, "test-text-1"); } - + #[gpui::test] async fn test_workspace_id_stability() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await); - + db.write(|conn| { conn.migrate( "test_table", @@ -566,13 +566,13 @@ mod tests { workspace_id INTEGER, FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE + ON DELETE CASCADE ) STRICT;)], ) }) .await .unwrap(); - + let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), @@ -583,7 +583,7 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), @@ -594,57 +594,57 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + db.save_workspace(workspace_1.clone()).await; - + db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-1", 1)) - .unwrap(); + .unwrap(); }) .await; - + db.save_workspace(workspace_2.clone()).await; - + db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-2", 2)) - .unwrap(); + .unwrap(); }) .await; - + workspace_1.location = (["/tmp", "/tmp3"]).into(); db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_1).await; - + workspace_2.dock_pane.children.push(SerializedItem { kind: Arc::from("Test"), item_id: 10, active: true, }); db.save_workspace(workspace_2).await; - + let test_text_2 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(2) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_2, "test-text-2"); - + let test_text_1 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(1) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_1, "test-text-1"); } - + #[gpui::test] async fn test_full_workspace_serialization() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await); - + let dock_pane = crate::persistence::model::SerializedPane { children: vec![ SerializedItem::new("Terminal", 1, false), @@ -654,7 +654,7 @@ mod tests { ], active: false, }; - + // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -691,7 +691,7 @@ mod tests { )), ], }; - + let workspace = SerializedWorkspace { id: 5, location: (["/tmp", "/tmp2"]).into(), @@ -702,26 +702,26 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + db.save_workspace(workspace.clone()).await; let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]); - + assert_eq!(workspace, round_trip_workspace.unwrap()); - + // Test guaranteed duplicate IDs db.save_workspace(workspace.clone()).await; db.save_workspace(workspace.clone()).await; - + let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]); assert_eq!(workspace, round_trip_workspace.unwrap()); } - + #[gpui::test] async fn test_workspace_assignment() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_basic_functionality").await); - + let workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), @@ -732,7 +732,7 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), @@ -743,10 +743,10 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_2.clone()).await; - + // Test that paths are treated as a set assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), @@ -756,20 +756,20 @@ mod tests { db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(), workspace_1 ); - + // Make sure that other keys work assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2); assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None); - + // Test 'mutate' case of updating a pre-existing id workspace_2.location = (["/tmp", "/tmp2"]).into(); - + db.save_workspace(workspace_2.clone()).await; assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_2 ); - + // Test other mechanism for mutating let mut workspace_3 = SerializedWorkspace { id: 3, @@ -781,13 +781,13 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + db.save_workspace(workspace_3.clone()).await; assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_3 ); - + // Make sure that updating paths differently also works workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into(); db.save_workspace(workspace_3.clone()).await; @@ -798,11 +798,11 @@ mod tests { workspace_3 ); } - + use crate::dock::DockPosition; use crate::persistence::model::SerializedWorkspace; use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup}; - + fn default_workspace>( workspace_id: &[P], dock_pane: SerializedPane, @@ -819,13 +819,13 @@ mod tests { bounds: Default::default(), } } - + #[gpui::test] async fn test_basic_dock_pane() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("basic_dock_pane").await); - + let dock_pane = crate::persistence::model::SerializedPane::new( vec![ SerializedItem::new("Terminal", 1, false), @@ -835,22 +835,22 @@ mod tests { ], false, ); - + let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default()); - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - + assert_eq!(workspace.dock_pane, new_workspace.dock_pane); } - + #[gpui::test] async fn test_simple_split() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("simple_split").await); - + // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -887,22 +887,22 @@ mod tests { )), ], }; - + let workspace = default_workspace(&["/tmp"], Default::default(), ¢er_pane); - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - + assert_eq!(workspace.center_group, new_workspace.center_group); } - + #[gpui::test] async fn test_cleanup_panes() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_cleanup_panes").await); - + let center_pane = SerializedPaneGroup::Group { axis: gpui::Axis::Horizontal, children: vec![ @@ -934,13 +934,13 @@ mod tests { )), ], }; - + let id = &["/tmp"]; - + let mut workspace = default_workspace(id, Default::default(), ¢er_pane); - + db.save_workspace(workspace.clone()).await; - + workspace.center_group = SerializedPaneGroup::Group { axis: gpui::Axis::Vertical, children: vec![ @@ -960,11 +960,11 @@ mod tests { )), ], }; - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(id).unwrap(); - + assert_eq!(workspace.center_group, new_workspace.center_group); } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 4bc8881bba..f6236ced79 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -11,7 +11,7 @@ use gpui::{ }; use db::sqlez::{ - bindable::{Bind, Column, StaticRowComponent}, + bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; use project::Project; @@ -42,7 +42,7 @@ impl, T: IntoIterator> From for WorkspaceLocation { } } -impl StaticRowComponent for WorkspaceLocation {} +impl StaticColumnCount for WorkspaceLocation {} impl Bind for &WorkspaceLocation { fn bind(&self, statement: &Statement, start_index: i32) -> Result { bincode::serialize(&self.0) @@ -254,8 +254,8 @@ impl Default for SerializedItem { } } -impl StaticRowComponent for SerializedItem { - fn static_column_count() -> usize { +impl StaticColumnCount for SerializedItem { + fn column_count() -> usize { 3 } } @@ -283,8 +283,8 @@ impl Column for SerializedItem { } } -impl StaticRowComponent for DockPosition { - fn static_column_count() -> usize { +impl StaticColumnCount for DockPosition { + fn column_count() -> usize { 2 } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index addbb9b017..4c742cd7fa 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -683,9 +683,9 @@ impl Workspace { // Use the serialized workspace to construct the new window let (_, workspace) = cx.add_window( - (app_state.build_window_options)( - serialized_workspace.as_ref().map(|sw| sw.bounds()), - ), + (app_state.build_window_options)(dbg!(serialized_workspace + .as_ref() + .map(|sw| sw.bounds()))), |cx| { let mut workspace = Workspace::new( serialized_workspace, @@ -697,15 +697,18 @@ impl Workspace { (app_state.initialize_workspace)(&mut workspace, &app_state, cx); cx.observe_window_bounds(move |_, bounds, cx| { let fullscreen = cx.window_is_fullscreen(cx.window_id()); - cx.background() - .spawn(DB.set_bounds( - workspace_id, - fullscreen, - bounds.min_x(), - bounds.min_y(), - bounds.width(), - bounds.height(), + let bounds = if let WindowBounds::Fixed(region) = bounds { + Some(( + region.min_x(), + region.min_y(), + region.width(), + region.height(), )) + } else { + None + }; + cx.background() + .spawn(DB.set_bounds(workspace_id, fullscreen, bounds)) .detach_and_log_err(cx); }) .detach(); From 5eac797a93a0d684c0d36d6b4904c0d21f7f9578 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 25 Jan 2023 11:30:03 -0800 Subject: [PATCH 032/180] mostly working now --- Cargo.lock | 11 + crates/collab/src/tests.rs | 2 +- crates/collab_ui/src/collab_ui.rs | 25 +- crates/gpui/Cargo.toml | 3 +- crates/gpui/src/app.rs | 43 +-- crates/gpui/src/platform.rs | 96 ++++++- crates/gpui/src/platform/mac/platform.rs | 12 +- crates/gpui/src/platform/mac/screen.rs | 67 ++++- crates/gpui/src/platform/mac/status_item.rs | 114 ++++---- crates/gpui/src/platform/mac/window.rs | 131 +++++---- crates/gpui/src/platform/test.rs | 203 +++++++------ crates/sqlez/Cargo.toml | 1 + crates/sqlez/src/bindable.rs | 41 ++- crates/workspace/Cargo.toml | 1 + crates/workspace/src/dock.rs | 2 +- crates/workspace/src/persistence.rs | 304 ++++++++++---------- crates/workspace/src/persistence/model.rs | 21 +- crates/workspace/src/workspace.rs | 35 +-- crates/zed/Cargo.toml | 1 + crates/zed/src/zed.rs | 16 +- 20 files changed, 675 insertions(+), 454 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f858a61aaa..927010c4d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1275,6 +1275,7 @@ source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2f dependencies = [ "core-foundation-sys", "libc", + "uuid 0.5.1", ] [[package]] @@ -2591,6 +2592,7 @@ dependencies = [ "tiny-skia", "usvg", "util", + "uuid 1.2.2", "waker-fn", ] @@ -6014,6 +6016,7 @@ dependencies = [ "parking_lot 0.11.2", "smol", "thread_local", + "uuid 1.2.2", ] [[package]] @@ -7324,6 +7327,12 @@ dependencies = [ "tempdir", ] +[[package]] +name = "uuid" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" + [[package]] name = "uuid" version = "0.8.2" @@ -8169,6 +8178,7 @@ dependencies = [ "smallvec", "theme", "util", + "uuid 1.2.2", ] [[package]] @@ -8312,6 +8322,7 @@ dependencies = [ "url", "urlencoding", "util", + "uuid 1.2.2", "vim", "workspace", ] diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index fb018a9017..120c577e0f 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -196,7 +196,7 @@ impl TestServer { languages: Arc::new(LanguageRegistry::new(Task::ready(()))), themes: ThemeRegistry::new((), cx.font_cache()), fs: fs.clone(), - build_window_options: |_| Default::default(), + build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), dock_default_item_factory: |_, _| unimplemented!(), }); diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 2d6b9489d8..38a47e87dc 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -54,17 +54,20 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { }) .await?; - let (_, workspace) = cx.add_window((app_state.build_window_options)(None), |cx| { - let mut workspace = Workspace::new( - Default::default(), - 0, - project, - app_state.dock_default_item_factory, - cx, - ); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - workspace - }); + let (_, workspace) = cx.add_window( + (app_state.build_window_options)(None, None, cx.platform().as_ref()), + |cx| { + let mut workspace = Workspace::new( + Default::default(), + 0, + project, + app_state.dock_default_item_factory, + cx, + ); + (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + workspace + }, + ); workspace }; diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index e1b6e11b46..7be254be4d 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -47,6 +47,7 @@ smol = "1.2" time = { version = "0.3", features = ["serde", "serde-well-known"] } tiny-skia = "0.5" usvg = "0.14" +uuid = { version = "1.1.2", features = ["v4"] } waker-fn = "1.1.0" [build-dependencies] @@ -66,7 +67,7 @@ media = { path = "../media" } anyhow = "1" block = "0.1" cocoa = "0.24" -core-foundation = "0.9.3" +core-foundation = { version = "0.9.3", features = ["with-uuid"] } core-graphics = "0.22.3" core-text = "19.2" font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1" } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index df62580c4d..c51de8c0c8 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -32,6 +32,7 @@ use collections::{hash_map::Entry, HashMap, HashSet, VecDeque}; use platform::Event; #[cfg(any(test, feature = "test-support"))] pub use test_app_context::{ContextHandle, TestAppContext}; +use uuid::Uuid; use crate::{ elements::ElementBox, @@ -595,7 +596,7 @@ type ReleaseObservationCallback = Box; type WindowActivationCallback = Box bool>; type WindowFullscreenCallback = Box bool>; -type WindowBoundsCallback = Box bool>; +type WindowBoundsCallback = Box bool>; type KeystrokeCallback = Box< dyn FnMut(&Keystroke, &MatchResult, Option<&Box>, &mut MutableAppContext) -> bool, >; @@ -909,10 +910,17 @@ impl MutableAppContext { .map_or(false, |window| window.is_fullscreen) } - pub fn window_bounds(&self, window_id: usize) -> RectF { + pub fn window_bounds(&self, window_id: usize) -> WindowBounds { self.presenters_and_platform_windows[&window_id].1.bounds() } + pub fn window_display_uuid(&self, window_id: usize) -> Uuid { + self.presenters_and_platform_windows[&window_id] + .1 + .screen() + .display_uuid() + } + pub fn render_view(&mut self, params: RenderParams) -> Result { let window_id = params.window_id; let view_id = params.view_id; @@ -1246,7 +1254,7 @@ impl MutableAppContext { fn observe_window_bounds(&mut self, window_id: usize, callback: F) -> Subscription where - F: 'static + FnMut(WindowBounds, &mut MutableAppContext) -> bool, + F: 'static + FnMut(WindowBounds, Uuid, &mut MutableAppContext) -> bool, { let subscription_id = post_inc(&mut self.next_subscription_id); self.pending_effects @@ -2382,14 +2390,12 @@ impl MutableAppContext { callback(is_fullscreen, this) }); - let bounds = if this.window_is_fullscreen(window_id) { - WindowBounds::Fullscreen - } else { - WindowBounds::Fixed(this.window_bounds(window_id)) - }; - + let bounds = this.window_bounds(window_id); + let uuid = this.window_display_uuid(window_id); let mut bounds_observations = this.window_bounds_observations.clone(); - bounds_observations.emit(window_id, this, |callback, this| callback(bounds, this)); + bounds_observations.emit(window_id, this, |callback, this| { + callback(bounds, uuid, this) + }); Some(()) }); @@ -2568,15 +2574,12 @@ impl MutableAppContext { } fn handle_window_moved(&mut self, window_id: usize) { - let bounds = if self.window_is_fullscreen(window_id) { - WindowBounds::Fullscreen - } else { - WindowBounds::Fixed(self.window_bounds(window_id)) - }; + let bounds = self.window_bounds(window_id); + let display = self.window_display_uuid(window_id); self.window_bounds_observations .clone() .emit(window_id, self, move |callback, this| { - callback(bounds, this); + callback(bounds, display, this); true }); } @@ -3717,7 +3720,7 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.toggle_window_full_screen(self.window_id) } - pub fn window_bounds(&self) -> RectF { + pub fn window_bounds(&self) -> WindowBounds { self.app.window_bounds(self.window_id) } @@ -4023,14 +4026,14 @@ impl<'a, T: View> ViewContext<'a, T> { pub fn observe_window_bounds(&mut self, mut callback: F) -> Subscription where - F: 'static + FnMut(&mut T, WindowBounds, &mut ViewContext), + F: 'static + FnMut(&mut T, WindowBounds, Uuid, &mut ViewContext), { let observer = self.weak_handle(); self.app - .observe_window_bounds(self.window_id(), move |bounds, cx| { + .observe_window_bounds(self.window_id(), move |bounds, display, cx| { if let Some(observer) = observer.upgrade(cx) { observer.update(cx, |observer, cx| { - callback(observer, bounds, cx); + callback(observer, bounds, display, cx); }); true } else { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 1db1fe62b0..e91c1e87aa 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -18,11 +18,15 @@ use crate::{ text_layout::{LineLayout, RunStyle}, Action, ClipboardItem, Menu, Scene, }; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use async_task::Runnable; pub use event::*; use postage::oneshot; use serde::Deserialize; +use sqlez::{ + bindable::{Bind, Column, StaticColumnCount}, + statement::Statement, +}; use std::{ any::Any, fmt::{self, Debug, Display}, @@ -33,6 +37,7 @@ use std::{ sync::Arc, }; use time::UtcOffset; +use uuid::Uuid; pub trait Platform: Send + Sync { fn dispatcher(&self) -> Arc; @@ -44,6 +49,7 @@ pub trait Platform: Send + Sync { fn unhide_other_apps(&self); fn quit(&self); + fn screen_by_id(&self, id: Uuid) -> Option>; fn screens(&self) -> Vec>; fn open_window( @@ -118,17 +124,18 @@ pub trait InputHandler { pub trait Screen: Debug { fn as_any(&self) -> &dyn Any; fn size(&self) -> Vector2F; + fn display_uuid(&self) -> Uuid; } pub trait Window { + fn bounds(&self) -> WindowBounds; + fn content_size(&self) -> Vector2F; + fn scale_factor(&self) -> f32; + fn titlebar_height(&self) -> f32; + fn appearance(&self) -> Appearance; + fn screen(&self) -> Rc; + fn as_any_mut(&mut self) -> &mut dyn Any; - fn on_event(&mut self, callback: Box bool>); - fn on_active_status_change(&mut self, callback: Box); - fn on_resize(&mut self, callback: Box); - fn on_fullscreen(&mut self, callback: Box); - fn on_moved(&mut self, callback: Box); - fn on_should_close(&mut self, callback: Box bool>); - fn on_close(&mut self, callback: Box); fn set_input_handler(&mut self, input_handler: Box); fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; fn activate(&self); @@ -137,14 +144,16 @@ pub trait Window { fn show_character_palette(&self); fn minimize(&self); fn zoom(&self); + fn present_scene(&mut self, scene: Scene); fn toggle_full_screen(&self); - fn bounds(&self) -> RectF; - fn content_size(&self) -> Vector2F; - fn scale_factor(&self) -> f32; - fn titlebar_height(&self) -> f32; - fn present_scene(&mut self, scene: Scene); - fn appearance(&self) -> Appearance; + fn on_event(&mut self, callback: Box bool>); + fn on_active_status_change(&mut self, callback: Box); + fn on_resize(&mut self, callback: Box); + fn on_fullscreen(&mut self, callback: Box); + fn on_moved(&mut self, callback: Box); + fn on_should_close(&mut self, callback: Box bool>); + fn on_close(&mut self, callback: Box); fn on_appearance_changed(&mut self, callback: Box); fn is_topmost_for_position(&self, position: Vector2F) -> bool; } @@ -187,13 +196,70 @@ pub enum WindowKind { PopUp, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum WindowBounds { Fullscreen, Maximized, Fixed(RectF), } +impl StaticColumnCount for WindowBounds { + fn column_count() -> usize { + 5 + } +} + +impl Bind for WindowBounds { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let (region, next_index) = match self { + WindowBounds::Fullscreen => { + let next_index = statement.bind("Fullscreen", start_index)?; + (None, next_index) + } + WindowBounds::Maximized => { + let next_index = statement.bind("Maximized", start_index)?; + (None, next_index) + } + WindowBounds::Fixed(region) => { + let next_index = statement.bind("Fixed", start_index)?; + (Some(*region), next_index) + } + }; + + statement.bind( + region.map(|region| { + ( + region.min_x(), + region.min_y(), + region.width(), + region.height(), + ) + }), + next_index, + ) + } +} + +impl Column for WindowBounds { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (window_state, next_index) = String::column(statement, start_index)?; + let bounds = match window_state.as_str() { + "Fullscreen" => WindowBounds::Fullscreen, + "Maximized" => WindowBounds::Maximized, + "Fixed" => { + let ((x, y, width, height), _) = Column::column(statement, next_index)?; + WindowBounds::Fixed(RectF::new( + Vector2F::new(x, y), + Vector2F::new(width, height), + )) + } + _ => bail!("Window State did not have a valid string"), + }; + + Ok((bounds, next_index + 4)) + } +} + pub struct PathPromptOptions { pub files: bool, pub directories: bool, diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index dbb1a01f31..5d13227585 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -440,6 +440,10 @@ impl platform::Platform for MacPlatform { self.dispatcher.clone() } + fn fonts(&self) -> Arc { + self.fonts.clone() + } + fn activate(&self, ignoring_other_apps: bool) { unsafe { let app = NSApplication::sharedApplication(nil); @@ -488,6 +492,10 @@ impl platform::Platform for MacPlatform { } } + fn screen_by_id(&self, id: uuid::Uuid) -> Option> { + Screen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>) + } + fn screens(&self) -> Vec> { Screen::all() .into_iter() @@ -512,10 +520,6 @@ impl platform::Platform for MacPlatform { Box::new(StatusItem::add(self.fonts())) } - fn fonts(&self) -> Arc { - self.fonts.clone() - } - fn write_to_clipboard(&self, item: ClipboardItem) { unsafe { self.pasteboard.clearContents(); diff --git a/crates/gpui/src/platform/mac/screen.rs b/crates/gpui/src/platform/mac/screen.rs index fdc7fbb505..a54ffb3f90 100644 --- a/crates/gpui/src/platform/mac/screen.rs +++ b/crates/gpui/src/platform/mac/screen.rs @@ -1,4 +1,4 @@ -use std::any::Any; +use std::{any::Any, ffi::c_void}; use crate::{ geometry::vector::{vec2f, Vector2F}, @@ -7,8 +7,19 @@ use crate::{ use cocoa::{ appkit::NSScreen, base::{id, nil}, - foundation::NSArray, + foundation::{NSArray, NSDictionary, NSString}, }; +use core_foundation::{ + number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef}, + uuid::{CFUUIDGetUUIDBytes, CFUUIDRef}, +}; +use core_graphics::display::CGDirectDisplayID; +use uuid::Uuid; + +#[link(name = "ApplicationServices", kind = "framework")] +extern "C" { + pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; +} #[derive(Debug)] pub struct Screen { @@ -16,11 +27,23 @@ pub struct Screen { } impl Screen { + pub fn find_by_id(uuid: Uuid) -> Option { + unsafe { + let native_screens = NSScreen::screens(nil); + (0..NSArray::count(native_screens)) + .into_iter() + .map(|ix| Screen { + native_screen: native_screens.objectAtIndex(ix), + }) + .find(|screen| platform::Screen::display_uuid(screen) == uuid) + } + } + pub fn all() -> Vec { let mut screens = Vec::new(); unsafe { let native_screens = NSScreen::screens(nil); - for ix in 0..native_screens.count() { + for ix in 0..NSArray::count(native_screens) { screens.push(Screen { native_screen: native_screens.objectAtIndex(ix), }); @@ -41,4 +64,42 @@ impl platform::Screen for Screen { vec2f(frame.size.width as f32, frame.size.height as f32) } } + + fn display_uuid(&self) -> uuid::Uuid { + unsafe { + // Screen ids are not stable. Further, the default device id is also unstable across restarts. + // CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use. + // This approach is similar to that which winit takes + // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99 + let device_description = self.native_screen.deviceDescription(); + let key = NSString::alloc(nil).init_str("NSScreenNumber"); + let device_id_obj = device_description.objectForKey_(key); + let mut device_id: u32 = 0; + CFNumberGetValue( + device_id_obj as CFNumberRef, + kCFNumberIntType, + (&mut device_id) as *mut _ as *mut c_void, + ); + let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID); + let bytes = CFUUIDGetUUIDBytes(cfuuid); + Uuid::from_bytes([ + bytes.byte0, + bytes.byte1, + bytes.byte2, + bytes.byte3, + bytes.byte4, + bytes.byte5, + bytes.byte6, + bytes.byte7, + bytes.byte8, + bytes.byte9, + bytes.byte10, + bytes.byte11, + bytes.byte12, + bytes.byte13, + bytes.byte14, + bytes.byte15, + ]) + } + } } diff --git a/crates/gpui/src/platform/mac/status_item.rs b/crates/gpui/src/platform/mac/status_item.rs index 8ac9dbea71..812027d35c 100644 --- a/crates/gpui/src/platform/mac/status_item.rs +++ b/crates/gpui/src/platform/mac/status_item.rs @@ -7,7 +7,7 @@ use crate::{ self, mac::{platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer}, }, - Event, FontSystem, Scene, + Event, FontSystem, Scene, WindowBounds, }; use cocoa::{ appkit::{NSScreen, NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow}, @@ -32,6 +32,8 @@ use std::{ sync::Arc, }; +use super::screen::Screen; + static mut VIEW_CLASS: *const Class = ptr::null(); const STATE_IVAR: &str = "state"; @@ -167,30 +169,42 @@ impl StatusItem { } impl platform::Window for StatusItem { + fn bounds(&self) -> WindowBounds { + self.0.borrow().bounds() + } + + fn content_size(&self) -> Vector2F { + self.0.borrow().content_size() + } + + fn scale_factor(&self) -> f32 { + self.0.borrow().scale_factor() + } + + fn titlebar_height(&self) -> f32 { + 0. + } + + fn appearance(&self) -> crate::Appearance { + unsafe { + let appearance: id = + msg_send![self.0.borrow().native_item.button(), effectiveAppearance]; + crate::Appearance::from_native(appearance) + } + } + + fn screen(&self) -> Rc { + unsafe { + Rc::new(Screen { + native_screen: self.0.borrow().native_window().screen(), + }) + } + } + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } - fn on_event(&mut self, callback: Box bool>) { - self.0.borrow_mut().event_callback = Some(callback); - } - - fn on_appearance_changed(&mut self, callback: Box) { - self.0.borrow_mut().appearance_changed_callback = Some(callback); - } - - fn on_active_status_change(&mut self, _: Box) {} - - fn on_resize(&mut self, _: Box) {} - - fn on_moved(&mut self, _: Box) {} - - fn on_fullscreen(&mut self, _: Box) {} - - fn on_should_close(&mut self, _: Box bool>) {} - - fn on_close(&mut self, _: Box) {} - fn set_input_handler(&mut self, _: Box) {} fn prompt( @@ -226,26 +240,6 @@ impl platform::Window for StatusItem { unimplemented!() } - fn toggle_full_screen(&self) { - unimplemented!() - } - - fn bounds(&self) -> RectF { - self.0.borrow().bounds() - } - - fn content_size(&self) -> Vector2F { - self.0.borrow().content_size() - } - - fn scale_factor(&self) -> f32 { - self.0.borrow().scale_factor() - } - - fn titlebar_height(&self) -> f32 { - 0. - } - fn present_scene(&mut self, scene: Scene) { self.0.borrow_mut().scene = Some(scene); unsafe { @@ -253,12 +247,28 @@ impl platform::Window for StatusItem { } } - fn appearance(&self) -> crate::Appearance { - unsafe { - let appearance: id = - msg_send![self.0.borrow().native_item.button(), effectiveAppearance]; - crate::Appearance::from_native(appearance) - } + fn toggle_full_screen(&self) { + unimplemented!() + } + + fn on_event(&mut self, callback: Box bool>) { + self.0.borrow_mut().event_callback = Some(callback); + } + + fn on_active_status_change(&mut self, _: Box) {} + + fn on_resize(&mut self, _: Box) {} + + fn on_fullscreen(&mut self, _: Box) {} + + fn on_moved(&mut self, _: Box) {} + + fn on_should_close(&mut self, _: Box bool>) {} + + fn on_close(&mut self, _: Box) {} + + fn on_appearance_changed(&mut self, callback: Box) { + self.0.borrow_mut().appearance_changed_callback = Some(callback); } fn is_topmost_for_position(&self, _: Vector2F) -> bool { @@ -267,9 +277,9 @@ impl platform::Window for StatusItem { } impl StatusItemState { - fn bounds(&self) -> RectF { + fn bounds(&self) -> WindowBounds { unsafe { - let window: id = msg_send![self.native_item.button(), window]; + let window: id = self.native_window(); let screen_frame = window.screen().visibleFrame(); let window_frame = NSWindow::frame(window); let origin = vec2f( @@ -281,7 +291,7 @@ impl StatusItemState { window_frame.size.width as f32, window_frame.size.height as f32, ); - RectF::new(origin, size) + WindowBounds::Fixed(RectF::new(origin, size)) } } @@ -299,6 +309,10 @@ impl StatusItemState { NSScreen::backingScaleFactor(window.screen()) as f32 } } + + pub fn native_window(&self) -> id { + unsafe { msg_send![self.native_item.button(), window] } + } } extern "C" fn dealloc_view(this: &Object, _: Sel) { diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index c4cd7d0e27..e981cc6131 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -419,19 +419,17 @@ impl Window { WindowBounds::Fixed(top_left_bounds) => { let frame = screen.visibleFrame(); let bottom_left_bounds = RectF::new( - vec2f( + dbg!(vec2f( top_left_bounds.origin_x(), frame.size.height as f32 - top_left_bounds.origin_y() - top_left_bounds.height(), - ), - top_left_bounds.size(), + )), + dbg!(top_left_bounds.size()), ) .to_ns_rect(); - native_window.setFrame_display_( - native_window.convertRectToScreen_(bottom_left_bounds), - YES, - ); + let screen_rect = native_window.convertRectToScreen_(bottom_left_bounds); + native_window.setFrame_display_(screen_rect, YES); } } @@ -585,38 +583,41 @@ impl Drop for Window { } impl platform::Window for Window { + fn bounds(&self) -> WindowBounds { + self.0.as_ref().borrow().bounds() + } + + fn content_size(&self) -> Vector2F { + self.0.as_ref().borrow().content_size() + } + + fn scale_factor(&self) -> f32 { + self.0.as_ref().borrow().scale_factor() + } + + fn titlebar_height(&self) -> f32 { + self.0.as_ref().borrow().titlebar_height() + } + + fn appearance(&self) -> crate::Appearance { + unsafe { + let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance]; + crate::Appearance::from_native(appearance) + } + } + + fn screen(&self) -> Rc { + unsafe { + Rc::new(Screen { + native_screen: self.0.as_ref().borrow().native_window.screen(), + }) + } + } + fn as_any_mut(&mut self) -> &mut dyn Any { self } - fn on_event(&mut self, callback: Box bool>) { - self.0.as_ref().borrow_mut().event_callback = Some(callback); - } - - fn on_resize(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().resize_callback = Some(callback); - } - - fn on_moved(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().moved_callback = Some(callback); - } - - fn on_fullscreen(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback); - } - - fn on_should_close(&mut self, callback: Box bool>) { - self.0.as_ref().borrow_mut().should_close_callback = Some(callback); - } - - fn on_close(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().close_callback = Some(callback); - } - - fn on_active_status_change(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().activate_callback = Some(callback); - } - fn set_input_handler(&mut self, input_handler: Box) { self.0.as_ref().borrow_mut().input_handler = Some(input_handler); } @@ -726,6 +727,10 @@ impl platform::Window for Window { .detach(); } + fn present_scene(&mut self, scene: Scene) { + self.0.as_ref().borrow_mut().present_scene(scene); + } + fn toggle_full_screen(&self) { let this = self.0.borrow(); let window = this.native_window; @@ -738,31 +743,32 @@ impl platform::Window for Window { .detach(); } - fn bounds(&self) -> RectF { - self.0.as_ref().borrow().bounds() + fn on_event(&mut self, callback: Box bool>) { + self.0.as_ref().borrow_mut().event_callback = Some(callback); } - fn content_size(&self) -> Vector2F { - self.0.as_ref().borrow().content_size() + fn on_active_status_change(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().activate_callback = Some(callback); } - fn scale_factor(&self) -> f32 { - self.0.as_ref().borrow().scale_factor() + fn on_resize(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().resize_callback = Some(callback); } - fn present_scene(&mut self, scene: Scene) { - self.0.as_ref().borrow_mut().present_scene(scene); + fn on_fullscreen(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback); } - fn titlebar_height(&self) -> f32 { - self.0.as_ref().borrow().titlebar_height() + fn on_moved(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().moved_callback = Some(callback); } - fn appearance(&self) -> crate::Appearance { - unsafe { - let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance]; - crate::Appearance::from_native(appearance) - } + fn on_should_close(&mut self, callback: Box bool>) { + self.0.as_ref().borrow_mut().should_close_callback = Some(callback); + } + + fn on_close(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().close_callback = Some(callback); } fn on_appearance_changed(&mut self, callback: Box) { @@ -846,20 +852,39 @@ impl WindowState { } } - fn bounds(&self) -> RectF { + fn is_fullscreen(&self) -> bool { unsafe { + let style_mask = self.native_window.styleMask(); + style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask) + } + } + + fn bounds(&self) -> WindowBounds { + unsafe { + if self.is_fullscreen() { + return WindowBounds::Fullscreen; + } + let screen_frame = self.native_window.screen().visibleFrame(); let window_frame = NSWindow::frame(self.native_window); let origin = vec2f( window_frame.origin.x as f32, - (window_frame.origin.y - screen_frame.size.height - window_frame.size.height) + (screen_frame.size.height - window_frame.origin.y - window_frame.size.height) as f32, ); let size = vec2f( window_frame.size.width as f32, window_frame.size.height as f32, ); - RectF::new(origin, size) + + if origin.is_zero() + && size.x() == screen_frame.size.width as f32 + && size.y() == screen_frame.size.height as f32 + { + WindowBounds::Maximized + } else { + WindowBounds::Fixed(RectF::new(origin, size)) + } } } diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index f3e1ce4055..2c8a940969 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -20,11 +20,20 @@ use std::{ }; use time::UtcOffset; -pub struct Platform { - dispatcher: Arc, - fonts: Arc, - current_clipboard_item: Mutex>, - cursor: Mutex, +struct Dispatcher; + +impl super::Dispatcher for Dispatcher { + fn is_main_thread(&self) -> bool { + true + } + + fn run_on_main_thread(&self, task: async_task::Runnable) { + task.run(); + } +} + +pub fn foreground_platform() -> ForegroundPlatform { + ForegroundPlatform::default() } #[derive(Default)] @@ -32,24 +41,6 @@ pub struct ForegroundPlatform { last_prompt_for_new_path_args: RefCell>)>>, } -struct Dispatcher; - -pub struct Window { - pub(crate) size: Vector2F, - scale_factor: f32, - current_scene: Option, - event_handlers: Vec bool>>, - pub(crate) resize_handlers: Vec>, - pub(crate) moved_handlers: Vec>, - close_handlers: Vec>, - fullscreen_handlers: Vec>, - pub(crate) active_status_change_handlers: Vec>, - pub(crate) should_close_handler: Option bool>>, - pub(crate) title: Option, - pub(crate) edited: bool, - pub(crate) pending_prompts: RefCell>>, -} - #[cfg(any(test, feature = "test-support"))] impl ForegroundPlatform { pub(crate) fn simulate_new_path_selection( @@ -103,6 +94,17 @@ impl super::ForegroundPlatform for ForegroundPlatform { } } +pub fn platform() -> Platform { + Platform::new() +} + +pub struct Platform { + dispatcher: Arc, + fonts: Arc, + current_clipboard_item: Mutex>, + cursor: Mutex, +} + impl Platform { fn new() -> Self { Self { @@ -133,6 +135,10 @@ impl super::Platform for Platform { fn quit(&self) {} + fn screen_by_id(&self, _id: uuid::Uuid) -> Option> { + None + } + fn screens(&self) -> Vec> { Default::default() } @@ -220,6 +226,39 @@ impl super::Platform for Platform { } } +#[derive(Debug)] +pub struct Screen; + +impl super::Screen for Screen { + fn as_any(&self) -> &dyn Any { + self + } + + fn size(&self) -> Vector2F { + Vector2F::new(1920., 1080.) + } + + fn display_uuid(&self) -> uuid::Uuid { + uuid::Uuid::new_v4() + } +} + +pub struct Window { + pub(crate) size: Vector2F, + scale_factor: f32, + current_scene: Option, + event_handlers: Vec bool>>, + pub(crate) resize_handlers: Vec>, + pub(crate) moved_handlers: Vec>, + close_handlers: Vec>, + fullscreen_handlers: Vec>, + pub(crate) active_status_change_handlers: Vec>, + pub(crate) should_close_handler: Option bool>>, + pub(crate) title: Option, + pub(crate) edited: bool, + pub(crate) pending_prompts: RefCell>>, +} + impl Window { fn new(size: Vector2F) -> Self { Self { @@ -244,45 +283,35 @@ impl Window { } } -impl super::Dispatcher for Dispatcher { - fn is_main_thread(&self) -> bool { - true - } - - fn run_on_main_thread(&self, task: async_task::Runnable) { - task.run(); - } -} - impl super::Window for Window { + fn bounds(&self) -> WindowBounds { + WindowBounds::Fixed(RectF::new(Vector2F::zero(), self.size)) + } + + fn content_size(&self) -> Vector2F { + self.size + } + + fn scale_factor(&self) -> f32 { + self.scale_factor + } + + fn titlebar_height(&self) -> f32 { + 24. + } + + fn appearance(&self) -> crate::Appearance { + crate::Appearance::Light + } + + fn screen(&self) -> Rc { + Rc::new(Screen) + } + fn as_any_mut(&mut self) -> &mut dyn Any { self } - fn on_event(&mut self, callback: Box bool>) { - self.event_handlers.push(callback); - } - - fn on_active_status_change(&mut self, callback: Box) { - self.active_status_change_handlers.push(callback); - } - - fn on_fullscreen(&mut self, callback: Box) { - self.fullscreen_handlers.push(callback) - } - - fn on_resize(&mut self, callback: Box) { - self.resize_handlers.push(callback); - } - - fn on_moved(&mut self, callback: Box) { - self.moved_handlers.push(callback); - } - - fn on_close(&mut self, callback: Box) { - self.close_handlers.push(callback); - } - fn set_input_handler(&mut self, _: Box) {} fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str]) -> oneshot::Receiver { @@ -301,40 +330,44 @@ impl super::Window for Window { self.edited = edited; } - fn on_should_close(&mut self, callback: Box bool>) { - self.should_close_handler = Some(callback); - } - fn show_character_palette(&self) {} fn minimize(&self) {} fn zoom(&self) {} - fn toggle_full_screen(&self) {} - - fn bounds(&self) -> RectF { - RectF::new(Default::default(), self.size) - } - - fn content_size(&self) -> Vector2F { - self.size - } - - fn scale_factor(&self) -> f32 { - self.scale_factor - } - - fn titlebar_height(&self) -> f32 { - 24. - } - fn present_scene(&mut self, scene: crate::Scene) { self.current_scene = Some(scene); } - fn appearance(&self) -> crate::Appearance { - crate::Appearance::Light + fn toggle_full_screen(&self) {} + + fn on_event(&mut self, callback: Box bool>) { + self.event_handlers.push(callback); + } + + fn on_active_status_change(&mut self, callback: Box) { + self.active_status_change_handlers.push(callback); + } + + fn on_resize(&mut self, callback: Box) { + self.resize_handlers.push(callback); + } + + fn on_fullscreen(&mut self, callback: Box) { + self.fullscreen_handlers.push(callback) + } + + fn on_moved(&mut self, callback: Box) { + self.moved_handlers.push(callback); + } + + fn on_should_close(&mut self, callback: Box bool>) { + self.should_close_handler = Some(callback); + } + + fn on_close(&mut self, callback: Box) { + self.close_handlers.push(callback); } fn on_appearance_changed(&mut self, _: Box) {} @@ -343,11 +376,3 @@ impl super::Window for Window { true } } - -pub fn platform() -> Platform { - Platform::new() -} - -pub fn foreground_platform() -> ForegroundPlatform { - ForegroundPlatform::default() -} diff --git a/crates/sqlez/Cargo.toml b/crates/sqlez/Cargo.toml index 8409a1dff5..716ec76644 100644 --- a/crates/sqlez/Cargo.toml +++ b/crates/sqlez/Cargo.toml @@ -15,3 +15,4 @@ thread_local = "1.1.4" lazy_static = "1.4" parking_lot = "0.11.1" futures = "0.3" +uuid = { version = "1.1.2", features = ["v4"] } \ No newline at end of file diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 9d5d0c8b2f..86d69afe5f 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -60,6 +60,14 @@ impl Bind for &[u8; C] { } } +impl Column for [u8; C] { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let bytes_slice = statement.column_blob(start_index)?; + let array = bytes_slice.try_into()?; + Ok((array, start_index + 1)) + } +} + impl StaticColumnCount for Vec {} impl Bind for Vec { fn bind(&self, statement: &Statement, start_index: i32) -> Result { @@ -236,8 +244,7 @@ impl Bind for Option { if let Some(this) = self { this.bind(statement, start_index) } else { - for i in 0..T::column_count() { - dbg!(i); + for _ in 0..T::column_count() { statement.bind_null(start_index)?; start_index += 1; } @@ -272,17 +279,6 @@ impl Bind for [T; COUNT] { } } -impl Column for [T; COUNT] { - fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let mut array = [Default::default(); COUNT]; - let mut current_index = start_index; - for i in 0..COUNT { - (array[i], current_index) = T::column(statement, current_index)?; - } - Ok((array, current_index)) - } -} - impl StaticColumnCount for &Path {} impl Bind for &Path { fn bind(&self, statement: &Statement, start_index: i32) -> Result { @@ -315,6 +311,25 @@ impl Column for PathBuf { } } +impl StaticColumnCount for uuid::Uuid { + fn column_count() -> usize { + 1 + } +} + +impl Bind for uuid::Uuid { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + self.as_bytes().bind(statement, start_index) + } +} + +impl Column for uuid::Uuid { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (bytes, next_index) = Column::column(statement, start_index)?; + Ok((uuid::Uuid::from_bytes(bytes), next_index)) + } +} + impl StaticColumnCount for () { fn column_count() -> usize { 0 diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 60680f82a2..fc069fe6c8 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -46,6 +46,7 @@ serde = { version = "1.0", features = ["derive", "rc"] } serde_json = { version = "1.0", features = ["preserve_order"] } smallvec = { version = "1.6", features = ["union"] } indoc = "1.0.4" +uuid = { version = "1.1.2", features = ["v4"] } [dev-dependencies] call = { path = "../call", features = ["test-support"] } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7a31d02433..1702c6e521 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -534,8 +534,8 @@ mod tests { }], }, left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; let fs = FakeFs::new(cx.background()); diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 7711ee5141..ddbea4c9f9 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -6,9 +6,10 @@ use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; -use gpui::{Axis, geometry::{rect::RectF, vector::Vector2F}}; +use gpui::{Axis, WindowBounds}; use util::{unzip_option, ResultExt}; +use uuid::Uuid; use crate::dock::DockPosition; use crate::WorkspaceId; @@ -29,11 +30,12 @@ define_connection! { // dock_pane: Option, // PaneId // left_sidebar_open: boolean, // timestamp: String, // UTC YYYY-MM-DD HH:MM:SS - // fullscreen bool, // Boolean - // window_x: f32, - // window_y: f32, - // window_width: f32, - // window_height: f32, + // window_state: String, // WindowBounds Discriminant + // window_x: Option, // WindowBounds::Fixed RectF x + // window_y: Option, // WindowBounds::Fixed RectF y + // window_width: Option, // WindowBounds::Fixed RectF width + // window_height: Option, // WindowBounds::Fixed RectF height + // display: Option, // Display id // ) // // pane_groups( @@ -76,7 +78,7 @@ define_connection! { timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) ) STRICT; - + CREATE TABLE pane_groups( group_id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, @@ -88,7 +90,7 @@ define_connection! { ON UPDATE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; - + CREATE TABLE panes( pane_id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, @@ -97,7 +99,7 @@ define_connection! { ON DELETE CASCADE ON UPDATE CASCADE ) STRICT; - + CREATE TABLE center_panes( pane_id INTEGER PRIMARY KEY, parent_group_id INTEGER, // NULL means that this is a root pane @@ -106,7 +108,7 @@ define_connection! { ON DELETE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; - + CREATE TABLE items( item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique workspace_id INTEGER NOT NULL, @@ -123,11 +125,12 @@ define_connection! { ) STRICT; ), sql!( - ALTER TABLE workspaces ADD COLUMN fullscreen INTEGER; // Boolean - ALTER TABLE workspaces ADD COLUMN window_x REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_y REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_width REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_height REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_state TEXT; + ALTER TABLE workspaces ADD COLUMN window_x REAL; + ALTER TABLE workspaces ADD COLUMN window_y REAL; + ALTER TABLE workspaces ADD COLUMN window_width REAL; + ALTER TABLE workspaces ADD COLUMN window_height REAL; + ALTER TABLE workspaces ADD COLUMN display BLOB; )]; } @@ -140,16 +143,16 @@ impl WorkspaceDb { worktree_roots: &[P], ) -> Option { let workspace_location: WorkspaceLocation = worktree_roots.into(); - + // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, dock_position, fullscreen, window_region): ( + let (workspace_id, workspace_location, left_sidebar_open, dock_position, bounds, display): ( WorkspaceId, WorkspaceLocation, bool, DockPosition, - bool, - Option<(f32, f32, f32, f32)> + Option, + Option, ) = self .select_row_bound(sql! { SELECT @@ -158,11 +161,12 @@ impl WorkspaceDb { left_sidebar_open, dock_visible, dock_anchor, - fullscreen, + window_state, window_x, window_y, window_width, - window_height + window_height, + display FROM workspaces WHERE workspace_location = ? }) @@ -170,7 +174,7 @@ impl WorkspaceDb { .context("No workspaces found") .warn_on_err() .flatten()?; - + Some(SerializedWorkspace { id: workspace_id, location: workspace_location.clone(), @@ -184,14 +188,11 @@ impl WorkspaceDb { .log_err()?, dock_position, left_sidebar_open, - fullscreen, - bounds: dbg!(window_region).map(|(x, y, width, height)| RectF::new( - Vector2F::new(x, y), - Vector2F::new(width, height) - )) + bounds, + display, }) } - + /// Saves a workspace using the worktree roots. Will garbage collect any workspaces /// that used this workspace previously pub async fn save_workspace(&self, workspace: SerializedWorkspace) { @@ -203,12 +204,12 @@ impl WorkspaceDb { DELETE FROM pane_groups WHERE workspace_id = ?1; DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id) .expect("Clearing old panes"); - + conn.exec_bound(sql!( DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ? ))?((&workspace.location, workspace.id.clone())) .context("clearing out old locations")?; - + // Upsert conn.exec_bound(sql!( INSERT INTO workspaces( @@ -222,11 +223,11 @@ impl WorkspaceDb { VALUES (?1, ?2, ?3, ?4, ?5, CURRENT_TIMESTAMP) ON CONFLICT DO UPDATE SET - workspace_location = ?2, - left_sidebar_open = ?3, - dock_visible = ?4, - dock_anchor = ?5, - timestamp = CURRENT_TIMESTAMP + workspace_location = ?2, + left_sidebar_open = ?3, + dock_visible = ?4, + dock_anchor = ?5, + timestamp = CURRENT_TIMESTAMP ))?(( workspace.id, &workspace.location, @@ -234,14 +235,14 @@ impl WorkspaceDb { workspace.dock_position, )) .context("Updating workspace")?; - + // Save center pane group and dock pane Self::save_pane_group(conn, workspace.id, &workspace.center_group, None) .context("save pane group in save workspace")?; - + let dock_id = Self::save_pane(conn, workspace.id, &workspace.dock_pane, None, true) .context("save pane in save workspace")?; - + // Complete workspace initialization conn.exec_bound(sql!( UPDATE workspaces @@ -249,20 +250,20 @@ impl WorkspaceDb { WHERE workspace_id = ? ))?((dock_id, workspace.id)) .context("Finishing initialization with dock pane")?; - + Ok(()) }) .log_err(); }) .await; } - + query! { pub async fn next_id() -> Result { INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id } } - + query! { fn recent_workspaces() -> Result> { SELECT workspace_id, workspace_location @@ -271,14 +272,14 @@ impl WorkspaceDb { ORDER BY timestamp DESC } } - + query! { async fn delete_stale_workspace(id: WorkspaceId) -> Result<()> { DELETE FROM workspaces WHERE workspace_id IS ? } } - + // Returns the recent locations which are still valid on disk and deletes ones which no longer // exist. pub async fn recent_workspaces_on_disk(&self) -> Result> { @@ -286,18 +287,18 @@ impl WorkspaceDb { let mut delete_tasks = Vec::new(); for (id, location) in self.recent_workspaces()? { if location.paths().iter().all(|path| path.exists()) - && location.paths().iter().any(|path| path.is_dir()) + && location.paths().iter().any(|path| path.is_dir()) { result.push((id, location)); } else { delete_tasks.push(self.delete_stale_workspace(id)); } } - + futures::future::join_all(delete_tasks).await; Ok(result) } - + pub async fn last_workspace(&self) -> Result> { Ok(self .recent_workspaces_on_disk() @@ -306,7 +307,7 @@ impl WorkspaceDb { .next() .map(|(_, location)| location)) } - + fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result { Ok(self .get_pane_group(workspace_id, None)? @@ -319,7 +320,7 @@ impl WorkspaceDb { }) })) } - + fn get_pane_group( &self, workspace_id: WorkspaceId, @@ -376,7 +377,7 @@ impl WorkspaceDb { }) .collect::>() } - + fn save_pane_group( conn: &Connection, workspace_id: WorkspaceId, @@ -386,18 +387,18 @@ impl WorkspaceDb { match pane_group { SerializedPaneGroup::Group { axis, children } => { let (parent_id, position) = unzip_option(parent); - + let group_id = conn.select_row_bound::<_, i64>(sql!( INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) VALUES (?, ?, ?, ?) RETURNING group_id ))?((workspace_id, parent_id, position, *axis))? .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; - + for (position, group) in children.iter().enumerate() { Self::save_pane_group(conn, workspace_id, group, Some((group_id, position)))? } - + Ok(()) } SerializedPaneGroup::Pane(pane) => { @@ -406,7 +407,7 @@ impl WorkspaceDb { } } } - + fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result { let (pane_id, active) = self.select_row_bound(sql!( SELECT pane_id, active @@ -414,13 +415,13 @@ impl WorkspaceDb { WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?) ))?(workspace_id)? .context("No dock pane for workspace")?; - + Ok(SerializedPane::new( self.get_items(pane_id).context("Reading items")?, active, )) } - + fn save_pane( conn: &Connection, workspace_id: WorkspaceId, @@ -434,7 +435,7 @@ impl WorkspaceDb { RETURNING pane_id ))?((workspace_id, pane.active))? .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?; - + if !dock { let (parent_id, order) = unzip_option(parent); conn.exec_bound(sql!( @@ -442,12 +443,12 @@ impl WorkspaceDb { VALUES (?, ?, ?) ))?((pane_id, parent_id, order))?; } - + Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?; - + Ok(pane_id) } - + fn get_items(&self, pane_id: PaneId) -> Result> { Ok(self.select_bound(sql!( SELECT kind, item_id, active FROM items @@ -455,7 +456,7 @@ impl WorkspaceDb { ORDER BY position ))?(pane_id)?) } - + fn save_items( conn: &Connection, workspace_id: WorkspaceId, @@ -468,10 +469,10 @@ impl WorkspaceDb { for (position, item) in items.iter().enumerate() { insert((workspace_id, pane_id, position, item))?; } - + Ok(()) } - + query! { pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> { UPDATE workspaces @@ -479,15 +480,16 @@ impl WorkspaceDb { WHERE workspace_id = ? } } - + query! { - pub async fn set_bounds(workspace_id: WorkspaceId, fullscreen: bool, bounds: Option<(f32, f32, f32, f32)>) -> Result<()> { + pub async fn set_window_bounds(workspace_id: WorkspaceId, bounds: WindowBounds, display: Uuid) -> Result<()> { UPDATE workspaces - SET fullscreen = ?2, + SET window_state = ?2, window_x = ?3, window_y = ?4, window_width = ?5, - window_height = ?6 + window_height = ?6, + display = ?7 WHERE workspace_id = ?1 } } @@ -495,20 +497,20 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - + use std::sync::Arc; - + use db::open_test_db; use settings::DockAnchor; - + use super::*; - + #[gpui::test] async fn test_next_id_stability() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_next_id_stability").await); - + db.write(|conn| { conn.migrate( "test_table", @@ -524,7 +526,7 @@ mod tests { .unwrap(); }) .await; - + let id = db.next_id().await.unwrap(); // Assert the empty row got inserted assert_eq!( @@ -535,28 +537,28 @@ mod tests { .unwrap()(id) .unwrap() ); - + db.write(move |conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-1", id)) - .unwrap() + .unwrap() }) .await; - + let test_text_1 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(1) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_1, "test-text-1"); } - + #[gpui::test] async fn test_workspace_id_stability() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await); - + db.write(|conn| { conn.migrate( "test_table", @@ -572,7 +574,7 @@ mod tests { }) .await .unwrap(); - + let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), @@ -580,10 +582,10 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), @@ -591,60 +593,60 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace_1.clone()).await; - + db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-1", 1)) - .unwrap(); + .unwrap(); }) .await; - + db.save_workspace(workspace_2.clone()).await; - + db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-2", 2)) - .unwrap(); + .unwrap(); }) .await; - + workspace_1.location = (["/tmp", "/tmp3"]).into(); db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_1).await; - + workspace_2.dock_pane.children.push(SerializedItem { kind: Arc::from("Test"), item_id: 10, active: true, }); db.save_workspace(workspace_2).await; - + let test_text_2 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(2) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_2, "test-text-2"); - + let test_text_1 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(1) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_1, "test-text-1"); } - + #[gpui::test] async fn test_full_workspace_serialization() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await); - + let dock_pane = crate::persistence::model::SerializedPane { children: vec![ SerializedItem::new("Terminal", 1, false), @@ -654,7 +656,7 @@ mod tests { ], active: false, }; - + // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -691,7 +693,7 @@ mod tests { )), ], }; - + let workspace = SerializedWorkspace { id: 5, location: (["/tmp", "/tmp2"]).into(), @@ -699,29 +701,29 @@ mod tests { center_group, dock_pane, left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace.clone()).await; let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]); - + assert_eq!(workspace, round_trip_workspace.unwrap()); - + // Test guaranteed duplicate IDs db.save_workspace(workspace.clone()).await; db.save_workspace(workspace.clone()).await; - + let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]); assert_eq!(workspace, round_trip_workspace.unwrap()); } - + #[gpui::test] async fn test_workspace_assignment() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_basic_functionality").await); - + let workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), @@ -729,10 +731,10 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), @@ -740,13 +742,13 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_2.clone()).await; - + // Test that paths are treated as a set assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), @@ -756,20 +758,20 @@ mod tests { db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(), workspace_1 ); - + // Make sure that other keys work assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2); assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None); - + // Test 'mutate' case of updating a pre-existing id workspace_2.location = (["/tmp", "/tmp2"]).into(); - + db.save_workspace(workspace_2.clone()).await; assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_2 ); - + // Test other mechanism for mutating let mut workspace_3 = SerializedWorkspace { id: 3, @@ -778,16 +780,16 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace_3.clone()).await; assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_3 ); - + // Make sure that updating paths differently also works workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into(); db.save_workspace(workspace_3.clone()).await; @@ -798,11 +800,11 @@ mod tests { workspace_3 ); } - + use crate::dock::DockPosition; use crate::persistence::model::SerializedWorkspace; use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup}; - + fn default_workspace>( workspace_id: &[P], dock_pane: SerializedPane, @@ -815,17 +817,17 @@ mod tests { center_group: center_group.clone(), dock_pane, left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), } } - + #[gpui::test] async fn test_basic_dock_pane() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("basic_dock_pane").await); - + let dock_pane = crate::persistence::model::SerializedPane::new( vec![ SerializedItem::new("Terminal", 1, false), @@ -835,22 +837,22 @@ mod tests { ], false, ); - + let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default()); - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - + assert_eq!(workspace.dock_pane, new_workspace.dock_pane); } - + #[gpui::test] async fn test_simple_split() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("simple_split").await); - + // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -887,22 +889,22 @@ mod tests { )), ], }; - + let workspace = default_workspace(&["/tmp"], Default::default(), ¢er_pane); - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - + assert_eq!(workspace.center_group, new_workspace.center_group); } - + #[gpui::test] async fn test_cleanup_panes() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_cleanup_panes").await); - + let center_pane = SerializedPaneGroup::Group { axis: gpui::Axis::Horizontal, children: vec![ @@ -934,13 +936,13 @@ mod tests { )), ], }; - + let id = &["/tmp"]; - + let mut workspace = default_workspace(id, Default::default(), ¢er_pane); - + db.save_workspace(workspace.clone()).await; - + workspace.center_group = SerializedPaneGroup::Group { axis: gpui::Axis::Vertical, children: vec![ @@ -960,11 +962,11 @@ mod tests { )), ], }; - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(id).unwrap(); - + assert_eq!(workspace.center_group, new_workspace.center_group); } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index f6236ced79..507582b216 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -6,9 +6,7 @@ use std::{ use anyhow::{Context, Result}; use async_recursion::async_recursion; -use gpui::{ - geometry::rect::RectF, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WindowBounds, -}; +use gpui::{AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WindowBounds}; use db::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, @@ -17,6 +15,7 @@ use db::sqlez::{ use project::Project; use settings::DockAnchor; use util::ResultExt; +use uuid::Uuid; use crate::{ dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, @@ -69,20 +68,8 @@ pub struct SerializedWorkspace { pub center_group: SerializedPaneGroup, pub dock_pane: SerializedPane, pub left_sidebar_open: bool, - pub fullscreen: bool, - pub bounds: Option, -} - -impl SerializedWorkspace { - pub fn bounds(&self) -> WindowBounds { - if self.fullscreen { - WindowBounds::Fullscreen - } else if let Some(bounds) = self.bounds { - WindowBounds::Fixed(bounds) - } else { - WindowBounds::Maximized - } - } + pub bounds: Option, + pub display: Option, } #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4c742cd7fa..4602e498f1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -37,8 +37,8 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, - MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, + MouseButton, MutableAppContext, PathPromptOptions, Platform, PromptLevel, RenderContext, + SizeConstraint, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -339,7 +339,8 @@ pub struct AppState { pub client: Arc, pub user_store: ModelHandle, pub fs: Arc, - pub build_window_options: fn(Option) -> WindowOptions<'static>, + pub build_window_options: + fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), pub dock_default_item_factory: DockDefaultItemFactory, } @@ -366,7 +367,7 @@ impl AppState { languages, user_store, initialize_workspace: |_, _, _| {}, - build_window_options: |_| Default::default(), + build_window_options: |_, _, _| Default::default(), dock_default_item_factory: |_, _| unimplemented!(), }) } @@ -681,11 +682,14 @@ impl Workspace { DB.next_id().await.unwrap_or(0) }; + let (bounds, display) = dbg!(serialized_workspace + .as_ref() + .and_then(|sw| sw.bounds.zip(sw.display)) + .unzip()); + // Use the serialized workspace to construct the new window let (_, workspace) = cx.add_window( - (app_state.build_window_options)(dbg!(serialized_workspace - .as_ref() - .map(|sw| sw.bounds()))), + (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), |cx| { let mut workspace = Workspace::new( serialized_workspace, @@ -695,20 +699,9 @@ impl Workspace { cx, ); (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - cx.observe_window_bounds(move |_, bounds, cx| { - let fullscreen = cx.window_is_fullscreen(cx.window_id()); - let bounds = if let WindowBounds::Fixed(region) = bounds { - Some(( - region.min_x(), - region.min_y(), - region.width(), - region.height(), - )) - } else { - None - }; + cx.observe_window_bounds(move |_, bounds, display, cx| { cx.background() - .spawn(DB.set_bounds(workspace_id, fullscreen, bounds)) + .spawn(DB.set_window_bounds(workspace_id, dbg!(bounds), dbg!(display))) .detach_and_log_err(cx); }) .detach(); @@ -2349,8 +2342,8 @@ impl Workspace { dock_pane, center_group, left_sidebar_open: self.left_sidebar.read(cx).is_open(), - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; cx.background() diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c2b15c7cb5..40861a8f26 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -111,6 +111,7 @@ tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} url = "2.2" urlencoding = "2.1.2" +uuid = { version = "1.1.2", features = ["v4"] } [dev-dependencies] call = { path = "../call", features = ["test-support"] } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3529e1c6dc..879cc441c8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -20,7 +20,8 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, PromptLevel, TitlebarOptions, ViewContext, WindowKind, + AssetSource, AsyncAppContext, ClipboardItem, Platform, PromptLevel, TitlebarOptions, + ViewContext, WindowKind, }; use language::Rope; use lazy_static::lazy_static; @@ -33,6 +34,7 @@ use serde_json::to_string_pretty; use settings::{keymap_file_json_schema, settings_file_json_schema, Settings}; use std::{borrow::Cow, env, path::Path, str, sync::Arc}; use util::{channel::ReleaseChannel, paths, ResultExt}; +use uuid::Uuid; pub use workspace; use workspace::{sidebar::SidebarSide, AppState, Workspace}; @@ -369,7 +371,11 @@ pub fn initialize_workspace( }); } -pub fn build_window_options(bounds: Option) -> WindowOptions<'static> { +pub fn build_window_options( + bounds: Option, + display: Option, + platform: &dyn Platform, +) -> WindowOptions<'static> { let bounds = bounds .or_else(|| { ZED_WINDOW_POSITION @@ -378,8 +384,9 @@ pub fn build_window_options(bounds: Option) -> WindowOptions<'stat }) .unwrap_or(WindowBounds::Maximized); + let screen = display.and_then(|display| platform.screen_by_id(display)); + WindowOptions { - bounds, titlebar: Some(TitlebarOptions { title: None, appears_transparent: true, @@ -389,7 +396,8 @@ pub fn build_window_options(bounds: Option) -> WindowOptions<'stat focus: true, kind: WindowKind::Normal, is_movable: true, - screen: None, + bounds, + screen, } } From a79b4e312baa586b13109ac09831f40f2dc89b04 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 25 Jan 2023 15:09:45 -0500 Subject: [PATCH 033/180] Style Co-Authored-By: Max Brunsfeld --- crates/live_kit_client/src/prod.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 47fd4f0b69..f45667e3c3 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -128,14 +128,9 @@ impl Room { let url = url.to_string(); let token = token.to_string(); async move { - match rx.await.unwrap().context("error connecting to room") { - Ok(()) => { - *this.connection.lock().0.borrow_mut() = - ConnectionState::Connected { url, token }; - Ok(()) - } - Err(err) => Err(err), - } + rx.await.unwrap().context("error connecting to room")?; + *this.connection.lock().0.borrow_mut() = ConnectionState::Connected { url, token }; + Ok(()) } } From 4c3244b98267a7a90286a3e90c662c4f8251ceb2 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 25 Jan 2023 15:20:41 -0500 Subject: [PATCH 034/180] v0.72.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f858a61aaa..fec8e8dc50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8216,7 +8216,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.71.0" +version = "0.72.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c2b15c7cb5..c82e778d8d 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.71.0" +version = "0.72.0" publish = false [lib] From 6e7101ca6bc4eb270c2ef80d54ca897402d8ce11 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 25 Jan 2023 17:47:15 -0500 Subject: [PATCH 035/180] Fix crash when opening feedback while in call --- crates/feedback/src/feedback.rs | 6 ++-- crates/feedback/src/feedback_editor.rs | 46 +++++++++++++++++--------- crates/zed/src/main.rs | 2 +- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 4b0dfc4df9..f47f95d4f3 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -5,7 +5,7 @@ mod system_specs; use gpui::{actions, impl_actions, ClipboardItem, ViewContext}; use serde::Deserialize; use system_specs::SystemSpecs; -use workspace::Workspace; +use workspace::{AppState, Workspace}; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -19,8 +19,8 @@ actions!( [CopySystemSpecsIntoClipboard, FileBugReport, RequestFeature,] ); -pub fn init(cx: &mut gpui::MutableAppContext) { - feedback_editor::init(cx); +pub fn init(app_state: Arc, cx: &mut gpui::MutableAppContext) { + feedback_editor::init(app_state, cx); cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index e034da7db2..e57b81a15c 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -26,7 +26,7 @@ use settings::Settings; use workspace::{ item::{Item, ItemHandle}, searchable::{SearchableItem, SearchableItemHandle}, - StatusItemView, Workspace, + AppState, StatusItemView, Workspace, }; use crate::system_specs::SystemSpecs; @@ -43,8 +43,12 @@ const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = actions!(feedback, [SubmitFeedback, GiveFeedback, DeployFeedback]); -pub fn init(cx: &mut MutableAppContext) { - cx.add_action(FeedbackEditor::deploy); +pub fn init(app_state: Arc, cx: &mut MutableAppContext) { + cx.add_action({ + move |workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext| { + FeedbackEditor::deploy(workspace, app_state.clone(), cx); + } + }); } pub struct FeedbackButton; @@ -116,15 +120,11 @@ impl FeedbackEditor { Self { editor, project } } - fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { - let markdown_language = project.read(cx).languages().language_for_name("Markdown"); - - let buffer = project - .update(cx, |project, cx| { - project.create_buffer("", markdown_language, cx) - }) - .expect("creating buffers on a local workspace always succeeds"); - + fn new( + project: ModelHandle, + buffer: ModelHandle, + cx: &mut ViewContext, + ) -> Self { Self::new_with_buffer(project, buffer, cx) } @@ -236,10 +236,24 @@ impl FeedbackEditor { } impl FeedbackEditor { - pub fn deploy(workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext) { - let feedback_editor = - cx.add_view(|cx| FeedbackEditor::new(workspace.project().clone(), cx)); - workspace.add_item(Box::new(feedback_editor), cx); + pub fn deploy( + workspace: &mut Workspace, + app_state: Arc, + cx: &mut ViewContext, + ) { + workspace + .with_local_workspace(&app_state, cx, |workspace, cx| { + let project = workspace.project().clone(); + let markdown_language = project.read(cx).languages().language_for_name("Markdown"); + let buffer = project + .update(cx, |project, cx| { + project.create_buffer("", markdown_language, cx) + }) + .expect("creating buffers on a local workspace always succeeds"); + let feedback_editor = cx.add_view(|cx| FeedbackEditor::new(project, buffer, cx)); + workspace.add_item(Box::new(feedback_editor), cx); + }) + .detach(); } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 79183f3128..3cd01a18a1 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -136,7 +136,6 @@ fn main() { client::init(client.clone(), cx); command_palette::init(cx); editor::init(cx); - feedback::init(cx); go_to_line::init(cx); file_finder::init(cx); outline::init(cx); @@ -191,6 +190,7 @@ fn main() { theme_selector::init(app_state.clone(), cx); zed::init(&app_state, cx); collab_ui::init(app_state.clone(), cx); + feedback::init(app_state.clone(), cx); cx.set_menus(menus::menus()); From 3819a67185dc07a86a5e734a88b9236adf003294 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 25 Jan 2023 18:51:25 -0500 Subject: [PATCH 036/180] Add "Close Window" global action which does not need a focused workspace --- crates/gpui/src/app.rs | 2 +- crates/workspace/src/workspace.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 2e7378fc16..3fc11a6b58 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -875,7 +875,7 @@ impl MutableAppContext { } pub fn window_ids(&self) -> impl Iterator + '_ { - self.cx.windows.keys().cloned() + self.cx.windows.keys().copied() } pub fn activate_window(&self, window_id: usize) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b0abe09070..b90c62aca1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -198,6 +198,7 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { cx.add_async_action(Workspace::toggle_follow); cx.add_async_action(Workspace::follow_next_collaborator); cx.add_async_action(Workspace::close); + cx.add_global_action(Workspace::close_global); cx.add_async_action(Workspace::save_all); cx.add_action(Workspace::open_shared_screen); cx.add_action(Workspace::add_folder_to_project); @@ -823,6 +824,15 @@ impl Workspace { } } + pub fn close_global(_: &CloseWindow, cx: &mut MutableAppContext) { + let id = cx.window_ids().find(|&id| cx.window_is_active(id)); + if let Some(id) = id { + //This can only get called when the window's project connection has been lost + //so we don't need to prompt the user for anything and instead just close the window + cx.remove_window(id); + } + } + pub fn close( &mut self, _: &CloseWindow, @@ -851,6 +861,7 @@ impl Workspace { .window_ids() .flat_map(|window_id| cx.root_view::(window_id)) .count(); + cx.spawn(|this, mut cx| async move { if let Some(active_call) = active_call { if !quitting @@ -866,6 +877,7 @@ impl Workspace { ) .next() .await; + if answer == Some(1) { return anyhow::Ok(false); } else { From 9ff34bcb6a74e146c2d6d2ab566ee00f28793f04 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 25 Jan 2023 20:03:44 -0500 Subject: [PATCH 037/180] Remove no-longer-needed method --- crates/feedback/src/feedback_editor.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index e57b81a15c..a4c10d8fc2 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -102,7 +102,7 @@ struct FeedbackEditor { } impl FeedbackEditor { - fn new_with_buffer( + fn new( project: ModelHandle, buffer: ModelHandle, cx: &mut ViewContext, @@ -120,14 +120,6 @@ impl FeedbackEditor { Self { editor, project } } - fn new( - project: ModelHandle, - buffer: ModelHandle, - cx: &mut ViewContext, - ) -> Self { - Self::new_with_buffer(project, buffer, cx) - } - fn handle_save( &mut self, _: ModelHandle, @@ -348,11 +340,7 @@ impl Item for FeedbackEditor { .as_singleton() .expect("Feedback buffer is only ever singleton"); - Some(Self::new_with_buffer( - self.project.clone(), - buffer.clone(), - cx, - )) + Some(Self::new(self.project.clone(), buffer.clone(), cx)) } fn serialized_item_kind() -> Option<&'static str> { From a369fb8033d700e0b897bcd4a67423bab098813b Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 25 Jan 2023 17:05:57 -0800 Subject: [PATCH 038/180] better but still broken --- .../src/incoming_call_notification.rs | 5 +- .../src/project_shared_notification.rs | 4 +- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/mac.rs | 61 +++- crates/gpui/src/platform/mac/geometry.rs | 104 +++++- crates/gpui/src/platform/mac/screen.rs | 26 +- crates/gpui/src/platform/mac/window.rs | 321 +++++++----------- crates/gpui/src/platform/test.rs | 4 +- crates/zed/src/zed.rs | 3 +- 9 files changed, 294 insertions(+), 236 deletions(-) diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index 6ad533665e..5d888bc093 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -32,11 +32,12 @@ pub fn init(cx: &mut MutableAppContext) { }); for screen in cx.platform().screens() { - let screen_size = screen.size(); + let screen_bounds = screen.bounds(); let (window_id, _) = cx.add_window( WindowOptions { bounds: WindowBounds::Fixed(RectF::new( - vec2f(screen_size.x() - window_size.x() - PADDING, PADDING), + screen_bounds.upper_right() + - vec2f(PADDING + window_size.x(), PADDING), window_size, )), titlebar: None, diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 0815d9c8d8..8488f3381e 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -31,11 +31,11 @@ pub fn init(cx: &mut MutableAppContext) { let window_size = vec2f(theme.window_width, theme.window_height); for screen in cx.platform().screens() { - let screen_size = screen.size(); + let screen_bounds = screen.bounds(); let (window_id, _) = cx.add_window( WindowOptions { bounds: WindowBounds::Fixed(RectF::new( - vec2f(screen_size.x() - window_size.x() - PADDING, PADDING), + screen_bounds.upper_right() - vec2f(PADDING + window_size.x(), PADDING), window_size, )), titlebar: None, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index e91c1e87aa..d1aaed7f47 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -123,7 +123,7 @@ pub trait InputHandler { pub trait Screen: Debug { fn as_any(&self) -> &dyn Any; - fn size(&self) -> Vector2F; + fn bounds(&self) -> RectF; fn display_uuid(&self) -> Uuid; } diff --git a/crates/gpui/src/platform/mac.rs b/crates/gpui/src/platform/mac.rs index 7eb080083e..9e3f104055 100644 --- a/crates/gpui/src/platform/mac.rs +++ b/crates/gpui/src/platform/mac.rs @@ -12,12 +12,15 @@ mod sprite_cache; mod status_item; mod window; -use cocoa::base::{BOOL, NO, YES}; +use cocoa::{ + base::{id, nil, BOOL, NO, YES}, + foundation::{NSAutoreleasePool, NSNotFound, NSString, NSUInteger}, +}; pub use dispatcher::Dispatcher; pub use fonts::FontSystem; use platform::{MacForegroundPlatform, MacPlatform}; pub use renderer::Surface; -use std::{rc::Rc, sync::Arc}; +use std::{ops::Range, rc::Rc, sync::Arc}; use window::Window; pub(crate) fn platform() -> Arc { @@ -41,3 +44,57 @@ impl BoolExt for bool { } } } + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +struct NSRange { + pub location: NSUInteger, + pub length: NSUInteger, +} + +impl NSRange { + fn invalid() -> Self { + Self { + location: NSNotFound as NSUInteger, + length: 0, + } + } + + fn is_valid(&self) -> bool { + self.location != NSNotFound as NSUInteger + } + + fn to_range(self) -> Option> { + if self.is_valid() { + let start = self.location as usize; + let end = start + self.length as usize; + Some(start..end) + } else { + None + } + } +} + +impl From> for NSRange { + fn from(range: Range) -> Self { + NSRange { + location: range.start as NSUInteger, + length: range.len() as NSUInteger, + } + } +} + +unsafe impl objc::Encode for NSRange { + fn encode() -> objc::Encoding { + let encoding = format!( + "{{NSRange={}{}}}", + NSUInteger::encode().as_str(), + NSUInteger::encode().as_str() + ); + unsafe { objc::Encoding::from_str(&encoding) } + } +} + +unsafe fn ns_string(string: &str) -> id { + NSString::alloc(nil).init_str(string).autorelease() +} diff --git a/crates/gpui/src/platform/mac/geometry.rs b/crates/gpui/src/platform/mac/geometry.rs index 89da409dbd..a59d72a796 100644 --- a/crates/gpui/src/platform/mac/geometry.rs +++ b/crates/gpui/src/platform/mac/geometry.rs @@ -1,27 +1,99 @@ -use cocoa::foundation::{NSPoint, NSRect, NSSize}; -use pathfinder_geometry::{rect::RectF, vector::Vector2F}; +use cocoa::{ + appkit::NSWindow, + base::id, + foundation::{NSPoint, NSRect, NSSize}, +}; +use objc::{msg_send, sel, sel_impl}; +use pathfinder_geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, +}; + +///! Macos screen have a y axis that goings up from the bottom of the screen and +///! an origin at the bottom left of the main display. pub trait Vector2FExt { - fn to_ns_point(&self) -> NSPoint; - fn to_ns_size(&self) -> NSSize; + /// Converts self to an NSPoint with y axis pointing up. + fn to_screen_ns_point(&self, native_window: id) -> NSPoint; +} +impl Vector2FExt for Vector2F { + fn to_screen_ns_point(&self, native_window: id) -> NSPoint { + unsafe { + let point = NSPoint::new(self.x() as f64, -self.y() as f64); + msg_send![native_window, convertPointToScreen: point] + } + } } pub trait RectFExt { + /// Converts self to an NSRect with y axis pointing up. + /// The resulting NSRect will have an origin at the bottom left of the rectangle. + /// Also takes care of converting from window scaled coordinates to screen coordinates + fn to_screen_ns_rect(&self, native_window: id) -> NSRect; + + /// Converts self to an NSRect with y axis point up. + /// The resulting NSRect will have an origin at the bottom left of the rectangle. + /// Unlike to_screen_ns_rect, coordinates are not converted and are assumed to already be in screen scale fn to_ns_rect(&self) -> NSRect; } - -impl Vector2FExt for Vector2F { - fn to_ns_point(&self) -> NSPoint { - NSPoint::new(self.x() as f64, self.y() as f64) - } - - fn to_ns_size(&self) -> NSSize { - NSSize::new(self.x() as f64, self.y() as f64) - } -} - impl RectFExt for RectF { + fn to_screen_ns_rect(&self, native_window: id) -> NSRect { + unsafe { native_window.convertRectToScreen_(self.to_ns_rect()) } + } + fn to_ns_rect(&self) -> NSRect { - NSRect::new(self.origin().to_ns_point(), self.size().to_ns_size()) + dbg!(&self); + NSRect::new( + NSPoint::new( + dbg!(self.origin_x() as f64), + dbg!(-(self.origin_y() - self.height()) as f64), + ), + NSSize::new(self.width() as f64, self.height() as f64), + ) + } +} + +pub trait NSPointExt { + /// Converts self to a Vector2F with y axis pointing down. + /// Also takes care of converting from window scaled coordinates to screen coordinates + fn to_window_vector2f(&self, native_window: id) -> Vector2F; +} +impl NSPointExt for NSPoint { + fn to_window_vector2f(&self, native_window: id) -> Vector2F { + unsafe { + let point: NSPoint = msg_send![native_window, convertPointFromScreen: self]; + vec2f(point.x as f32, -point.y as f32) + } + } +} + +pub trait NSRectExt { + /// Converts self to a RectF with y axis pointing down. + /// The resulting RectF will have an origin at the top left of the rectangle. + /// Also takes care of converting from screen scale coordinates to window coordinates + fn to_window_rectf(&self, native_window: id) -> RectF; + + /// Converts self to a RectF with y axis pointing down. + /// The resulting RectF will have an origin at the top left of the rectangle. + /// Unlike to_screen_ns_rect, coordinates are not converted and are assumed to already be in screen scale + fn to_rectf(&self) -> RectF; +} +impl NSRectExt for NSRect { + fn to_window_rectf(&self, native_window: id) -> RectF { + unsafe { + dbg!(self.origin.x); + let rect: NSRect = native_window.convertRectFromScreen_(*self); + rect.to_rectf() + } + } + + fn to_rectf(&self) -> RectF { + RectF::new( + vec2f( + dbg!(self.origin.x as f32), + dbg!(-(self.origin.y - self.size.height) as f32), + ), + vec2f(self.size.width as f32, self.size.height as f32), + ) } } diff --git a/crates/gpui/src/platform/mac/screen.rs b/crates/gpui/src/platform/mac/screen.rs index a54ffb3f90..27fb4b12d1 100644 --- a/crates/gpui/src/platform/mac/screen.rs +++ b/crates/gpui/src/platform/mac/screen.rs @@ -1,21 +1,21 @@ use std::{any::Any, ffi::c_void}; -use crate::{ - geometry::vector::{vec2f, Vector2F}, - platform, -}; +use crate::platform; use cocoa::{ appkit::NSScreen, base::{id, nil}, - foundation::{NSArray, NSDictionary, NSString}, + foundation::{NSArray, NSDictionary}, }; use core_foundation::{ number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef}, uuid::{CFUUIDGetUUIDBytes, CFUUIDRef}, }; use core_graphics::display::CGDirectDisplayID; +use pathfinder_geometry::rect::RectF; use uuid::Uuid; +use super::{geometry::NSRectExt, ns_string}; + #[link(name = "ApplicationServices", kind = "framework")] extern "C" { pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; @@ -58,13 +58,6 @@ impl platform::Screen for Screen { self } - fn size(&self) -> Vector2F { - unsafe { - let frame = self.native_screen.frame(); - vec2f(frame.size.width as f32, frame.size.height as f32) - } - } - fn display_uuid(&self) -> uuid::Uuid { unsafe { // Screen ids are not stable. Further, the default device id is also unstable across restarts. @@ -72,7 +65,7 @@ impl platform::Screen for Screen { // This approach is similar to that which winit takes // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99 let device_description = self.native_screen.deviceDescription(); - let key = NSString::alloc(nil).init_str("NSScreenNumber"); + let key = ns_string("NSScreenNumber"); let device_id_obj = device_description.objectForKey_(key); let mut device_id: u32 = 0; CFNumberGetValue( @@ -102,4 +95,11 @@ impl platform::Screen for Screen { ]) } } + + fn bounds(&self) -> RectF { + unsafe { + let frame = self.native_screen.frame(); + frame.to_rectf() + } + } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index e981cc6131..ecea0604f5 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -17,14 +17,12 @@ use crate::{ use block::ConcreteBlock; use cocoa::{ appkit::{ - CGFloat, CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, - NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, - NSWindowCollectionBehavior, NSWindowStyleMask, + CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, + NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior, + NSWindowStyleMask, }, base::{id, nil}, - foundation::{ - NSAutoreleasePool, NSInteger, NSNotFound, NSPoint, NSRect, NSSize, NSString, NSUInteger, - }, + foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger}, }; use core_graphics::display::CGRect; use ctor::ctor; @@ -52,6 +50,11 @@ use std::{ time::Duration, }; +use super::{ + geometry::{NSRectExt, Vector2FExt}, + ns_string, NSRange, +}; + const WINDOW_STATE_IVAR: &str = "windowState"; static mut WINDOW_CLASS: *const Class = ptr::null(); @@ -76,56 +79,6 @@ const NSTrackingInVisibleRect: NSUInteger = 0x200; #[allow(non_upper_case_globals)] const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4; -#[repr(C)] -#[derive(Copy, Clone, Debug)] -struct NSRange { - pub location: NSUInteger, - pub length: NSUInteger, -} - -impl NSRange { - fn invalid() -> Self { - Self { - location: NSNotFound as NSUInteger, - length: 0, - } - } - - fn is_valid(&self) -> bool { - self.location != NSNotFound as NSUInteger - } - - fn to_range(self) -> Option> { - if self.is_valid() { - let start = self.location as usize; - let end = start + self.length as usize; - Some(start..end) - } else { - None - } - } -} - -impl From> for NSRange { - fn from(range: Range) -> Self { - NSRange { - location: range.start as NSUInteger, - length: range.len() as NSUInteger, - } - } -} - -unsafe impl objc::Encode for NSRange { - fn encode() -> objc::Encoding { - let encoding = format!( - "{{NSRange={}{}}}", - NSUInteger::encode().as_str(), - NSUInteger::encode().as_str() - ); - unsafe { objc::Encoding::from_str(&encoding) } - } -} - #[ctor] unsafe fn build_classes() { WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow)); @@ -315,8 +268,6 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C decl.register() } -pub struct Window(Rc>); - ///Used to track what the IME does when we send it a keystroke. ///This is only used to handle the case where the IME mysteriously ///swallows certain keys. @@ -329,6 +280,11 @@ enum ImeState { None, } +struct InsertText { + replacement_range: Option>, + text: String, +} + struct WindowState { id: usize, native_window: id, @@ -357,11 +313,112 @@ struct WindowState { ime_text: Option, } -struct InsertText { - replacement_range: Option>, - text: String, +impl WindowState { + fn move_traffic_light(&self) { + if let Some(traffic_light_position) = self.traffic_light_position { + let titlebar_height = self.titlebar_height(); + + unsafe { + let close_button: id = msg_send![ + self.native_window, + standardWindowButton: NSWindowButton::NSWindowCloseButton + ]; + let min_button: id = msg_send![ + self.native_window, + standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton + ]; + let zoom_button: id = msg_send![ + self.native_window, + standardWindowButton: NSWindowButton::NSWindowZoomButton + ]; + + let mut close_button_frame: CGRect = msg_send![close_button, frame]; + let mut min_button_frame: CGRect = msg_send![min_button, frame]; + let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame]; + let mut origin = vec2f( + traffic_light_position.x(), + titlebar_height + - traffic_light_position.y() + - close_button_frame.size.height as f32, + ); + let button_spacing = + (min_button_frame.origin.x - close_button_frame.origin.x) as f32; + + close_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); + let _: () = msg_send![close_button, setFrame: close_button_frame]; + origin.set_x(origin.x() + button_spacing); + + min_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); + let _: () = msg_send![min_button, setFrame: min_button_frame]; + origin.set_x(origin.x() + button_spacing); + + zoom_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); + let _: () = msg_send![zoom_button, setFrame: zoom_button_frame]; + } + } + } + + fn is_fullscreen(&self) -> bool { + unsafe { + let style_mask = self.native_window.styleMask(); + style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask) + } + } + + fn bounds(&self) -> WindowBounds { + unsafe { + if self.is_fullscreen() { + return WindowBounds::Fullscreen; + } + + let screen_frame = self + .native_window + .screen() + .visibleFrame() + .to_window_rectf(self.native_window); + let window_frame = self.frame(); + + if screen_frame == window_frame { + WindowBounds::Maximized + } else { + WindowBounds::Fixed(window_frame) + } + } + } + + // Returns the window bounds in window coordinates + fn frame(&self) -> RectF { + unsafe { NSWindow::frame(self.native_window).to_window_rectf(self.native_window) } + } + + fn content_size(&self) -> Vector2F { + let NSSize { width, height, .. } = + unsafe { NSView::frame(self.native_window.contentView()) }.size; + vec2f(width as f32, height as f32) + } + + fn scale_factor(&self) -> f32 { + get_scale_factor(self.native_window) + } + + fn titlebar_height(&self) -> f32 { + unsafe { + let frame = NSWindow::frame(self.native_window); + let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect]; + (frame.size.height - content_layout_rect.size.height) as f32 + } + } + + fn present_scene(&mut self, scene: Scene) { + self.scene_to_render = Some(scene); + unsafe { + let _: () = msg_send![self.native_window.contentView(), setNeedsDisplay: YES]; + } + } } +pub struct Window(Rc>); + impl Window { pub fn open( id: usize, @@ -395,7 +452,7 @@ impl Window { } }; let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_( - RectF::new(Default::default(), vec2f(1024., 768.)).to_ns_rect(), + NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)), style_mask, NSBackingStoreBuffered, NO, @@ -416,20 +473,8 @@ impl Window { WindowBounds::Maximized => { native_window.setFrame_display_(screen.visibleFrame(), YES); } - WindowBounds::Fixed(top_left_bounds) => { - let frame = screen.visibleFrame(); - let bottom_left_bounds = RectF::new( - dbg!(vec2f( - top_left_bounds.origin_x(), - frame.size.height as f32 - - top_left_bounds.origin_y() - - top_left_bounds.height(), - )), - dbg!(top_left_bounds.size()), - ) - .to_ns_rect(); - let screen_rect = native_window.convertRectToScreen_(bottom_left_bounds); - native_window.setFrame_display_(screen_rect, YES); + WindowBounds::Fixed(rect) => { + native_window.setFrame_display_(rect.to_screen_ns_rect(native_window), YES); } } @@ -776,21 +821,16 @@ impl platform::Window for Window { } fn is_topmost_for_position(&self, position: Vector2F) -> bool { - let window_bounds = self.bounds(); let self_borrow = self.0.borrow(); let self_id = self_borrow.id; unsafe { + let window_frame = self_borrow.frame(); let app = NSApplication::sharedApplication(nil); - // Convert back to bottom-left coordinates - let point = NSPoint::new( - position.x() as CGFloat, - (window_bounds.height() - position.y()) as CGFloat, - ); - - let screen_point: NSPoint = - msg_send![self_borrow.native_window, convertPointToScreen: point]; + // Convert back to screen coordinates + let screen_point = + (position + window_frame.origin()).to_screen_ns_point(self_borrow.native_window); let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0]; let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number]; @@ -807,113 +847,6 @@ impl platform::Window for Window { } } -impl WindowState { - fn move_traffic_light(&self) { - if let Some(traffic_light_position) = self.traffic_light_position { - let titlebar_height = self.titlebar_height(); - - unsafe { - let close_button: id = msg_send![ - self.native_window, - standardWindowButton: NSWindowButton::NSWindowCloseButton - ]; - let min_button: id = msg_send![ - self.native_window, - standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton - ]; - let zoom_button: id = msg_send![ - self.native_window, - standardWindowButton: NSWindowButton::NSWindowZoomButton - ]; - - let mut close_button_frame: CGRect = msg_send![close_button, frame]; - let mut min_button_frame: CGRect = msg_send![min_button, frame]; - let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame]; - let mut origin = vec2f( - traffic_light_position.x(), - titlebar_height - - traffic_light_position.y() - - close_button_frame.size.height as f32, - ); - let button_spacing = - (min_button_frame.origin.x - close_button_frame.origin.x) as f32; - - close_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); - let _: () = msg_send![close_button, setFrame: close_button_frame]; - origin.set_x(origin.x() + button_spacing); - - min_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); - let _: () = msg_send![min_button, setFrame: min_button_frame]; - origin.set_x(origin.x() + button_spacing); - - zoom_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); - let _: () = msg_send![zoom_button, setFrame: zoom_button_frame]; - } - } - } - - fn is_fullscreen(&self) -> bool { - unsafe { - let style_mask = self.native_window.styleMask(); - style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask) - } - } - - fn bounds(&self) -> WindowBounds { - unsafe { - if self.is_fullscreen() { - return WindowBounds::Fullscreen; - } - - let screen_frame = self.native_window.screen().visibleFrame(); - let window_frame = NSWindow::frame(self.native_window); - let origin = vec2f( - window_frame.origin.x as f32, - (screen_frame.size.height - window_frame.origin.y - window_frame.size.height) - as f32, - ); - let size = vec2f( - window_frame.size.width as f32, - window_frame.size.height as f32, - ); - - if origin.is_zero() - && size.x() == screen_frame.size.width as f32 - && size.y() == screen_frame.size.height as f32 - { - WindowBounds::Maximized - } else { - WindowBounds::Fixed(RectF::new(origin, size)) - } - } - } - - fn content_size(&self) -> Vector2F { - let NSSize { width, height, .. } = - unsafe { NSView::frame(self.native_window.contentView()) }.size; - vec2f(width as f32, height as f32) - } - - fn scale_factor(&self) -> f32 { - get_scale_factor(self.native_window) - } - - fn titlebar_height(&self) -> f32 { - unsafe { - let frame = NSWindow::frame(self.native_window); - let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect]; - (frame.size.height - content_layout_rect.size.height) as f32 - } - } - - fn present_scene(&mut self, scene: Scene) { - self.scene_to_render = Some(scene); - unsafe { - let _: () = msg_send![self.native_window.contentView(), setNeedsDisplay: YES]; - } - } -} - fn get_scale_factor(native_window: id) -> f32 { unsafe { let screen: id = msg_send![native_window, screen]; @@ -1547,10 +1480,6 @@ async fn synthetic_drag( } } -unsafe fn ns_string(string: &str) -> id { - NSString::alloc(nil).init_str(string).autorelease() -} - fn with_input_handler(window: &Object, f: F) -> Option where F: FnOnce(&mut dyn InputHandler) -> R, diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 2c8a940969..6c8637948e 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -234,8 +234,8 @@ impl super::Screen for Screen { self } - fn size(&self) -> Vector2F { - Vector2F::new(1920., 1080.) + fn bounds(&self) -> RectF { + RectF::new(Vector2F::zero(), Vector2F::new(1920., 1080.)) } fn display_uuid(&self) -> uuid::Uuid { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 879cc441c8..a0a58a86fb 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -20,8 +20,7 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, ClipboardItem, Platform, PromptLevel, TitlebarOptions, - ViewContext, WindowKind, + AssetSource, AsyncAppContext, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind, }; use language::Rope; use lazy_static::lazy_static; From ddf4e1a3162edf7a03fbdba1cdd41504173756c1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 25 Jan 2023 17:42:49 -0800 Subject: [PATCH 039/180] Load languages lazily in the background --- crates/language/src/buffer_tests.rs | 20 +- crates/language/src/language.rs | 256 ++++++++++++++++++------- crates/language/src/syntax_map.rs | 26 +-- crates/project/src/project.rs | 8 +- crates/zed/src/languages.rs | 122 +++++------- crates/zed/src/languages/c.rs | 14 +- crates/zed/src/languages/go.rs | 5 +- crates/zed/src/languages/python.rs | 15 +- crates/zed/src/languages/rust.rs | 27 +-- crates/zed/src/languages/typescript.rs | 10 +- crates/zed/src/main.rs | 14 +- crates/zed/src/zed.rs | 4 +- 12 files changed, 311 insertions(+), 210 deletions(-) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 0b2ef1d7a7..59fd6a534b 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -51,7 +51,7 @@ fn test_line_endings(cx: &mut gpui::MutableAppContext) { #[gpui::test] fn test_select_language() { - let registry = LanguageRegistry::test(); + let registry = Arc::new(LanguageRegistry::test()); registry.add(Arc::new(Language::new( LanguageConfig { name: "Rust".into(), @@ -71,27 +71,33 @@ fn test_select_language() { // matching file extension assert_eq!( - registry.select_language("zed/lib.rs").map(|l| l.name()), + registry.language_for_path("zed/lib.rs").map(|l| l.name()), Some("Rust".into()) ); assert_eq!( - registry.select_language("zed/lib.mk").map(|l| l.name()), + registry.language_for_path("zed/lib.mk").map(|l| l.name()), Some("Make".into()) ); // matching filename assert_eq!( - registry.select_language("zed/Makefile").map(|l| l.name()), + registry.language_for_path("zed/Makefile").map(|l| l.name()), Some("Make".into()) ); // matching suffix that is not the full file extension or filename - assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None); assert_eq!( - registry.select_language("zed/a.cars").map(|l| l.name()), + registry.language_for_path("zed/cars").map(|l| l.name()), + None + ); + assert_eq!( + registry.language_for_path("zed/a.cars").map(|l| l.name()), + None + ); + assert_eq!( + registry.language_for_path("zed/sumk").map(|l| l.name()), None ); - assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None); } #[gpui::test] diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 6e1a120c81..4279ce6654 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -16,7 +16,7 @@ use futures::{ future::{BoxFuture, Shared}, FutureExt, TryFutureExt, }; -use gpui::{MutableAppContext, Task}; +use gpui::{executor::Background, MutableAppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; @@ -26,6 +26,7 @@ use serde::{de, Deserialize, Deserializer}; use serde_json::Value; use std::{ any::Any, + borrow::Cow, cell::RefCell, fmt::Debug, hash::Hash, @@ -89,8 +90,7 @@ pub struct CachedLspAdapter { } impl CachedLspAdapter { - pub async fn new(adapter: T) -> Arc { - let adapter = Box::new(adapter); + pub async fn new(adapter: Box) -> Arc { let name = adapter.name().await; let server_args = adapter.server_args().await; let initialization_options = adapter.initialization_options().await; @@ -248,6 +248,16 @@ pub struct LanguageConfig { pub overrides: HashMap, } +#[derive(Debug, Default)] +pub struct LanguageQueries { + pub highlights: Option>, + pub brackets: Option>, + pub indents: Option>, + pub outline: Option>, + pub injections: Option>, + pub overrides: Option>, +} + #[derive(Clone)] pub struct LanguageScope { language: Arc, @@ -407,8 +417,17 @@ pub enum LanguageServerBinaryStatus { Failed { error: String }, } +struct AvailableLanguage { + path: &'static str, + config: LanguageConfig, + grammar: tree_sitter::Language, + lsp_adapter: Option>, + get_queries: fn(&str) -> LanguageQueries, +} + pub struct LanguageRegistry { languages: RwLock>>, + available_languages: RwLock>, language_server_download_dir: Option>, lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc, LanguageServerBinaryStatus)>, @@ -422,6 +441,7 @@ pub struct LanguageRegistry { >, subscription: RwLock<(watch::Sender<()>, watch::Receiver<()>)>, theme: RwLock>>, + executor: Option>, version: AtomicUsize, } @@ -431,6 +451,7 @@ impl LanguageRegistry { Self { language_server_download_dir: None, languages: Default::default(), + available_languages: Default::default(), lsp_binary_statuses_tx, lsp_binary_statuses_rx, login_shell_env_loaded: login_shell_env_loaded.shared(), @@ -438,6 +459,7 @@ impl LanguageRegistry { subscription: RwLock::new(watch::channel()), theme: Default::default(), version: Default::default(), + executor: None, } } @@ -446,6 +468,44 @@ impl LanguageRegistry { Self::new(Task::ready(())) } + pub fn set_executor(&mut self, executor: Arc) { + self.executor = Some(executor); + } + + pub fn register( + &self, + path: &'static str, + config: LanguageConfig, + grammar: tree_sitter::Language, + lsp_adapter: Option>, + get_queries: fn(&str) -> LanguageQueries, + ) { + self.available_languages.write().push(AvailableLanguage { + path, + config, + grammar, + lsp_adapter, + get_queries, + }); + } + + pub fn language_names(&self) -> Vec { + let mut result = self + .available_languages + .read() + .iter() + .map(|l| l.config.name.to_string()) + .chain( + self.languages + .read() + .iter() + .map(|l| l.config.name.to_string()), + ) + .collect::>(); + result.sort_unstable(); + result + } + pub fn add(&self, language: Arc) { if let Some(theme) = self.theme.read().clone() { language.set_theme(&theme.editor.syntax); @@ -474,58 +534,79 @@ impl LanguageRegistry { self.language_server_download_dir = Some(path.into()); } - pub fn language_for_name(&self, name: &str) -> Option> { + pub fn language_for_name(self: &Arc, name: &str) -> Option> { let name = UniCase::new(name); - self.languages - .read() - .iter() - .find(|language| UniCase::new(language.name()) == name) - .cloned() + self.get_or_load_language(|config| UniCase::new(config.name.as_ref()) == name) } - pub fn language_for_extension(&self, extension: &str) -> Option> { - let extension = UniCase::new(extension); - self.languages - .read() - .iter() - .find(|language| { - language - .config + pub fn language_for_name_or_extension(self: &Arc, string: &str) -> Option> { + let string = UniCase::new(string); + self.get_or_load_language(|config| { + UniCase::new(config.name.as_ref()) == string + || config .path_suffixes .iter() - .any(|suffix| UniCase::new(suffix) == extension) - }) - .cloned() + .any(|suffix| UniCase::new(suffix) == string) + }) } - pub fn to_vec(&self) -> Vec> { - self.languages.read().iter().cloned().collect() - } - - pub fn language_names(&self) -> Vec { - self.languages - .read() - .iter() - .map(|language| language.name().to_string()) - .collect() - } - - pub fn select_language(&self, path: impl AsRef) -> Option> { + pub fn language_for_path(self: &Arc, path: impl AsRef) -> Option> { let path = path.as_ref(); let filename = path.file_name().and_then(|name| name.to_str()); let extension = path.extension().and_then(|name| name.to_str()); let path_suffixes = [extension, filename]; - self.languages + self.get_or_load_language(|config| { + config + .path_suffixes + .iter() + .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) + }) + } + + fn get_or_load_language( + self: &Arc, + callback: impl Fn(&LanguageConfig) -> bool, + ) -> Option> { + if let Some(language) = self + .languages .read() .iter() - .find(|language| { - language - .config - .path_suffixes - .iter() - .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) - }) - .cloned() + .find(|language| callback(&language.config)) + { + return Some(language.clone()); + } + + if let Some(executor) = self.executor.clone() { + let mut available_languages = self.available_languages.write(); + + if let Some(ix) = available_languages.iter().position(|l| callback(&l.config)) { + let language = available_languages.remove(ix); + drop(available_languages); + let name = language.config.name.clone(); + let this = self.clone(); + executor + .spawn(async move { + let queries = (language.get_queries)(&language.path); + let language = Language::new(language.config, Some(language.grammar)) + .with_lsp_adapter(language.lsp_adapter) + .await; + match language.with_queries(queries) { + Ok(language) => this.add(Arc::new(language)), + Err(err) => { + log::error!("failed to load language {}: {}", name, err); + return; + } + }; + }) + .detach(); + } + } + + None + } + + pub fn to_vec(&self) -> Vec> { + self.languages.read().iter().cloned().collect() } pub fn start_language_server( @@ -729,12 +810,70 @@ impl Language { self.grammar.as_ref().map(|g| g.id) } + pub fn with_queries(mut self, queries: LanguageQueries) -> Result { + if let Some(query) = queries.highlights { + self = self + .with_highlights_query(query.as_ref()) + .expect("failed to evaluate highlights query"); + } + if let Some(query) = queries.brackets { + self = self + .with_brackets_query(query.as_ref()) + .expect("failed to load brackets query"); + } + if let Some(query) = queries.indents { + self = self + .with_indents_query(query.as_ref()) + .expect("failed to load indents query"); + } + if let Some(query) = queries.outline { + self = self + .with_outline_query(query.as_ref()) + .expect("failed to load outline query"); + } + if let Some(query) = queries.injections { + self = self + .with_injection_query(query.as_ref()) + .expect("failed to load injection query"); + } + if let Some(query) = queries.overrides { + self = self + .with_override_query(query.as_ref()) + .expect("failed to load override query"); + } + Ok(self) + } pub fn with_highlights_query(mut self, source: &str) -> Result { let grammar = self.grammar_mut(); grammar.highlights_query = Some(Query::new(grammar.ts_language, source)?); Ok(self) } + pub fn with_outline_query(mut self, source: &str) -> Result { + let grammar = self.grammar_mut(); + let query = Query::new(grammar.ts_language, source)?; + let mut item_capture_ix = None; + let mut name_capture_ix = None; + let mut context_capture_ix = None; + get_capture_indices( + &query, + &mut [ + ("item", &mut item_capture_ix), + ("name", &mut name_capture_ix), + ("context", &mut context_capture_ix), + ], + ); + if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) { + grammar.outline_config = Some(OutlineConfig { + query, + item_capture_ix, + name_capture_ix, + context_capture_ix, + }); + } + Ok(self) + } + pub fn with_brackets_query(mut self, source: &str) -> Result { let grammar = self.grammar_mut(); let query = Query::new(grammar.ts_language, source)?; @@ -785,31 +924,6 @@ impl Language { Ok(self) } - pub fn with_outline_query(mut self, source: &str) -> Result { - let grammar = self.grammar_mut(); - let query = Query::new(grammar.ts_language, source)?; - let mut item_capture_ix = None; - let mut name_capture_ix = None; - let mut context_capture_ix = None; - get_capture_indices( - &query, - &mut [ - ("item", &mut item_capture_ix), - ("name", &mut name_capture_ix), - ("context", &mut context_capture_ix), - ], - ); - if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) { - grammar.outline_config = Some(OutlineConfig { - query, - item_capture_ix, - name_capture_ix, - context_capture_ix, - }); - } - Ok(self) - } - pub fn with_injection_query(mut self, source: &str) -> Result { let grammar = self.grammar_mut(); let query = Query::new(grammar.ts_language, source)?; @@ -882,8 +996,10 @@ impl Language { Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap() } - pub fn with_lsp_adapter(mut self, lsp_adapter: Arc) -> Self { - self.adapter = Some(lsp_adapter); + pub async fn with_lsp_adapter(mut self, lsp_adapter: Option>) -> Self { + if let Some(adapter) = lsp_adapter { + self.adapter = Some(CachedLspAdapter::new(adapter).await); + } self } @@ -894,7 +1010,7 @@ impl Language { ) -> mpsc::UnboundedReceiver { let (servers_tx, servers_rx) = mpsc::unbounded(); self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone())); - let adapter = CachedLspAdapter::new(fake_lsp_adapter).await; + let adapter = CachedLspAdapter::new(Box::new(fake_lsp_adapter)).await; self.adapter = Some(adapter); servers_rx } diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index ada981ec26..41966c7596 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -381,7 +381,12 @@ impl SyntaxSnapshot { cursor.next(text); while let Some(layer) = cursor.item() { let SyntaxLayerContent::Pending { language_name } = &layer.content else { unreachable!() }; - if language_for_injection(language_name, ®istry).is_some() { + if { + let language_registry = ®istry; + language_registry.language_for_name_or_extension(language_name) + } + .is_some() + { resolved_injection_ranges.push(layer.range.to_offset(text)); } @@ -1066,7 +1071,7 @@ fn get_injections( config: &InjectionConfig, text: &BufferSnapshot, node: Node, - language_registry: &LanguageRegistry, + language_registry: &Arc, depth: usize, changed_ranges: &[Range], combined_injection_ranges: &mut HashMap, Vec>, @@ -1078,7 +1083,8 @@ fn get_injections( combined_injection_ranges.clear(); for pattern in &config.patterns { if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) { - if let Some(language) = language_for_injection(language_name, language_registry) { + if let Some(language) = language_registry.language_for_name_or_extension(language_name) + { combined_injection_ranges.insert(language, Vec::new()); } } @@ -1123,7 +1129,10 @@ fn get_injections( }; if let Some(language_name) = language_name { - let language = language_for_injection(&language_name, language_registry); + let language = { + let language_name: &str = &language_name; + language_registry.language_for_name_or_extension(language_name) + }; let range = text.anchor_before(step_range.start)..text.anchor_after(step_range.end); if let Some(language) = language { if combined { @@ -1171,15 +1180,6 @@ fn get_injections( } } -fn language_for_injection( - language_name: &str, - language_registry: &LanguageRegistry, -) -> Option> { - language_registry - .language_for_name(language_name) - .or_else(|| language_registry.language_for_extension(language_name)) -} - fn splice_included_ranges( mut ranges: Vec, changed_ranges: &[Range], diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 34117cf39e..54939af8d8 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1802,7 +1802,7 @@ impl Project { ) -> Option<()> { // If the buffer has a language, set it and start the language server if we haven't already. let full_path = buffer.read(cx).file()?.full_path(cx); - let new_language = self.languages.select_language(&full_path)?; + let new_language = self.languages.language_for_path(&full_path)?; buffer.update(cx, |buffer, cx| { if buffer.language().map_or(true, |old_language| { !Arc::ptr_eq(old_language, &new_language) @@ -2211,7 +2211,7 @@ impl Project { }) .collect(); for (worktree_id, worktree_abs_path, full_path) in language_server_lookup_info { - let language = self.languages.select_language(&full_path)?; + let language = self.languages.language_for_path(&full_path)?; self.restart_language_server(worktree_id, worktree_abs_path, language, cx); } @@ -3171,7 +3171,7 @@ impl Project { let signature = this.symbol_signature(&project_path); let language = this .languages - .select_language(&project_path.path) + .language_for_path(&project_path.path) .unwrap_or(adapter_language.clone()); let language_server_name = adapter.name.clone(); Some(async move { @@ -5947,7 +5947,7 @@ impl Project { worktree_id, path: PathBuf::from(serialized_symbol.path).into(), }; - let language = languages.select_language(&path.path); + let language = languages.language_for_path(&path.path); Ok(Symbol { language_server_name: LanguageServerName( serialized_symbol.language_server_name.into(), diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 240d1dc49e..548c07fb82 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -1,7 +1,5 @@ use anyhow::Context; -use gpui::executor::Background; pub use language::*; -use lazy_static::lazy_static; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; @@ -32,32 +30,17 @@ mod typescript; #[exclude = "*.rs"] struct LanguageDir; -// TODO - Remove this once the `init` function is synchronous again. -lazy_static! { - pub static ref LANGUAGE_NAMES: Vec = LanguageDir::iter() - .filter_map(|path| { - if path.ends_with("config.toml") { - let config = LanguageDir::get(&path)?; - let config = toml::from_slice::(&config.data).ok()?; - Some(config.name.to_string()) - } else { - None - } - }) - .collect(); -} - -pub async fn init(languages: Arc, _executor: Arc) { +pub fn init(languages: Arc) { for (name, grammar, lsp_adapter) in [ ( "c", tree_sitter_c::language(), - Some(CachedLspAdapter::new(c::CLspAdapter).await), + Some(Box::new(c::CLspAdapter) as Box), ), ( "cpp", tree_sitter_cpp::language(), - Some(CachedLspAdapter::new(c::CLspAdapter).await), + Some(Box::new(c::CLspAdapter)), ), ( "css", @@ -67,17 +50,17 @@ pub async fn init(languages: Arc, _executor: Arc) ( "elixir", tree_sitter_elixir::language(), - Some(CachedLspAdapter::new(elixir::ElixirLspAdapter).await), + Some(Box::new(elixir::ElixirLspAdapter)), ), ( "go", tree_sitter_go::language(), - Some(CachedLspAdapter::new(go::GoLspAdapter).await), + Some(Box::new(go::GoLspAdapter)), ), ( "json", tree_sitter_json::language(), - Some(CachedLspAdapter::new(json::JsonLspAdapter).await), + Some(Box::new(json::JsonLspAdapter)), ), ( "markdown", @@ -87,12 +70,12 @@ pub async fn init(languages: Arc, _executor: Arc) ( "python", tree_sitter_python::language(), - Some(CachedLspAdapter::new(python::PythonLspAdapter).await), + Some(Box::new(python::PythonLspAdapter)), ), ( "rust", tree_sitter_rust::language(), - Some(CachedLspAdapter::new(rust::RustLspAdapter).await), + Some(Box::new(rust::RustLspAdapter)), ), ( "toml", @@ -102,89 +85,82 @@ pub async fn init(languages: Arc, _executor: Arc) ( "tsx", tree_sitter_typescript::language_tsx(), - Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await), + Some(Box::new(typescript::TypeScriptLspAdapter)), ), ( "typescript", tree_sitter_typescript::language_typescript(), - Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await), + Some(Box::new(typescript::TypeScriptLspAdapter)), ), ( "javascript", tree_sitter_typescript::language_tsx(), - Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await), + Some(Box::new(typescript::TypeScriptLspAdapter)), ), ( "html", tree_sitter_html::language(), - Some(CachedLspAdapter::new(html::HtmlLspAdapter).await), + Some(Box::new(html::HtmlLspAdapter)), ), ( "ruby", tree_sitter_ruby::language(), - Some(CachedLspAdapter::new(ruby::RubyLanguageServer).await), + Some(Box::new(ruby::RubyLanguageServer)), ), ( "erb", tree_sitter_embedded_template::language(), - Some(CachedLspAdapter::new(ruby::RubyLanguageServer).await), + Some(Box::new(ruby::RubyLanguageServer)), + ), + ( + "scheme", + tree_sitter_scheme::language(), + None, // + ), + ( + "racket", + tree_sitter_racket::language(), + None, // ), - ("scheme", tree_sitter_scheme::language(), None), - ("racket", tree_sitter_racket::language(), None), ] { - languages.add(language(name, grammar, lsp_adapter)); + languages.register(name, load_config(name), grammar, lsp_adapter, load_queries); } } -pub(crate) fn language( +#[cfg(any(test, feature = "test-support"))] +pub async fn language( name: &str, grammar: tree_sitter::Language, - lsp_adapter: Option>, + lsp_adapter: Option>, ) -> Arc { - let config = toml::from_slice( + Arc::new( + Language::new(load_config(name), Some(grammar)) + .with_lsp_adapter(lsp_adapter) + .await + .with_queries(load_queries(name)) + .unwrap(), + ) +} + +fn load_config(name: &str) -> LanguageConfig { + toml::from_slice( &LanguageDir::get(&format!("{}/config.toml", name)) .unwrap() .data, ) .with_context(|| format!("failed to load config.toml for language {name:?}")) - .unwrap(); + .unwrap() +} - let mut language = Language::new(config, Some(grammar)); - - if let Some(query) = load_query(name, "/highlights") { - language = language - .with_highlights_query(query.as_ref()) - .expect("failed to evaluate highlights query"); +fn load_queries(name: &str) -> LanguageQueries { + LanguageQueries { + highlights: load_query(name, "/highlights"), + brackets: load_query(name, "/brackets"), + indents: load_query(name, "/indents"), + outline: load_query(name, "/outline"), + injections: load_query(name, "/injections"), + overrides: load_query(name, "/overrides"), } - if let Some(query) = load_query(name, "/brackets") { - language = language - .with_brackets_query(query.as_ref()) - .expect("failed to load brackets query"); - } - if let Some(query) = load_query(name, "/indents") { - language = language - .with_indents_query(query.as_ref()) - .expect("failed to load indents query"); - } - if let Some(query) = load_query(name, "/outline") { - language = language - .with_outline_query(query.as_ref()) - .expect("failed to load outline query"); - } - if let Some(query) = load_query(name, "/injections") { - language = language - .with_injection_query(query.as_ref()) - .expect("failed to load injection query"); - } - if let Some(query) = load_query(name, "/overrides") { - language = language - .with_override_query(query.as_ref()) - .expect("failed to load override query"); - } - if let Some(lsp_adapter) = lsp_adapter { - language = language.with_lsp_adapter(lsp_adapter) - } - Arc::new(language) } fn load_query(name: &str, filename_prefix: &str) -> Option> { diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 712e87101b..9fbb12857f 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -248,17 +248,19 @@ impl super::LspAdapter for CLspAdapter { #[cfg(test)] mod tests { - use gpui::MutableAppContext; + use gpui::TestAppContext; use language::{AutoindentMode, Buffer}; use settings::Settings; #[gpui::test] - fn test_c_autoindent(cx: &mut MutableAppContext) { + async fn test_c_autoindent(cx: &mut TestAppContext) { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); - let language = crate::languages::language("c", tree_sitter_c::language(), None); + cx.update(|cx| { + let mut settings = Settings::test(cx); + settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); + cx.set_global(settings); + }); + let language = crate::languages::language("c", tree_sitter_c::language(), None).await; cx.add_model(|cx| { let mut buffer = Buffer::new(0, "", cx).with_language(language, cx); diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 19692fdf44..dc84599e4e 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -314,8 +314,9 @@ mod tests { let language = language( "go", tree_sitter_go::language(), - Some(CachedLspAdapter::new(GoLspAdapter).await), - ); + Some(Box::new(GoLspAdapter)), + ) + .await; let theme = SyntaxTheme::new(vec![ ("type".into(), Color::green().into()), diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index ba6ccf7bf0..1391494ab1 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -165,17 +165,20 @@ impl LspAdapter for PythonLspAdapter { #[cfg(test)] mod tests { - use gpui::{ModelContext, MutableAppContext}; + use gpui::{ModelContext, TestAppContext}; use language::{AutoindentMode, Buffer}; use settings::Settings; #[gpui::test] - fn test_python_autoindent(cx: &mut MutableAppContext) { + async fn test_python_autoindent(cx: &mut TestAppContext) { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); - let language = crate::languages::language("python", tree_sitter_python::language(), None); - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); + let language = + crate::languages::language("python", tree_sitter_python::language(), None).await; + cx.update(|cx| { + let mut settings = Settings::test(cx); + settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); + cx.set_global(settings); + }); cx.add_model(|cx| { let mut buffer = Buffer::new(0, "", cx).with_language(language, cx); diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 30971fef1a..40948d5005 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -255,8 +255,8 @@ impl LspAdapter for RustLspAdapter { #[cfg(test)] mod tests { use super::*; - use crate::languages::{language, CachedLspAdapter}; - use gpui::{color::Color, MutableAppContext}; + use crate::languages::language; + use gpui::{color::Color, TestAppContext}; use settings::Settings; use theme::SyntaxTheme; @@ -306,8 +306,9 @@ mod tests { let language = language( "rust", tree_sitter_rust::language(), - Some(CachedLspAdapter::new(RustLspAdapter).await), - ); + Some(Box::new(RustLspAdapter)), + ) + .await; let grammar = language.grammar().unwrap(); let theme = SyntaxTheme::new(vec![ ("type".into(), Color::green().into()), @@ -391,8 +392,9 @@ mod tests { let language = language( "rust", tree_sitter_rust::language(), - Some(CachedLspAdapter::new(RustLspAdapter).await), - ); + Some(Box::new(RustLspAdapter)), + ) + .await; let grammar = language.grammar().unwrap(); let theme = SyntaxTheme::new(vec![ ("type".into(), Color::green().into()), @@ -431,12 +433,15 @@ mod tests { } #[gpui::test] - fn test_rust_autoindent(cx: &mut MutableAppContext) { + async fn test_rust_autoindent(cx: &mut TestAppContext) { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); - let language = crate::languages::language("rust", tree_sitter_rust::language(), None); - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); + cx.update(|cx| { + let mut settings = Settings::test(cx); + settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); + cx.set_global(settings); + }); + + let language = crate::languages::language("rust", tree_sitter_rust::language(), None).await; cx.add_model(|cx| { let mut buffer = Buffer::new(0, "", cx).with_language(language, cx); diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 01b62577ad..5290158dea 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -154,17 +154,17 @@ impl LspAdapter for TypeScriptLspAdapter { #[cfg(test)] mod tests { - - use gpui::MutableAppContext; + use gpui::TestAppContext; use unindent::Unindent; #[gpui::test] - fn test_outline(cx: &mut MutableAppContext) { + async fn test_outline(cx: &mut TestAppContext) { let language = crate::languages::language( "typescript", tree_sitter_typescript::language_typescript(), None, - ); + ) + .await; let text = r#" function a() { @@ -183,7 +183,7 @@ mod tests { let buffer = cx.add_model(|cx| language::Buffer::new(0, text, cx).with_language(language, cx)); - let outline = buffer.read(cx).snapshot().outline(None).unwrap(); + let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None).unwrap()); assert_eq!( outline .items diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 79183f3128..e1f151643c 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -120,11 +120,10 @@ fn main() { let client = client::Client::new(http.clone(), cx); let mut languages = LanguageRegistry::new(login_shell_env_loaded); + languages.set_executor(cx.background().clone()); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); let languages = Arc::new(languages); - let init_languages = cx - .background() - .spawn(languages::init(languages.clone(), cx.background().clone())); + languages::init(languages.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); watch_keymap_file(keymap_file, cx); @@ -152,14 +151,7 @@ fn main() { cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx)) .detach(); - cx.spawn({ - let languages = languages.clone(); - |cx| async move { - cx.read(|cx| languages.set_theme(cx.global::().theme.clone())); - init_languages.await; - } - }) - .detach(); + languages.set_theme(cx.global::().theme.clone()); cx.observe_global::({ let languages = languages.clone(); move |cx| languages.set_theme(cx.global::().theme.clone()) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c9f8b2d408..793172a111 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -306,7 +306,7 @@ pub fn initialize_workspace( ) .map(|meta| meta.name) .collect(); - let language_names = &languages::LANGUAGE_NAMES; + let language_names = app_state.languages.language_names(); workspace.project().update(cx, |project, cx| { let action_names = cx.all_action_names().collect::>(); @@ -318,7 +318,7 @@ pub fn initialize_workspace( "schemas": [ { "fileMatch": [schema_file_match(&paths::SETTINGS)], - "schema": settings_file_json_schema(theme_names, language_names), + "schema": settings_file_json_schema(theme_names, &language_names), }, { "fileMatch": [schema_file_match(&paths::KEYMAP)], From 74aeec360d03d23a3ec878fb517336495a99ea88 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Jan 2023 16:44:55 +0100 Subject: [PATCH 040/180] Cancel pending call when participant leaves room after a reconnection Previously, if a user temporarily disconnected while there was a pending call, we would fail to cancel such pending call when the caller left the room. This was due to the caller reconnecting and having a different connection id than the one originally used to initiate the call. --- crates/collab/src/db.rs | 8 ++------ .../collab/src/tests/randomized_integration_tests.rs | 10 ++++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 63ea7fdd9e..3358652feb 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1586,12 +1586,8 @@ impl Database { .filter( Condition::all() .add( - room_participant::Column::CallingConnectionId - .eq(connection.id as i32), - ) - .add( - room_participant::Column::CallingConnectionServerId - .eq(connection.owner_id as i32), + room_participant::Column::CallingUserId + .eq(leaving_participant.user_id), ) .add(room_participant::Column::AnsweringConnectionId.is_null()), ) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index e0170f6648..4783957dbb 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -166,12 +166,10 @@ async fn test_random_collaboration( let contacts = server.app_state.db.get_contacts(*user_id).await.unwrap(); let pool = server.connection_pool.lock(); for contact in contacts { - if let db::Contact::Accepted { user_id, .. } = contact { - if pool.is_user_online(user_id) { - assert_ne!( - user_id, removed_user_id, - "removed client is still a contact of another peer" - ); + if let db::Contact::Accepted { user_id, busy, .. } = contact { + if user_id == removed_user_id { + assert!(!pool.is_user_online(user_id)); + assert!(!busy); } } } From eca6115e4badec7be52695172b07ee9f495ec235 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Jan 2023 17:22:45 +0100 Subject: [PATCH 041/180] Ensure `proto::UpdateWorktree::removed_entries` doesn't exceed chunk size This was causing the database to panic because we were trying to remove too many entries at once. --- crates/rpc/src/proto.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 6b09f07db4..1a56abc783 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -9,7 +9,7 @@ use std::fmt; use std::{ cmp, fmt::Debug, - io, iter, mem, + io, iter, time::{Duration, SystemTime, UNIX_EPOCH}, }; @@ -489,16 +489,26 @@ pub fn split_worktree_update( return None; } - let chunk_size = cmp::min(message.updated_entries.len(), max_chunk_size); - let updated_entries = message.updated_entries.drain(..chunk_size).collect(); - done = message.updated_entries.is_empty(); + let updated_entries_chunk_size = cmp::min(message.updated_entries.len(), max_chunk_size); + let updated_entries = message + .updated_entries + .drain(..updated_entries_chunk_size) + .collect(); + + let removed_entries_chunk_size = cmp::min(message.removed_entries.len(), max_chunk_size); + let removed_entries = message + .removed_entries + .drain(..removed_entries_chunk_size) + .collect(); + + done = message.updated_entries.is_empty() && message.removed_entries.is_empty(); Some(UpdateWorktree { project_id: message.project_id, worktree_id: message.worktree_id, root_name: message.root_name.clone(), abs_path: message.abs_path.clone(), updated_entries, - removed_entries: mem::take(&mut message.removed_entries), + removed_entries, scan_id: message.scan_id, is_last_update: done && message.is_last_update, }) From 73af155dd64ce5460ea4fb3cf84be53290d139ab Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Thu, 26 Jan 2023 19:01:51 +0200 Subject: [PATCH 042/180] Refactor Database::remove_contact Refactor it to avoid sending irrelevant messages to update the UI. Co-Authored-By: Antonio Scandurra --- crates/collab/src/db.rs | 25 ++++++++++++++++--------- crates/collab/src/rpc.rs | 24 +++++++++++++++--------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 63ea7fdd9e..0edd79e0e4 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -595,7 +595,16 @@ impl Database { .await } - pub async fn remove_contact(&self, requester_id: UserId, responder_id: UserId) -> Result<()> { + /// Returns a bool indicating whether the removed contact had originally accepted or not + /// + /// Deletes the contact identified by the requester and responder ids, and then returns + /// whether the deleted contact had originally accepted or was a pending contact request. + /// + /// # Arguments + /// + /// * `requester_id` - The user that initiates this request + /// * `responder_id` - The user that will be removed + pub async fn remove_contact(&self, requester_id: UserId, responder_id: UserId) -> Result { self.transaction(|tx| async move { let (id_a, id_b) = if responder_id < requester_id { (responder_id, requester_id) @@ -603,20 +612,18 @@ impl Database { (requester_id, responder_id) }; - let result = contact::Entity::delete_many() + let contact = contact::Entity::find() .filter( contact::Column::UserIdA .eq(id_a) .and(contact::Column::UserIdB.eq(id_b)), ) - .exec(&*tx) - .await?; + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such contact"))?; - if result.rows_affected == 1 { - Ok(()) - } else { - Err(anyhow!("no such contact"))? - } + contact::Entity::delete_by_id(contact.id).exec(&*tx).await?; + Ok(contact.accepted) }) .await } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 54ffd9a958..32cce1e681 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1961,25 +1961,31 @@ async fn remove_contact( let requester_id = session.user_id; let responder_id = UserId::from_proto(request.user_id); let db = session.db().await; - db.remove_contact(requester_id, responder_id).await?; + let contact_accepted = db.remove_contact(requester_id, responder_id).await?; let pool = session.connection_pool().await; // Update outgoing contact requests of requester let mut update = proto::UpdateContacts::default(); - update.remove_contacts.push(responder_id.to_proto()); - update - .remove_outgoing_requests - .push(responder_id.to_proto()); + if contact_accepted { + update.remove_contacts.push(responder_id.to_proto()); + } else { + update + .remove_outgoing_requests + .push(responder_id.to_proto()); + } for connection_id in pool.user_connection_ids(requester_id) { session.peer.send(connection_id, update.clone())?; } // Update incoming contact requests of responder let mut update = proto::UpdateContacts::default(); - update.remove_contacts.push(requester_id.to_proto()); - update - .remove_incoming_requests - .push(requester_id.to_proto()); + if contact_accepted { + update.remove_contacts.push(requester_id.to_proto()); + } else { + update + .remove_incoming_requests + .push(requester_id.to_proto()); + } for connection_id in pool.user_connection_ids(responder_id) { session.peer.send(connection_id, update.clone())?; } From 1c572fd86e41cf904ad07247975e7db907304cbf Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 26 Jan 2023 09:53:46 -0800 Subject: [PATCH 043/180] Add 'view dependency licenses' item to Help appication menu --- crates/zed/src/menus.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 834eb751e1..85ab62af73 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -337,6 +337,10 @@ pub fn menus() -> Vec> { name: "View Telemetry Log", action: Box::new(crate::OpenTelemetryLog), }, + MenuItem::Action { + name: "View Dependency Licenses", + action: Box::new(crate::OpenLicenses), + }, MenuItem::Separator, MenuItem::Action { name: "Copy System Specs Into Clipboard", From 9f86748aff13bd0b402f0b2ff28d125344c25202 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 26 Jan 2023 10:30:01 -0800 Subject: [PATCH 044/180] Avoid opening a definitions tab if there are no definitions found --- crates/editor/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 84b97468e0..f25eefcb7d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5042,7 +5042,7 @@ impl Editor { pane.update(cx, |pane, _| pane.enable_history()); }); - } else { + } else if !definitions.is_empty() { let replica_id = editor_handle.read(cx).replica_id(cx); let title = definitions .iter() From 1b459118578d5af55e4ef3154bd2592b9685f74d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 26 Jan 2023 14:47:37 -0800 Subject: [PATCH 045/180] Omit hidden worktrees when showing projects in collaboration UI --- crates/collab/src/db.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 0edd79e0e4..2b0e2ce9cf 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1924,7 +1924,9 @@ impl Database { }; if let Some(db_worktree) = db_worktree { - project.worktree_root_names.push(db_worktree.root_name); + if db_worktree.visible { + project.worktree_root_names.push(db_worktree.root_name); + } } } } From f99e4043c478b9e9c4791b534a0f7778e66a5bd5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 26 Jan 2023 14:55:06 -0800 Subject: [PATCH 046/180] Run CI for version branches but not all branches starting with 'v' --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fce5717ca9..b3268cc13c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - "v*" + - "v[0-9]+.[0-9]+.x" tags: - "v*" pull_request: From 1593b1e13d3a842fcb115873c5bd25f85c929bfe Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Thu, 26 Jan 2023 16:35:00 -0800 Subject: [PATCH 047/180] window position restoration working --- crates/gpui/src/platform/mac/geometry.rs | 38 +++++++++++----------- crates/gpui/src/platform/mac/window.rs | 21 +++++++------ crates/workspace/src/workspace.rs | 40 +++++++++++++++++++++--- 3 files changed, 66 insertions(+), 33 deletions(-) diff --git a/crates/gpui/src/platform/mac/geometry.rs b/crates/gpui/src/platform/mac/geometry.rs index a59d72a796..0f3b1f6fce 100644 --- a/crates/gpui/src/platform/mac/geometry.rs +++ b/crates/gpui/src/platform/mac/geometry.rs @@ -42,31 +42,16 @@ impl RectFExt for RectF { } fn to_ns_rect(&self) -> NSRect { - dbg!(&self); NSRect::new( NSPoint::new( - dbg!(self.origin_x() as f64), - dbg!(-(self.origin_y() - self.height()) as f64), + self.origin_x() as f64, + -(self.origin_y() + self.height()) as f64, ), NSSize::new(self.width() as f64, self.height() as f64), ) } } -pub trait NSPointExt { - /// Converts self to a Vector2F with y axis pointing down. - /// Also takes care of converting from window scaled coordinates to screen coordinates - fn to_window_vector2f(&self, native_window: id) -> Vector2F; -} -impl NSPointExt for NSPoint { - fn to_window_vector2f(&self, native_window: id) -> Vector2F { - unsafe { - let point: NSPoint = msg_send![native_window, convertPointFromScreen: self]; - vec2f(point.x as f32, -point.y as f32) - } - } -} - pub trait NSRectExt { /// Converts self to a RectF with y axis pointing down. /// The resulting RectF will have an origin at the top left of the rectangle. @@ -77,11 +62,13 @@ pub trait NSRectExt { /// The resulting RectF will have an origin at the top left of the rectangle. /// Unlike to_screen_ns_rect, coordinates are not converted and are assumed to already be in screen scale fn to_rectf(&self) -> RectF; + + fn intersects(&self, other: Self) -> bool; } impl NSRectExt for NSRect { fn to_window_rectf(&self, native_window: id) -> RectF { unsafe { - dbg!(self.origin.x); + self.origin.x; let rect: NSRect = native_window.convertRectFromScreen_(*self); rect.to_rectf() } @@ -90,10 +77,21 @@ impl NSRectExt for NSRect { fn to_rectf(&self) -> RectF { RectF::new( vec2f( - dbg!(self.origin.x as f32), - dbg!(-(self.origin.y - self.size.height) as f32), + self.origin.x as f32, + -(self.origin.y + self.size.height) as f32, ), vec2f(self.size.width as f32, self.size.height as f32), ) } + + fn intersects(&self, other: Self) -> bool { + self.size.width > 0. + && self.size.height > 0. + && other.size.width > 0. + && other.size.height > 0. + && self.origin.x <= other.origin.x + other.size.width + && self.origin.x + self.size.width >= other.origin.x + && self.origin.y <= other.origin.y + other.size.height + && self.origin.y + self.size.height >= other.origin.y + } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index ecea0604f5..bc934703be 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -371,14 +371,8 @@ impl WindowState { return WindowBounds::Fullscreen; } - let screen_frame = self - .native_window - .screen() - .visibleFrame() - .to_window_rectf(self.native_window); let window_frame = self.frame(); - - if screen_frame == window_frame { + if window_frame == self.native_window.screen().visibleFrame().to_rectf() { WindowBounds::Maximized } else { WindowBounds::Fixed(window_frame) @@ -388,7 +382,10 @@ impl WindowState { // Returns the window bounds in window coordinates fn frame(&self) -> RectF { - unsafe { NSWindow::frame(self.native_window).to_window_rectf(self.native_window) } + unsafe { + let ns_frame = NSWindow::frame(self.native_window); + ns_frame.to_rectf() + } } fn content_size(&self) -> Vector2F { @@ -474,7 +471,13 @@ impl Window { native_window.setFrame_display_(screen.visibleFrame(), YES); } WindowBounds::Fixed(rect) => { - native_window.setFrame_display_(rect.to_screen_ns_rect(native_window), YES); + let screen_frame = screen.visibleFrame(); + let ns_rect = rect.to_ns_rect(); + if ns_rect.intersects(screen_frame) { + native_window.setFrame_display_(ns_rect, YES); + } else { + native_window.setFrame_display_(screen_frame, YES); + } } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4602e498f1..80c52e0bc8 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -682,10 +682,28 @@ impl Workspace { DB.next_id().await.unwrap_or(0) }; - let (bounds, display) = dbg!(serialized_workspace + let (bounds, display) = serialized_workspace .as_ref() .and_then(|sw| sw.bounds.zip(sw.display)) - .unzip()); + .and_then(|(mut bounds, display)| { + // Stored bounds are relative to the containing display. So convert back to global coordinates if that screen still exists + if let WindowBounds::Fixed(mut window_bounds) = bounds { + if let Some(screen) = cx.platform().screen_by_id(display) { + let screen_bounds = screen.bounds(); + window_bounds + .set_origin_x(window_bounds.origin_x() + screen_bounds.origin_x()); + window_bounds + .set_origin_y(window_bounds.origin_y() + screen_bounds.origin_y()); + bounds = WindowBounds::Fixed(window_bounds); + } else { + // Screen no longer exists. Return none here. + return None; + } + } + + Some((bounds, display)) + }) + .unzip(); // Use the serialized workspace to construct the new window let (_, workspace) = cx.add_window( @@ -699,9 +717,23 @@ impl Workspace { cx, ); (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - cx.observe_window_bounds(move |_, bounds, display, cx| { + cx.observe_window_bounds(move |_, mut bounds, display, cx| { + // Transform fixed bounds to be stored in terms of the containing display + if let WindowBounds::Fixed(mut window_bounds) = bounds { + if let Some(screen) = cx.platform().screen_by_id(display) { + let screen_bounds = screen.bounds(); + window_bounds.set_origin_x( + window_bounds.origin_x() - screen_bounds.origin_x(), + ); + window_bounds.set_origin_y( + window_bounds.origin_y() - screen_bounds.origin_y(), + ); + bounds = WindowBounds::Fixed(window_bounds); + } + } + cx.background() - .spawn(DB.set_window_bounds(workspace_id, dbg!(bounds), dbg!(display))) + .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }) .detach(); From c44acaefff95e3dd93f4e7f7514b78bcc2cbac17 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 26 Jan 2023 17:27:42 -0800 Subject: [PATCH 048/180] Added build-licenses command to style tree --- .gitignore | 2 +- crates/zed/src/menus.rs | 6 +- crates/zed/src/zed.rs | 22 +- script/generate-licenses | 20 +- script/licenses/zed-licenses.toml | 2 + styles/package-lock.json | 631 ++++++++++++----------- styles/package.json | 4 +- styles/src/buildLicenses.ts | 75 +++ styles/src/buildThemes.ts | 14 +- styles/src/colorSchemes.ts | 37 +- styles/src/themes/andromeda.ts | 18 +- styles/src/themes/atelier-cave.ts | 19 +- styles/src/themes/atelier-sulphurpool.ts | 18 +- styles/src/themes/common/colorScheme.ts | 22 + styles/src/themes/one-dark.ts | 18 +- styles/src/themes/one-light.ts | 18 +- styles/src/themes/rose-pine-dawn.ts | 18 +- styles/src/themes/rose-pine-moon.ts | 18 +- styles/src/themes/rose-pine.ts | 18 +- styles/src/themes/sandcastle.ts | 19 +- styles/src/themes/solarized.ts | 19 +- styles/src/themes/summercamp.ts | 17 +- 22 files changed, 632 insertions(+), 403 deletions(-) create mode 100644 styles/src/buildLicenses.ts diff --git a/.gitignore b/.gitignore index 8bca2eafac..69dbbd3e1c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ /assets/themes/*.json /assets/themes/Internal/*.json /assets/themes/Experiments/*.json -/assets/licenses.md +/assets/*licenses.md **/venv .build Packages diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 85ab62af73..5c84f78375 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -339,7 +339,11 @@ pub fn menus() -> Vec> { }, MenuItem::Action { name: "View Dependency Licenses", - action: Box::new(crate::OpenLicenses), + action: Box::new(crate::OpenSoftwareLicenses), + }, + MenuItem::Action { + name: "View Theme Licenses", + action: Box::new(crate::OpenThemeLicenses), }, MenuItem::Separator, MenuItem::Action { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 793172a111..d3e1d3d618 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -57,7 +57,8 @@ actions!( DebugElements, OpenSettings, OpenLog, - OpenLicenses, + OpenSoftwareLicenses, + OpenThemeLicenses, OpenTelemetryLog, OpenKeymap, OpenDefaultSettings, @@ -179,17 +180,32 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { }); cx.add_action({ let app_state = app_state.clone(); - move |workspace: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext| { + move |workspace: &mut Workspace, + _: &OpenSoftwareLicenses, + cx: &mut ViewContext| { open_bundled_file( workspace, app_state.clone(), - "licenses.md", + "software_licenses.md", "Open Source License Attribution", "Markdown", cx, ); } }); + cx.add_action({ + let app_state = app_state.clone(); + move |workspace: &mut Workspace, _: &OpenThemeLicenses, cx: &mut ViewContext| { + open_bundled_file( + workspace, + app_state.clone(), + "theme_licenses.md", + "Theme License Attribution", + "Markdown", + cx, + ); + } + }); cx.add_action({ let app_state = app_state.clone(); move |workspace: &mut Workspace, _: &OpenTelemetryLog, cx: &mut ViewContext| { diff --git a/script/generate-licenses b/script/generate-licenses index e1a917292c..f8a718bb1b 100755 --- a/script/generate-licenses +++ b/script/generate-licenses @@ -4,12 +4,18 @@ set -e [[ "$(cargo about --version)" == "cargo-about 0.5.2" ]] || cargo install cargo-about --locked --git https://github.com/zed-industries/cargo-about --branch error-code-on-warn -cargo about generate --fail-on-missing-license -o assets/licenses.md -c script/licenses/zed-licenses.toml script/licenses/template.hbs.md +echo "Generating cargo licenses" +cargo about generate --fail-on-missing-license -o assets/software_licenses.md -c script/licenses/zed-licenses.toml script/licenses/template.hbs.md # cargo about automatically html-escapes all output, so we need to undo it here: -sed -i '' 's/"/"/g' assets/licenses.md -sed -i '' 's/'/'\''/g' assets/licenses.md # `'\''` ends the string, appends a single quote, and re-opens the string -sed -i '' 's/=/=/g' assets/licenses.md -sed -i '' 's/`/`/g' assets/licenses.md -sed -i '' 's/<//g' assets/licenses.md \ No newline at end of file +sed -i '' 's/"/"/g' assets/software_licenses.md +sed -i '' 's/'/'\''/g' assets/software_licenses.md # `'\''` ends the string, appends a single quote, and re-opens the string +sed -i '' 's/=/=/g' assets/software_licenses.md +sed -i '' 's/`/`/g' assets/software_licenses.md +sed -i '' 's/<//g' assets/software_licenses.md + +# Now make theme licenses +echo "Generating theme licenses" +cd styles +npm run build-licenses \ No newline at end of file diff --git a/script/licenses/zed-licenses.toml b/script/licenses/zed-licenses.toml index d338e7ab0b..e166b653c8 100644 --- a/script/licenses/zed-licenses.toml +++ b/script/licenses/zed-licenses.toml @@ -1,3 +1,5 @@ +# NOTE: This file's location is hardcoded into the theme build system in +# styles/src/buildLicenses.ts no-clearly-defined = true private = { ignore = true } accepted = [ diff --git a/styles/package-lock.json b/styles/package-lock.json index 582f1c8496..b0a904b11d 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -1,316 +1,327 @@ { - "name": "styles", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "styles", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@types/chroma-js": "^2.1.3", - "@types/node": "^17.0.23", - "case-anything": "^2.1.10", - "chroma-js": "^2.4.2", - "ts-node": "^10.7.0" - } - }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", - "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" - }, - "node_modules/@types/chroma-js": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz", - "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g==" - }, - "node_modules/@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" - }, - "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" - }, - "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/chroma-js": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", - "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" - }, - "node_modules/ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", - "dependencies": { - "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true + "name": "styles", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "styles", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@types/chroma-js": "^2.1.3", + "@types/node": "^17.0.23", + "case-anything": "^2.1.10", + "chroma-js": "^2.4.2", + "toml": "^3.0.0", + "ts-node": "^10.7.0" + } }, - "@swc/wasm": { - "optional": true + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" + }, + "node_modules/@types/chroma-js": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz", + "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g==" + }, + "node_modules/@types/node": { + "version": "17.0.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", + "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" + }, + "node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "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/chroma-js": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", + "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" + }, + "node_modules/ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } } - } }, - "node_modules/typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", - "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==" - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "engines": { - "node": ">=6" - } + "dependencies": { + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==" + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" + }, + "@types/chroma-js": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz", + "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g==" + }, + "@types/node": { + "version": "17.0.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", + "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" + }, + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "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==" + }, + "chroma-js": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", + "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" + }, + "ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "peer": true + }, + "v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==" + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + } } - }, - "dependencies": { - "@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==" - }, - "@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", - "requires": { - "@cspotcode/source-map-consumer": "0.8.0" - } - }, - "@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" - }, - "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" - }, - "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" - }, - "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" - }, - "@types/chroma-js": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz", - "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g==" - }, - "@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" - }, - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" - }, - "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==" - }, - "chroma-js": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", - "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" - }, - "ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", - "requires": { - "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", - "yn": "3.1.1" - } - }, - "typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", - "peer": true - }, - "v8-compile-cache-lib": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", - "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==" - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" - } - } } diff --git a/styles/package.json b/styles/package.json index 11bcbadf73..118269bc81 100644 --- a/styles/package.json +++ b/styles/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "build": "ts-node ./src/buildThemes.ts" + "build": "ts-node ./src/buildThemes.ts", + "build-licenses": "ts-node ./src/buildLicenses.ts" }, "author": "", "license": "ISC", @@ -13,6 +14,7 @@ "@types/node": "^17.0.23", "case-anything": "^2.1.10", "chroma-js": "^2.4.2", + "toml": "^3.0.0", "ts-node": "^10.7.0" } } diff --git a/styles/src/buildLicenses.ts b/styles/src/buildLicenses.ts new file mode 100644 index 0000000000..5cf1d02dbf --- /dev/null +++ b/styles/src/buildLicenses.ts @@ -0,0 +1,75 @@ +import * as fs from "fs"; +import toml from "toml"; +import { + schemeMeta +} from "./colorSchemes"; +import { Meta } from "./themes/common/colorScheme"; +import https from "https"; +import crypto from "crypto"; +const license_file = `${__dirname}/../../assets/theme_licenses.md` +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[] { + let buffer = fs.readFileSync(file).toString(); + + let obj = toml.parse(buffer); + + if (!Array.isArray(obj.accepted)) { + throw Error("Accepted license source is malformed") + } + + return obj.accepted +} + + +function checkLicenses(schemeMeta: Meta[], licenses: string[]) { + for (let meta of schemeMeta) { + // FIXME: Add support for conjuctions and conditions + if (licenses.indexOf(meta.license.SPDX) < 0) { + throw Error(`License for theme ${meta.name} (${meta.license.SPDX}) is not supported`) + } + } +} + + +function getLicenseText(schemeMeta: Meta[], callback: (meta: Meta, license_text: string) => void) { + for (let meta of schemeMeta) { + // The following copied from the example code on nodejs.org: + // https://nodejs.org/api/http.html#httpgetoptions-callback + https.get(meta.license.https_url, (res) => { + const { statusCode } = res; + + if (statusCode < 200 || statusCode >= 300) { + throw new Error('Failed to fetch license file.\n' + + `Status Code: ${statusCode}`); + } + + res.setEncoding('utf8'); + let rawData = ''; + res.on('data', (chunk) => { rawData += chunk; }); + res.on('end', () => { + const hash = crypto.createHash('sha256').update(rawData).digest('hex'); + if (meta.license.license_checksum == hash) { + callback(meta, rawData) + } else { + throw Error(`Checksum for ${meta.name} did not match file downloaded from ${meta.license.https_url}`) + } + }); + }).on('error', (e) => { + throw e + }); + } +} + +function writeLicense(schemeMeta: Meta, text: String, stream: fs.WriteStream) { + stream.write(`# [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n******************************************************************************** \n`) +} + +const accepted_licenses = parseAcceptedToml(accepted_licenses_file); +checkLicenses(schemeMeta, accepted_licenses) + +const stream = fs.createWriteStream(license_file); +getLicenseText(schemeMeta, (meta, text) => { + writeLicense(meta, text, stream) +}); diff --git a/styles/src/buildThemes.ts b/styles/src/buildThemes.ts index 32749a7aaa..6512d13a47 100644 --- a/styles/src/buildThemes.ts +++ b/styles/src/buildThemes.ts @@ -1,15 +1,15 @@ import * as fs from "fs"; -import * as path from "path"; import { tmpdir } from "os"; -import app from "./styleTree/app"; +import * as path from "path"; import colorSchemes, { - internalColorSchemes, - experimentalColorSchemes, + experimentalColorSchemes, internalColorSchemes } from "./colorSchemes"; -import snakeCase from "./utils/snakeCase"; +import app from "./styleTree/app"; import { ColorScheme } from "./themes/common/colorScheme"; +import snakeCase from "./utils/snakeCase"; -const themeDirectory = `${__dirname}/../../assets/themes`; +const assetsDirectory = `${__dirname}/../../assets` +const themeDirectory = `${assetsDirectory}/themes`; const internalDirectory = `${themeDirectory}/Internal`; const experimentsDirectory = `${themeDirectory}/Experiments`; @@ -50,4 +50,4 @@ function writeThemes(colorSchemes: ColorScheme[], outputDirectory: string) { // Write new themes to theme directory writeThemes(colorSchemes, themeDirectory); writeThemes(internalColorSchemes, internalDirectory); -writeThemes(experimentalColorSchemes, experimentsDirectory); +writeThemes(experimentalColorSchemes, experimentsDirectory); \ No newline at end of file diff --git a/styles/src/colorSchemes.ts b/styles/src/colorSchemes.ts index 746443119d..fdf9b51ebe 100644 --- a/styles/src/colorSchemes.ts +++ b/styles/src/colorSchemes.ts @@ -1,35 +1,58 @@ import fs from "fs"; import path from "path"; -import { ColorScheme } from "./themes/common/colorScheme"; +import { ColorScheme, Meta } from "./themes/common/colorScheme"; const colorSchemes: ColorScheme[] = []; export default colorSchemes; +const schemeMeta: Meta[] = []; +export { schemeMeta }; + const internalColorSchemes: ColorScheme[] = []; export { internalColorSchemes }; const experimentalColorSchemes: ColorScheme[] = []; export { experimentalColorSchemes }; -function fillColorSchemes(themesPath: string, colorSchemes: ColorScheme[]) { +const themes_directory = path.resolve(`${__dirname}/themes`); + +function for_all_color_schemes_in(themesPath: string, callback: (module: any, path: string) => void) { for (const fileName of fs.readdirSync(themesPath)) { if (fileName == "template.ts") continue; const filePath = path.join(themesPath, fileName); if (fs.statSync(filePath).isFile()) { const colorScheme = require(filePath); - if (colorScheme.dark) colorSchemes.push(colorScheme.dark); - if (colorScheme.light) colorSchemes.push(colorScheme.light); + callback(colorScheme, path.basename(filePath)); } } } -fillColorSchemes(path.resolve(`${__dirname}/themes`), colorSchemes); +function fillColorSchemes(themesPath: string, colorSchemes: ColorScheme[]) { + for_all_color_schemes_in(themesPath, (colorScheme, _path) => { + if (colorScheme.dark) colorSchemes.push(colorScheme.dark); + if (colorScheme.light) colorSchemes.push(colorScheme.light); + }) +} + +fillColorSchemes(themes_directory, colorSchemes); fillColorSchemes( - path.resolve(`${__dirname}/themes/internal`), + path.resolve(`${themes_directory}/internal`), internalColorSchemes ); fillColorSchemes( - path.resolve(`${__dirname}/themes/experiments`), + path.resolve(`${themes_directory}/experiments`), experimentalColorSchemes ); + +function fillMeta(themesPath: string, meta: Meta[]) { + for_all_color_schemes_in(themesPath, (colorScheme, path) => { + if (colorScheme.meta) { + meta.push(colorScheme.meta) + } else { + throw Error(`Public theme ${path} must have a meta field`) + } + }) +} + +fillMeta(themes_directory, schemeMeta); diff --git a/styles/src/themes/andromeda.ts b/styles/src/themes/andromeda.ts index 520ceb67fe..b76179b3c5 100644 --- a/styles/src/themes/andromeda.ts +++ b/styles/src/themes/andromeda.ts @@ -1,13 +1,8 @@ import chroma from "chroma-js"; +import { Meta } from "./common/colorScheme"; import { colorRamp, createColorScheme } from "./common/ramps"; const name = "Andromeda"; -const author = "EliverLara"; -const url = "https://github.com/EliverLara/Andromeda"; -const license = { - type: "MIT", - url: "https://github.com/EliverLara/Andromeda/blob/master/LICENSE.md", -}; const ramps = { neutral: chroma @@ -33,3 +28,14 @@ const ramps = { }; export const dark = createColorScheme(`${name}`, false, ramps); + +export const meta: Meta = { + name, + author: "EliverLara", + license: { + SPDX: "MIT", + https_url: "https://raw.githubusercontent.com/EliverLara/Andromeda/master/LICENSE.md", + license_checksum: "2f7886f1a05cefc2c26f5e49de1a39fa4466413c1ccb06fc80960e73f5ed4b89" + }, + url: "https://github.com/EliverLara/Andromeda" +} \ No newline at end of file diff --git a/styles/src/themes/atelier-cave.ts b/styles/src/themes/atelier-cave.ts index 98cf834704..b7b06381d8 100644 --- a/styles/src/themes/atelier-cave.ts +++ b/styles/src/themes/atelier-cave.ts @@ -1,13 +1,8 @@ import chroma from "chroma-js"; +import { Meta } from "./common/colorScheme"; import { colorRamp, createColorScheme } from "./common/ramps"; const name = "Atelier Cave"; -const author = "atelierbram"; -const url = "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/"; -const license = { - type: "MIT", - url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE", -}; export const dark = createColorScheme(`${name} Dark`, false, { neutral: chroma @@ -54,3 +49,15 @@ export const light = createColorScheme(`${name} Light`, true, { violet: colorRamp(chroma("#955ae7")), magenta: colorRamp(chroma("#bf40bf")), }); + + +export const meta: Meta = { + name, + author: "atelierbram", + license: { + SPDX: "MIT", + https_url: "https://raw.githubusercontent.com/atelierbram/syntax-highlighting/master/LICENSE", + license_checksum: "6c2353bb9dd0b7b211364d98184ab482e54f40f611eda0c02974c3a1f9e6193c" + }, + url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/" +} \ No newline at end of file diff --git a/styles/src/themes/atelier-sulphurpool.ts b/styles/src/themes/atelier-sulphurpool.ts index d8293db3a7..2e2f708442 100644 --- a/styles/src/themes/atelier-sulphurpool.ts +++ b/styles/src/themes/atelier-sulphurpool.ts @@ -1,13 +1,8 @@ import chroma from "chroma-js"; +import { Meta } from "./common/colorScheme"; import { colorRamp, createColorScheme } from "./common/ramps"; const name = "Atelier Sulphurpool"; -const author = "atelierbram"; -const url = "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune/"; -const license = { - type: "MIT", - url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE", -}; const ramps = { neutral: chroma @@ -34,3 +29,14 @@ const ramps = { export const dark = createColorScheme(`${name} Dark`, false, ramps); export const light = createColorScheme(`${name} Light`, true, ramps); + +export const meta: Meta = { + name, + author: "atelierbram", + license: { + SPDX: "MIT", + https_url: "https://raw.githubusercontent.com/atelierbram/syntax-highlighting/master/LICENSE", + license_checksum: "6c2353bb9dd0b7b211364d98184ab482e54f40f611eda0c02974c3a1f9e6193c" + }, + url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune/" +} \ No newline at end of file diff --git a/styles/src/themes/common/colorScheme.ts b/styles/src/themes/common/colorScheme.ts index 1b2c2cf7e8..ee858627d7 100644 --- a/styles/src/themes/common/colorScheme.ts +++ b/styles/src/themes/common/colorScheme.ts @@ -16,6 +16,28 @@ export interface ColorScheme { players: Players; } +export interface Meta { + name: string, + author: string, + url: string, + license: License +} + +export interface License { + SPDX: SPDXExpression, + /// A url where we can download the license's text + https_url: string, + license_checksum: string +} + +// License name -> License text +export interface Licenses { + [key: string]: string +} + +// FIXME: Add support for the SPDX expression syntax +export type SPDXExpression = "MIT"; + export interface Player { cursor: string; selection: string; diff --git a/styles/src/themes/one-dark.ts b/styles/src/themes/one-dark.ts index 612a71ccc1..42a765e3e3 100644 --- a/styles/src/themes/one-dark.ts +++ b/styles/src/themes/one-dark.ts @@ -1,13 +1,8 @@ import chroma from "chroma-js"; +import { Meta } from "./common/colorScheme"; import { colorRamp, createColorScheme } from "./common/ramps"; const name = "One Dark"; -const author = "simurai"; -const url = "https://github.com/atom/atom/tree/master/packages/one-dark-ui"; -const license = { - type: "MIT", - url: "https://github.com/atom/atom/blob/master/packages/one-dark-ui/LICENSE.md", -}; export const dark = createColorScheme(`${name}`, false, { neutral: chroma @@ -32,3 +27,14 @@ export const dark = createColorScheme(`${name}`, false, { violet: colorRamp(chroma("#c678dd")), magenta: colorRamp(chroma("#be5046")), }); + +export const meta: Meta = { + name, + author: "simurai", + license: { + SPDX: "MIT", + https_url: "https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md", + license_checksum: "d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8" + }, + url: "https://github.com/atom/atom/tree/master/packages/one-dark-ui" +} diff --git a/styles/src/themes/one-light.ts b/styles/src/themes/one-light.ts index a5ac1f7158..50f99becdc 100644 --- a/styles/src/themes/one-light.ts +++ b/styles/src/themes/one-light.ts @@ -1,13 +1,8 @@ import chroma from "chroma-js"; +import { Meta } from "./common/colorScheme"; import { colorRamp, createColorScheme } from "./common/ramps"; const name = "One Light"; -const author = "simurai"; -const url = "https://github.com/atom/atom/tree/master/packages/one-light-ui"; -const license = { - type: "MIT", - url: "https://github.com/atom/atom/blob/master/packages/one-light-ui/LICENSE.md", -}; export const light = createColorScheme(`${name}`, true, { neutral: chroma.scale([ @@ -31,3 +26,14 @@ export const light = createColorScheme(`${name}`, true, { violet: colorRamp(chroma("#a626a4")), magenta: colorRamp(chroma("#986801")), }); + +export const meta: Meta = { + name, + author: "simurai", + license: { + SPDX: "MIT", + https_url: "https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md", + license_checksum: "d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8" + }, + url: "https://github.com/atom/atom/tree/master/packages/one-light-ui" +} diff --git a/styles/src/themes/rose-pine-dawn.ts b/styles/src/themes/rose-pine-dawn.ts index 20d5dd1ebe..b1744f9c20 100644 --- a/styles/src/themes/rose-pine-dawn.ts +++ b/styles/src/themes/rose-pine-dawn.ts @@ -1,13 +1,8 @@ import chroma from "chroma-js"; +import { Meta } from "./common/colorScheme"; import { colorRamp, createColorScheme } from "./common/ramps"; const name = "Rosé Pine Dawn"; -const author = "edunfelt"; -const url = "https://github.com/edunfelt/base16-rose-pine-scheme"; -const license = { - type: "MIT", - url: "https://github.com/edunfelt/base16-rose-pine-scheme/blob/main/rose-pine-dawn.yaml", -}; const ramps = { neutral: chroma @@ -33,3 +28,14 @@ const ramps = { }; export const light = createColorScheme(`${name}`, true, ramps); + +export const meta: Meta = { + name, + author: "edunfelt", + license: { + SPDX: "MIT", + https_url: "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", + license_checksum: "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a" + }, + url: "https://github.com/edunfelt/base16-rose-pine-scheme" +} \ No newline at end of file diff --git a/styles/src/themes/rose-pine-moon.ts b/styles/src/themes/rose-pine-moon.ts index 5920357bd3..a4c1737c2b 100644 --- a/styles/src/themes/rose-pine-moon.ts +++ b/styles/src/themes/rose-pine-moon.ts @@ -1,13 +1,8 @@ import chroma from "chroma-js"; +import { Meta } from "./common/colorScheme"; import { colorRamp, createColorScheme } from "./common/ramps"; const name = "Rosé Pine Moon"; -const author = "edunfelt"; -const url = "https://github.com/edunfelt/base16-rose-pine-scheme"; -const license = { - type: "MIT", - url: "https://github.com/edunfelt/base16-rose-pine-scheme/blob/main/rose-pine-moon.yaml", -}; const ramps = { neutral: chroma @@ -33,3 +28,14 @@ const ramps = { }; export const dark = createColorScheme(`${name}`, false, ramps); + +export const meta: Meta = { + name, + author: "edunfelt", + license: { + SPDX: "MIT", + https_url: "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", + license_checksum: "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a" + }, + url: "https://github.com/edunfelt/base16-rose-pine-scheme" +} \ No newline at end of file diff --git a/styles/src/themes/rose-pine.ts b/styles/src/themes/rose-pine.ts index 9144a136d2..e3c115213b 100644 --- a/styles/src/themes/rose-pine.ts +++ b/styles/src/themes/rose-pine.ts @@ -1,13 +1,8 @@ import chroma from "chroma-js"; +import { Meta } from "./common/colorScheme"; import { colorRamp, createColorScheme } from "./common/ramps"; const name = "Rosé Pine"; -const author = "edunfelt"; -const url = "https://github.com/edunfelt/base16-rose-pine-scheme"; -const license = { - type: "MIT", - url: "https://github.com/edunfelt/base16-rose-pine-scheme", -}; const ramps = { neutral: chroma.scale([ @@ -31,3 +26,14 @@ const ramps = { }; export const dark = createColorScheme(`${name}`, false, ramps); + +export const meta: Meta = { + name, + author: "edunfelt", + license: { + SPDX: "MIT", + https_url: "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", + license_checksum: "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a" + }, + url: "https://github.com/edunfelt/base16-rose-pine-scheme" +} \ No newline at end of file diff --git a/styles/src/themes/sandcastle.ts b/styles/src/themes/sandcastle.ts index c625ab2986..0e1328feab 100644 --- a/styles/src/themes/sandcastle.ts +++ b/styles/src/themes/sandcastle.ts @@ -1,13 +1,8 @@ import chroma from "chroma-js"; +import { Meta } from "./common/colorScheme"; import { colorRamp, createColorScheme } from "./common/ramps"; const name = "Sandcastle"; -const author = "gessig"; -const url = "https://github.com/gessig/base16-sandcastle-scheme"; -const license = { - type: "MIT", - url: "https://github.com/gessig/base16-sandcastle-scheme/blob/master/LICENSE", -}; const ramps = { neutral: chroma.scale([ @@ -31,3 +26,15 @@ const ramps = { }; export const dark = createColorScheme(`${name}`, false, ramps); + +export const meta: Meta = { + name, + author: "gessig", + license: { + SPDX: "MIT", + https_url: "https://raw.githubusercontent.com/gessig/base16-sandcastle-scheme/master/LICENSE", + license_checksum: "8399d44b4d935b60be9fee0a76d7cc9a817b4f3f11574c9d6d1e8fd57e72ffdc" + }, + url: "https://github.com/gessig/base16-sandcastle-scheme" +} + diff --git a/styles/src/themes/solarized.ts b/styles/src/themes/solarized.ts index 3e0fff61e8..98f9339d6e 100644 --- a/styles/src/themes/solarized.ts +++ b/styles/src/themes/solarized.ts @@ -1,13 +1,8 @@ import chroma from "chroma-js"; +import { Meta as Metadata } from "./common/colorScheme"; import { colorRamp, createColorScheme } from "./common/ramps"; const name = "Solarized"; -const author = "Ethan Schoonover"; -const url = "https://github.com/altercation/solarized"; -const license = { - type: "MIT", - url: "https://github.com/altercation/solarized/blob/master/README.md", -}; const ramps = { neutral: chroma @@ -34,3 +29,15 @@ const ramps = { export const dark = createColorScheme(`${name} Dark`, false, ramps); export const light = createColorScheme(`${name} Light`, true, ramps); + +export const meta: Metadata = { + name, + author: "Ethan Schoonover", + license: { + SPDX: "MIT", + https_url: "https://raw.githubusercontent.com/altercation/solarized/master/LICENSE", + license_checksum: "494aefdabf86acce06bd63001ad8aedad4ee38da23509d3f917d95aa3368b9a6" + }, + url: "https://github.com/altercation/solarized" +} + diff --git a/styles/src/themes/summercamp.ts b/styles/src/themes/summercamp.ts index bc5b7e1d24..60e0b1834d 100644 --- a/styles/src/themes/summercamp.ts +++ b/styles/src/themes/summercamp.ts @@ -1,13 +1,8 @@ import chroma from "chroma-js"; +import { Meta } from "./common/colorScheme"; import { colorRamp, createColorScheme } from "./common/ramps"; const name = "Summercamp"; -const author = "zoefiri"; -const url = "https://github.com/zoefiri/base16-sc"; -const license = { - type: "MIT", - url: "https://github.com/zoefiri/base16-sc/blob/master/summercamp.yaml", -}; const ramps = { neutral: chroma @@ -33,3 +28,13 @@ const ramps = { }; export const dark = createColorScheme(`${name}`, false, ramps); +export const meta: Meta = { + name, + author: "zoefiri", + url: "https://github.com/zoefiri/base16-sc", + license: { + SPDX: "MIT", + https_url: "https://raw.githubusercontent.com/zoefiri/base16-sc/master/LICENSE", + license_checksum: "fadcc834b7eaf2943800956600e8aeea4b495ecf6490f4c4b6c91556a90accaf" + } +} \ No newline at end of file From 3a1d533c01b308897873cdca65bef9bcf0dba6f2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 26 Jan 2023 18:15:34 -0800 Subject: [PATCH 049/180] Combine both license generations into one file --- crates/zed/src/menus.rs | 6 +----- crates/zed/src/zed.rs | 22 +++------------------- script/generate-licenses | 33 ++++++++++++++++++++------------- script/licenses/template.hbs.md | 12 +++--------- styles/src/buildLicenses.ts | 9 ++++----- 5 files changed, 31 insertions(+), 51 deletions(-) diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 5c84f78375..85ab62af73 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -339,11 +339,7 @@ pub fn menus() -> Vec> { }, MenuItem::Action { name: "View Dependency Licenses", - action: Box::new(crate::OpenSoftwareLicenses), - }, - MenuItem::Action { - name: "View Theme Licenses", - action: Box::new(crate::OpenThemeLicenses), + action: Box::new(crate::OpenLicenses), }, MenuItem::Separator, MenuItem::Action { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d3e1d3d618..793172a111 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -57,8 +57,7 @@ actions!( DebugElements, OpenSettings, OpenLog, - OpenSoftwareLicenses, - OpenThemeLicenses, + OpenLicenses, OpenTelemetryLog, OpenKeymap, OpenDefaultSettings, @@ -180,32 +179,17 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { }); cx.add_action({ let app_state = app_state.clone(); - move |workspace: &mut Workspace, - _: &OpenSoftwareLicenses, - cx: &mut ViewContext| { + move |workspace: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext| { open_bundled_file( workspace, app_state.clone(), - "software_licenses.md", + "licenses.md", "Open Source License Attribution", "Markdown", cx, ); } }); - cx.add_action({ - let app_state = app_state.clone(); - move |workspace: &mut Workspace, _: &OpenThemeLicenses, cx: &mut ViewContext| { - open_bundled_file( - workspace, - app_state.clone(), - "theme_licenses.md", - "Theme License Attribution", - "Markdown", - cx, - ); - } - }); cx.add_action({ let app_state = app_state.clone(); move |workspace: &mut Workspace, _: &OpenTelemetryLog, cx: &mut ViewContext| { diff --git a/script/generate-licenses b/script/generate-licenses index f8a718bb1b..5147929cb8 100755 --- a/script/generate-licenses +++ b/script/generate-licenses @@ -2,20 +2,27 @@ set -e +OUTPUT_FILE=$(pwd)/assets/licenses.md + +> $OUTPUT_FILE + +echo -e "# ###### THEME LICENSES ######\n" >> $OUTPUT_FILE + +echo "Generating theme licenses" +cd styles +npm run --silent build-licenses >> $OUTPUT_FILE +cd .. + +echo -e "# ###### CODE LICENSES ######\n" >> $OUTPUT_FILE + [[ "$(cargo about --version)" == "cargo-about 0.5.2" ]] || cargo install cargo-about --locked --git https://github.com/zed-industries/cargo-about --branch error-code-on-warn echo "Generating cargo licenses" -cargo about generate --fail-on-missing-license -o assets/software_licenses.md -c script/licenses/zed-licenses.toml script/licenses/template.hbs.md +cargo about generate --fail-on-missing-license -c script/licenses/zed-licenses.toml script/licenses/template.hbs.md >> $OUTPUT_FILE -# cargo about automatically html-escapes all output, so we need to undo it here: -sed -i '' 's/"/"/g' assets/software_licenses.md -sed -i '' 's/'/'\''/g' assets/software_licenses.md # `'\''` ends the string, appends a single quote, and re-opens the string -sed -i '' 's/=/=/g' assets/software_licenses.md -sed -i '' 's/`/`/g' assets/software_licenses.md -sed -i '' 's/<//g' assets/software_licenses.md - -# Now make theme licenses -echo "Generating theme licenses" -cd styles -npm run build-licenses \ No newline at end of file +sed -i '' 's/"/"/g' $OUTPUT_FILE +sed -i '' 's/'/'\''/g' $OUTPUT_FILE # The ` '\'' ` thing ends the string, appends a single quote, and re-opens the string +sed -i '' 's/=/=/g' $OUTPUT_FILE +sed -i '' 's/`/`/g' $OUTPUT_FILE +sed -i '' 's/<//g' $OUTPUT_FILE \ No newline at end of file diff --git a/script/licenses/template.hbs.md b/script/licenses/template.hbs.md index a51b714dae..a41aee8a4c 100644 --- a/script/licenses/template.hbs.md +++ b/script/licenses/template.hbs.md @@ -1,20 +1,15 @@ -# Third Party Licenses - -This page lists the licenses of the projects used in Zed. - ## Overview of licenses: {{#each overview}} * {{name}} ({{count}}) {{/each}} -## All license texts: - +### All license texts: {{#each licenses}} -### {{name}} +#### {{name}} -#### Used by: +##### Used by: {{#each used_by}} * [{{crate.name}} {{crate.version}}]({{#if crate.repository}} {{crate.repository}} {{else}} https://crates.io/crates/{{crate.name}} {{/if}}) @@ -23,5 +18,4 @@ This page lists the licenses of the projects used in Zed. {{text}} -------------------------------------------------------------------------------- - {{/each}} \ No newline at end of file diff --git a/styles/src/buildLicenses.ts b/styles/src/buildLicenses.ts index 5cf1d02dbf..e83496d91d 100644 --- a/styles/src/buildLicenses.ts +++ b/styles/src/buildLicenses.ts @@ -6,7 +6,7 @@ import { import { Meta } from "./themes/common/colorScheme"; import https from "https"; import crypto from "crypto"; -const license_file = `${__dirname}/../../assets/theme_licenses.md` + const accepted_licenses_file = `${__dirname}/../../script/licenses/zed-licenses.toml` // Use the cargo-about configuration file as the source of truth for supported licenses. @@ -62,14 +62,13 @@ function getLicenseText(schemeMeta: Meta[], callback: (meta: Meta, license_text: } } -function writeLicense(schemeMeta: Meta, text: String, stream: fs.WriteStream) { - stream.write(`# [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n******************************************************************************** \n`) +function writeLicense(schemeMeta: Meta, text: String) { + process.stdout.write(`## [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n********************************************************************************\n\n`) } const accepted_licenses = parseAcceptedToml(accepted_licenses_file); checkLicenses(schemeMeta, accepted_licenses) -const stream = fs.createWriteStream(license_file); getLicenseText(schemeMeta, (meta, text) => { - writeLicense(meta, text, stream) + writeLicense(meta, text) }); From 647d9861b1e077602921ce8338153fcd5a84e080 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Jan 2023 09:50:59 +0100 Subject: [PATCH 050/180] Abort collaboration process if any thread panics --- Cargo.toml | 2 -- Dockerfile | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 77469c0623..39e4cd6367 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,5 +84,3 @@ split-debuginfo = "unpacked" [profile.release] debug = true - - diff --git a/Dockerfile b/Dockerfile index 5a6279a95e..d3170696c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ WORKDIR app COPY . . # Compile collab server +ARG CARGO_PROFILE_RELEASE_PANIC=abort RUN --mount=type=cache,target=./script/node_modules \ --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=./target \ From 5431488a9a09a18013d6eac93c5d05bc4eddae13 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Jan 2023 11:07:12 +0100 Subject: [PATCH 051/180] collab 0.5.4 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fec8e8dc50..09eac8c264 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1132,7 +1132,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.3" +version = "0.5.4" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 456bcf6531..9301a1974a 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.3" +version = "0.5.4" publish = false [[bin]] From 89a5506f43dcd7d982e20f7c37883c06a04f6265 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 27 Jan 2023 12:39:32 -0800 Subject: [PATCH 052/180] Add function which checks if a child of a view is focused and use that to only focus item updates from the leader when that the active item was focused --- crates/gpui/src/app.rs | 41 ++++++++++++++++++++----------- crates/workspace/src/workspace.rs | 18 +++++++++----- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 3fc11a6b58..cad800a38c 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1414,21 +1414,6 @@ impl MutableAppContext { true } - /// Returns an iterator over all of the view ids from the passed view up to the root of the window - /// Includes the passed view itself - fn ancestors(&self, window_id: usize, mut view_id: usize) -> impl Iterator + '_ { - std::iter::once(view_id) - .into_iter() - .chain(std::iter::from_fn(move || { - if let Some(ParentId::View(parent_id)) = self.parents.get(&(window_id, view_id)) { - view_id = *parent_id; - Some(view_id) - } else { - None - } - })) - } - fn actions_mut( &mut self, capture_phase: bool, @@ -2733,6 +2718,32 @@ impl AppContext { panic!("no global has been added for {}", type_name::()); } } + + /// Returns an iterator over all of the view ids from the passed view up to the root of the window + /// Includes the passed view itself + fn ancestors(&self, window_id: usize, mut view_id: usize) -> impl Iterator + '_ { + std::iter::once(view_id) + .into_iter() + .chain(std::iter::from_fn(move || { + if let Some(ParentId::View(parent_id)) = self.parents.get(&(window_id, view_id)) { + view_id = *parent_id; + Some(view_id) + } else { + None + } + })) + } + + pub fn is_child_focused(&self, view: impl Into) -> bool { + let view = view.into(); + if let Some(focused_view_id) = self.focused_view_id(view.window_id) { + self.ancestors(view.window_id, focused_view_id) + .skip(1) // Skip self id + .any(|parent| parent == view.view_id) + } else { + false + } + } } impl ReadModel for AppContext { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b90c62aca1..9c64eada5a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2140,7 +2140,7 @@ impl Workspace { let call = self.active_call()?; let room = call.read(cx).room()?.read(cx); let participant = room.remote_participant_for_peer_id(leader_id)?; - let mut items_to_add = Vec::new(); + let mut items_to_activate = Vec::new(); match participant.location { call::ParticipantLocation::SharedProject { project_id } => { if Some(project_id) == self.project.read(cx).remote_id() { @@ -2149,12 +2149,12 @@ impl Workspace { .active_view_id .and_then(|id| state.items_by_leader_view_id.get(&id)) { - items_to_add.push((pane.clone(), item.boxed_clone())); + items_to_activate.push((pane.clone(), item.boxed_clone())); } else { if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { - items_to_add.push((pane.clone(), Box::new(shared_screen))); + items_to_activate.push((pane.clone(), Box::new(shared_screen))); } } } @@ -2164,20 +2164,26 @@ impl Workspace { call::ParticipantLocation::External => { for (pane, _) in self.follower_states_by_leader.get(&leader_id)? { if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { - items_to_add.push((pane.clone(), Box::new(shared_screen))); + items_to_activate.push((pane.clone(), Box::new(shared_screen))); } } } } - for (pane, item) in items_to_add { + for (pane, item) in items_to_activate { + let active_item_was_focused = pane + .read(cx) + .active_item() + .map(|active_item| cx.is_child_focused(active_item.to_any())) + .unwrap_or_default(); + if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); } else { Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx); } - if pane == self.active_pane { + if active_item_was_focused { pane.update(cx, |pane, cx| pane.focus_active_item(cx)); } } From d6acea525df660d2ec2bdfe85b0d1cc7029865c6 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 27 Jan 2023 13:00:26 -0800 Subject: [PATCH 053/180] add test for is_child_focused --- crates/gpui/src/app.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index cad800a38c..9b1e13910d 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -6394,6 +6394,8 @@ mod tests { cx.focus(&view_1); cx.focus(&view_2); }); + assert!(cx.is_child_focused(view_1.clone())); + assert!(!cx.is_child_focused(view_2.clone())); assert_eq!( mem::take(&mut *view_events.lock()), [ @@ -6418,6 +6420,8 @@ mod tests { ); view_1.update(cx, |_, cx| cx.focus(&view_1)); + assert!(!cx.is_child_focused(view_1.clone())); + assert!(!cx.is_child_focused(view_2.clone())); assert_eq!( mem::take(&mut *view_events.lock()), ["view 2 blurred", "view 1 focused"], From 77a4f907a00eb0ef461111808487c8b8d9141628 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 27 Jan 2023 13:43:36 -0800 Subject: [PATCH 054/180] removed invalid focus assertion --- crates/collab/src/tests/integration_tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index a645d6dc71..9db5996b39 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -5623,7 +5623,6 @@ async fn test_following( .downcast::() .unwrap() }); - assert!(cx_b.read(|cx| editor_b2.is_focused(cx))); assert_eq!( cx_b.read(|cx| editor_b2.project_path(cx)), Some((worktree_id, "2.txt").into()) From 0f93386071ec5a7f3b0f9293136a757735738aeb Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 27 Jan 2023 15:07:51 -0800 Subject: [PATCH 055/180] Add run until parked to test_fs_operations to ensure both update chunks are completed before asserting the changes --- crates/collab/src/tests/integration_tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index a645d6dc71..1818361be3 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2571,6 +2571,8 @@ async fn test_fs_operations( }) .await .unwrap(); + deterministic.run_until_parked(); + worktree_a.read_with(cx_a, |worktree, _| { assert_eq!( worktree From e530406d62d21a7c42df43a4323818b66eb3ac49 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 27 Jan 2023 15:24:21 -0800 Subject: [PATCH 056/180] Add an install step to the CI build script --- script/generate-licenses | 1 + 1 file changed, 1 insertion(+) diff --git a/script/generate-licenses b/script/generate-licenses index 5147929cb8..8a41f55c02 100755 --- a/script/generate-licenses +++ b/script/generate-licenses @@ -10,6 +10,7 @@ echo -e "# ###### THEME LICENSES ######\n" >> $OUTPUT_FILE echo "Generating theme licenses" cd styles +npm ci npm run --silent build-licenses >> $OUTPUT_FILE cd .. From ca2e0256e1f1f2d5f3695a901e2363797eede81d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 23 Jan 2023 13:40:34 -0800 Subject: [PATCH 057/180] Renamed open recent action to match menu --- assets/keymaps/default.json | 2 +- crates/recent_projects/src/recent_projects.rs | 4 ++-- crates/zed/src/menus.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index a0f437cf91..ea8c341461 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -38,7 +38,7 @@ "cmd-n": "workspace::NewFile", "cmd-shift-n": "workspace::NewWindow", "cmd-o": "workspace::Open", - "alt-cmd-o": "recent_projects::Toggle", + "alt-cmd-o": "projects::OpenRecent", "ctrl-`": "workspace::NewTerminal" } }, diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 02e15290ab..d7de7ae718 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -13,7 +13,7 @@ use picker::{Picker, PickerDelegate}; use settings::Settings; use workspace::{OpenPaths, Workspace, WorkspaceLocation, WORKSPACE_DB}; -actions!(recent_projects, [Toggle]); +actions!(projects, [OpenRecent]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(RecentProjectsView::toggle); @@ -40,7 +40,7 @@ impl RecentProjectsView { } } - fn toggle(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { + fn toggle(_: &mut Workspace, _: &OpenRecent, cx: &mut ViewContext) { cx.spawn(|workspace, mut cx| async move { let workspace_locations = cx .background() diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 85ab62af73..d238514917 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -81,7 +81,7 @@ pub fn menus() -> Vec> { }, MenuItem::Action { name: "Open Recent...", - action: Box::new(recent_projects::Toggle), + action: Box::new(recent_projects::OpenRecent), }, MenuItem::Separator, MenuItem::Action { From ea39983f7858a978e276357a99f8942d12c5913d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 23 Jan 2023 14:31:10 -0800 Subject: [PATCH 058/180] Removed old experiments settings and staff mode flag, added new StaffMode global that is set based on the webserver's staff bit --- .dockerignore | 2 +- .gitignore | 3 +-- Cargo.lock | 1 + README.md | 25 ++----------------- assets/keymaps/internal.json | 1 - crates/client/src/user.rs | 11 +++++++- crates/collab/src/tests.rs | 3 +-- crates/fs/src/fs.rs | 11 -------- crates/project/src/project.rs | 1 - crates/project/src/worktree.rs | 7 +++--- crates/settings/src/keymap_file.rs | 13 ++-------- crates/settings/src/settings.rs | 24 ------------------ crates/theme/src/theme_registry.rs | 13 +++------- crates/theme_selector/Cargo.toml | 1 + crates/theme_selector/src/theme_selector.rs | 6 ++--- crates/util/src/paths.rs | 13 +++++++++- crates/workspace/src/workspace.rs | 3 --- crates/zed/src/main.rs | 14 ++++++++--- crates/zed/src/zed.rs | 15 ++++++----- styles/src/buildThemes.ts | 11 +++----- styles/src/colorSchemes.ts | 12 +++------ styles/src/themes/experiments/.gitkeep | 0 styles/src/themes/internal/.gitkeep | 0 .../src/themes/staff}/.gitkeep | 0 .../themes/{experiments => staff}/abruzzo.ts | 0 .../{internal => staff}/atelier-dune.ts | 0 .../{internal => staff}/atelier-heath.ts | 0 .../{internal => staff}/atelier-seaside.ts | 0 .../themes/{internal => staff}/ayu-mirage.ts | 0 styles/src/themes/{internal => staff}/ayu.ts | 0 .../{experiments => staff}/brushtrees.ts | 0 .../src/themes/{internal => staff}/dracula.ts | 0 .../{internal => staff}/gruvbox-medium.ts | 0 .../src/themes/{internal => staff}/monokai.ts | 0 styles/src/themes/{internal => staff}/nord.ts | 0 .../src/themes/{internal => staff}/seti-ui.ts | 0 .../{internal => staff}/tokyo-night-storm.ts | 0 .../themes/{internal => staff}/tokyo-night.ts | 0 .../src/themes/{internal => staff}/zed-pro.ts | 0 .../src/themes/{internal => staff}/zenburn.ts | 0 40 files changed, 66 insertions(+), 124 deletions(-) delete mode 100644 assets/keymaps/internal.json delete mode 100644 styles/src/themes/experiments/.gitkeep delete mode 100644 styles/src/themes/internal/.gitkeep rename {assets/keymaps/experiments => styles/src/themes/staff}/.gitkeep (100%) rename styles/src/themes/{experiments => staff}/abruzzo.ts (100%) rename styles/src/themes/{internal => staff}/atelier-dune.ts (100%) rename styles/src/themes/{internal => staff}/atelier-heath.ts (100%) rename styles/src/themes/{internal => staff}/atelier-seaside.ts (100%) rename styles/src/themes/{internal => staff}/ayu-mirage.ts (100%) rename styles/src/themes/{internal => staff}/ayu.ts (100%) rename styles/src/themes/{experiments => staff}/brushtrees.ts (100%) rename styles/src/themes/{internal => staff}/dracula.ts (100%) rename styles/src/themes/{internal => staff}/gruvbox-medium.ts (100%) rename styles/src/themes/{internal => staff}/monokai.ts (100%) rename styles/src/themes/{internal => staff}/nord.ts (100%) rename styles/src/themes/{internal => staff}/seti-ui.ts (100%) rename styles/src/themes/{internal => staff}/tokyo-night-storm.ts (100%) rename styles/src/themes/{internal => staff}/tokyo-night.ts (100%) rename styles/src/themes/{internal => staff}/zed-pro.ts (100%) rename styles/src/themes/{internal => staff}/zenburn.ts (100%) diff --git a/.dockerignore b/.dockerignore index add07b4bf7..d89a9d83e2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,4 @@ crates/collab/static/styles.css vendor/bin assets/themes/*.json assets/themes/internal/*.json -assets/themes/experiments/*.json +assets/themes/staff/*.json diff --git a/.gitignore b/.gitignore index 69dbbd3e1c..5a4d2ff25e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,8 @@ /crates/collab/static/styles.css /vendor/bin /assets/themes/*.json -/assets/themes/Internal/*.json -/assets/themes/Experiments/*.json /assets/*licenses.md +/assets/themes/staff/*.json **/venv .build Packages diff --git a/Cargo.lock b/Cargo.lock index 0c0744d3a9..914e61226d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6466,6 +6466,7 @@ dependencies = [ "settings", "smol", "theme", + "util", "workspace", ] diff --git a/README.md b/README.md index 24614e97c2..af8eeffb9c 100644 --- a/README.md +++ b/README.md @@ -49,30 +49,9 @@ script/zed-with-local-servers --release If you trigger `cmd-alt-i`, Zed will copy a JSON representation of the current window contents to the clipboard. You can paste this in a tool like [DJSON](https://chrome.google.com/webstore/detail/djson-json-viewer-formatt/chaeijjekipecdajnijdldjjipaegdjc?hl=en) to navigate the state of on-screen elements in a structured way. -### Staff Only Features +### Licensing -Many features (e.g. the terminal) take significant time and effort before they are polished enough to be released to even Alpha users. But Zed's team workflow relies on fast, daily PRs and there can be large merge conflicts for feature branchs that diverge for a few days. To bridge this gap, there is a `staff_mode` field in the Settings that staff can set to enable these unpolished or incomplete features. Note that this setting isn't leaked via autocompletion, but there is no mechanism to stop users from setting this anyway. As initilization of Zed components is only done once, on startup, setting `staff_mode` may require a restart to take effect. You can set staff only key bindings in the `assets/keymaps/internal.json` file, and add staff only themes in the `styles/src/themes/internal` directory - -### Experimental Features - -A user facing feature flag can be added to Zed by: - -* Adding a setting to the crates/settings/src/settings.rs FeatureFlags struct. Use a boolean for a simple on/off, or use a struct to experiment with different configuration options. -* If the feature needs keybindings, add a file to the `assets/keymaps/experiments/` folder, then update the `FeatureFlags::keymap_files()` method to check for your feature's flag and add it's keybindings's path to the method's list. -* If you want to add an experimental theme, add it to the `styles/src/themes/experiments` folder - -The Settings global should be initialized with the user's feature flags by the time the feature's `init(cx)` equivalent is called. - -To promote an experimental feature to a full feature: - -* If this is an experimental theme, move the theme file from the `styles/src/themes/experiments` folder to the `styles/src/themes/` folder -* Take the features settings (if any) and add them under a new variable in the Settings struct. Don't forget to add a `merge()` call in `set_user_settings()`! -* Take the feature's keybindings and add them to the default.json (or equivalent) file -* Remove the file from the `FeatureFlags::keymap_files()` method -* Remove the conditional in the feature's `init(cx)` equivalent. - - -That's it 😸 +We use cargo-about to automatically comply with open source licenses. If CI is failing due to an unsupported license, first check whether this system is able to support this license type; ask a lawyer if you're unsure. Once you've verified the license, go to `script/licenses/zed-licenses.toml` and add the associated `accepted` SPDX identifier. If cargo about cannot find the license for the dependency at all, add a clarification field at the end of the file, as specified in the [cargo-about book](https://embarkstudios.github.io/cargo-about/cli/generate/config.html#crate-configuration). ### Wasm Plugins diff --git a/assets/keymaps/internal.json b/assets/keymaps/internal.json deleted file mode 100644 index 0637a088a0..0000000000 --- a/assets/keymaps/internal.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 1201665571..ab95261461 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -7,7 +7,7 @@ use postage::{sink::Sink, watch}; use rpc::proto::{RequestMessage, UsersResponse}; use settings::Settings; use std::sync::{Arc, Weak}; -use util::TryFutureExt as _; +use util::{paths::StaffMode, TryFutureExt as _}; #[derive(Default, Debug)] pub struct User { @@ -148,6 +148,15 @@ impl UserStore { cx.read(|cx| cx.global::().telemetry()), ); + cx.update(|cx| { + cx.update_global::(|staff_mode, _| { + *staff_mode = info + .as_ref() + .map(|info| StaffMode(info.staff)) + .unwrap_or(StaffMode(false)); + }) + }); + current_user_tx.send(user).await.ok(); } } diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 120c577e0f..e9ffecf246 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -11,7 +11,7 @@ use client::{ EstablishConnectionError, UserStore, }; use collections::{HashMap, HashSet}; -use fs::{FakeFs, HomeDir}; +use fs::FakeFs; use futures::{channel::oneshot, StreamExt as _}; use gpui::{ executor::Deterministic, test::EmptyView, ModelHandle, Task, TestAppContext, ViewHandle, @@ -100,7 +100,6 @@ impl TestServer { async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient { cx.update(|cx| { - cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf())); cx.set_global(Settings::test(cx)); }); diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 6a79953f41..f640f35036 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -13,7 +13,6 @@ use smol::io::{AsyncReadExt, AsyncWriteExt}; use std::borrow::Cow; use std::cmp; use std::io::Write; -use std::ops::Deref; use std::sync::Arc; use std::{ io, @@ -94,16 +93,6 @@ impl LineEnding { } } -pub struct HomeDir(pub PathBuf); - -impl Deref for HomeDir { - type Target = PathBuf; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - #[async_trait::async_trait] pub trait Fs: Send + Sync { async fn create_dir(&self, path: &Path) -> Result<()>; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 54939af8d8..8f6b867b12 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -550,7 +550,6 @@ impl Project { if !cx.read(|cx| cx.has_global::()) { cx.update(|cx| { cx.set_global(Settings::test(cx)); - cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf())) }); } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index b65cf9e39b..d743ba2031 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -5,8 +5,8 @@ use anyhow::{anyhow, Context, Result}; use client::{proto, Client}; use clock::ReplicaId; use collections::{HashMap, VecDeque}; +use fs::LineEnding; use fs::{repository::GitRepository, Fs}; -use fs::{HomeDir, LineEnding}; use futures::{ channel::{ mpsc::{self, UnboundedSender}, @@ -49,6 +49,7 @@ use std::{ time::{Duration, SystemTime}, }; use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; +use util::paths::HOME; use util::{ResultExt, TryFutureExt}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] @@ -1831,9 +1832,9 @@ impl language::File for File { } else { let path = worktree.abs_path(); - if worktree.is_local() && path.starts_with(cx.global::().as_path()) { + if worktree.is_local() && path.starts_with(HOME.as_path()) { full_path.push("~"); - full_path.push(path.strip_prefix(cx.global::().as_path()).unwrap()); + full_path.push(path.strip_prefix(HOME.as_path()).unwrap()); } else { full_path.push(path) } diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 4090bcc63a..01992d9431 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -1,4 +1,4 @@ -use crate::{parse_json_with_comments, Settings}; +use crate::parse_json_with_comments; use anyhow::{Context, Result}; use assets::Assets; use collections::BTreeMap; @@ -42,16 +42,7 @@ struct ActionWithData(Box, Box); impl KeymapFileContent { pub fn load_defaults(cx: &mut MutableAppContext) { - let settings = cx.global::(); - let mut paths = vec!["keymaps/default.json", "keymaps/vim.json"]; - - if settings.staff_mode { - paths.push("keymaps/internal.json") - } - - paths.extend(settings.experiments.keymap_files()); - - for path in paths { + for path in ["keymaps/default.json", "keymaps/vim.json"] { Self::load(path, cx).unwrap(); } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 6e25222d96..a184c9a929 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -27,7 +27,6 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; #[derive(Clone)] pub struct Settings { - pub experiments: FeatureFlags, pub buffer_font_family: FamilyId, pub default_buffer_font_size: f32, pub buffer_font_size: f32, @@ -53,7 +52,6 @@ pub struct Settings { pub theme: Arc, pub telemetry_defaults: TelemetrySettings, pub telemetry_overrides: TelemetrySettings, - pub staff_mode: bool, } #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -71,17 +69,6 @@ impl TelemetrySettings { } } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct FeatureFlags { - pub experimental_themes: bool, -} - -impl FeatureFlags { - pub fn keymap_files(&self) -> Vec<&'static str> { - vec![] - } -} - #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct GitSettings { pub git_gutter: Option, @@ -283,7 +270,6 @@ impl Column for DockAnchor { #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct SettingsFileContent { - pub experiments: Option, #[serde(default)] pub projects_online_by_default: Option, #[serde(default)] @@ -323,8 +309,6 @@ pub struct SettingsFileContent { pub theme: Option, #[serde(default)] pub telemetry: TelemetrySettings, - #[serde(default)] - pub staff_mode: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -352,7 +336,6 @@ impl Settings { .unwrap(); Self { - experiments: FeatureFlags::default(), buffer_font_family: font_cache .load_family(&[defaults.buffer_font_family.as_ref().unwrap()]) .unwrap(), @@ -388,7 +371,6 @@ impl Settings { theme: themes.get(&defaults.theme.unwrap()).unwrap(), telemetry_defaults: defaults.telemetry, telemetry_overrides: Default::default(), - staff_mode: false, } } @@ -425,8 +407,6 @@ impl Settings { ); merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); - merge(&mut self.experiments, data.experiments); - merge(&mut self.staff_mode, data.staff_mode); merge(&mut self.default_dock_anchor, data.default_dock_anchor); // Ensure terminal font is loaded, so we can request it in terminal_element layout @@ -552,7 +532,6 @@ impl Settings { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &gpui::AppContext) -> Settings { Settings { - experiments: FeatureFlags::default(), buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(), buffer_font_size: 14., active_pane_magnification: 1., @@ -589,7 +568,6 @@ impl Settings { metrics: Some(true), }, telemetry_overrides: Default::default(), - staff_mode: false, } } @@ -647,8 +625,6 @@ pub fn settings_file_json_schema( ]); let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap(); - // Avoid automcomplete for non-user facing settings - root_schema_object.properties.remove("staff_mode"); root_schema_object.properties.extend([ ( "theme".to_owned(), diff --git a/crates/theme/src/theme_registry.rs b/crates/theme/src/theme_registry.rs index 3d4783604d..cc5e5490af 100644 --- a/crates/theme/src/theme_registry.rs +++ b/crates/theme/src/theme_registry.rs @@ -22,20 +22,13 @@ impl ThemeRegistry { }) } - pub fn list(&self, internal: bool, experiments: bool) -> impl Iterator + '_ { + pub fn list(&self, staff: bool) -> impl Iterator + '_ { let mut dirs = self.assets.list("themes/"); - if !internal { + if !staff { dirs = dirs .into_iter() - .filter(|path| !path.starts_with("themes/Internal")) - .collect() - } - - if !experiments { - dirs = dirs - .into_iter() - .filter(|path| !path.starts_with("themes/Experiments")) + .filter(|path| !path.starts_with("themes/staff")) .collect() } diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index 8f6fc74600..80ff311069 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -16,6 +16,7 @@ picker = { path = "../picker" } theme = { path = "../theme" } settings = { path = "../settings" } workspace = { path = "../workspace" } +util = { path = "../util" } log = { version = "0.4.16", features = ["kv_unstable_serde"] } parking_lot = "0.11.1" postage = { version = "0.4.1", features = ["futures-traits"] } diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 252a64c7fd..45dc36a3ad 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -7,6 +7,7 @@ use picker::{Picker, PickerDelegate}; use settings::{settings_file::SettingsFile, Settings}; use std::sync::Arc; use theme::{Theme, ThemeMeta, ThemeRegistry}; +use util::paths::StaffMode; use workspace::{AppState, Workspace}; pub struct ThemeSelector { @@ -44,10 +45,7 @@ impl ThemeSelector { let original_theme = settings.theme.clone(); let mut theme_names = registry - .list( - settings.staff_mode, - settings.experiments.experimental_themes, - ) + .list(**cx.global::()) .collect::>(); theme_names.sort_unstable_by(|a, b| { a.is_light diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 8698d6891e..94af0aa75d 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -1,4 +1,15 @@ -use std::path::PathBuf; +use std::{ops::Deref, path::PathBuf}; + +#[derive(Debug)] +pub struct StaffMode(pub bool); + +impl Deref for StaffMode { + type Target = bool; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} lazy_static::lazy_static! { pub static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory"); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e5f22d1169..0abc25724c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -349,9 +349,6 @@ pub struct AppState { impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut MutableAppContext) -> Arc { - use fs::HomeDir; - - cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf())); let settings = Settings::test(cx); cx.set_global(settings); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 56f259339c..4cef2c0775 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -24,7 +24,7 @@ use isahc::{config::Configurable, Request}; use language::LanguageRegistry; use log::LevelFilter; use parking_lot::Mutex; -use project::{Fs, HomeDir}; +use project::Fs; use serde_json::json; use settings::{ self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent, @@ -39,7 +39,11 @@ use terminal_view::{get_working_directory, TerminalView}; use fs::RealFs; use settings::watched_json::{watch_keymap_file, watch_settings_file, WatchedJsonFile}; use theme::ThemeRegistry; -use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; +use util::{ + channel::RELEASE_CHANNEL, + paths::{self, StaffMode}, + ResultExt, TryFutureExt, +}; use workspace::{ self, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, OpenPaths, Workspace, }; @@ -104,7 +108,11 @@ fn main() { app.run(move |cx| { cx.set_global(*RELEASE_CHANNEL); - cx.set_global(HomeDir(paths::HOME.to_path_buf())); + + #[cfg(not(debug_assertions))] + cx.set_global(StaffMode(false)); + #[cfg(debug_assertions)] + cx.set_global(StaffMode(true)); let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index a8773d2c0b..af7dfdaee0 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -32,7 +32,11 @@ use serde::Deserialize; use serde_json::to_string_pretty; use settings::{keymap_file_json_schema, settings_file_json_schema, Settings}; use std::{borrow::Cow, env, path::Path, str, sync::Arc}; -use util::{channel::ReleaseChannel, paths, ResultExt}; +use util::{ + channel::ReleaseChannel, + paths::{self, StaffMode}, + ResultExt, +}; use uuid::Uuid; pub use workspace; use workspace::{sidebar::SidebarSide, AppState, Workspace}; @@ -297,14 +301,9 @@ pub fn initialize_workspace( cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); cx.emit(workspace::Event::PaneAdded(workspace.dock_pane().clone())); - let settings = cx.global::(); - let theme_names = app_state .themes - .list( - settings.staff_mode, - settings.experiments.experimental_themes, - ) + .list(**cx.global::()) .map(|meta| meta.name) .collect(); let language_names = app_state.languages.language_names(); @@ -1868,7 +1867,7 @@ mod tests { let settings = Settings::defaults(Assets, cx.font_cache(), &themes); let mut has_default_theme = false; - for theme_name in themes.list(false, false).map(|meta| meta.name) { + for theme_name in themes.list(false).map(|meta| meta.name) { let theme = themes.get(&theme_name).unwrap(); if theme.meta.name == settings.theme.meta.name { has_default_theme = true; diff --git a/styles/src/buildThemes.ts b/styles/src/buildThemes.ts index 6512d13a47..4bb7b8fc09 100644 --- a/styles/src/buildThemes.ts +++ b/styles/src/buildThemes.ts @@ -2,7 +2,7 @@ import * as fs from "fs"; import { tmpdir } from "os"; import * as path from "path"; import colorSchemes, { - experimentalColorSchemes, internalColorSchemes + staffColorSchemes, } from "./colorSchemes"; import app from "./styleTree/app"; import { ColorScheme } from "./themes/common/colorScheme"; @@ -10,8 +10,7 @@ import snakeCase from "./utils/snakeCase"; const assetsDirectory = `${__dirname}/../../assets` const themeDirectory = `${assetsDirectory}/themes`; -const internalDirectory = `${themeDirectory}/Internal`; -const experimentsDirectory = `${themeDirectory}/Experiments`; +const staffDirectory = `${themeDirectory}/staff`; const tempDirectory = fs.mkdtempSync(path.join(tmpdir(), "build-themes")); @@ -32,8 +31,7 @@ function clearThemes(themeDirectory: string) { } clearThemes(themeDirectory); -clearThemes(internalDirectory); -clearThemes(experimentsDirectory); +clearThemes(staffDirectory); function writeThemes(colorSchemes: ColorScheme[], outputDirectory: string) { for (let colorScheme of colorSchemes) { @@ -49,5 +47,4 @@ function writeThemes(colorSchemes: ColorScheme[], outputDirectory: string) { // Write new themes to theme directory writeThemes(colorSchemes, themeDirectory); -writeThemes(internalColorSchemes, internalDirectory); -writeThemes(experimentalColorSchemes, experimentsDirectory); \ No newline at end of file +writeThemes(staffColorSchemes, staffDirectory); diff --git a/styles/src/colorSchemes.ts b/styles/src/colorSchemes.ts index fdf9b51ebe..c7e1d4ead7 100644 --- a/styles/src/colorSchemes.ts +++ b/styles/src/colorSchemes.ts @@ -8,8 +8,8 @@ export default colorSchemes; const schemeMeta: Meta[] = []; export { schemeMeta }; -const internalColorSchemes: ColorScheme[] = []; -export { internalColorSchemes }; +const staffColorSchemes: ColorScheme[] = []; +export { staffColorSchemes }; const experimentalColorSchemes: ColorScheme[] = []; export { experimentalColorSchemes }; @@ -37,12 +37,8 @@ function fillColorSchemes(themesPath: string, colorSchemes: ColorScheme[]) { fillColorSchemes(themes_directory, colorSchemes); fillColorSchemes( - path.resolve(`${themes_directory}/internal`), - internalColorSchemes -); -fillColorSchemes( - path.resolve(`${themes_directory}/experiments`), - experimentalColorSchemes + path.resolve(`${themes_directory}/staff`), + staffColorSchemes ); function fillMeta(themesPath: string, meta: Meta[]) { diff --git a/styles/src/themes/experiments/.gitkeep b/styles/src/themes/experiments/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/styles/src/themes/internal/.gitkeep b/styles/src/themes/internal/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/assets/keymaps/experiments/.gitkeep b/styles/src/themes/staff/.gitkeep similarity index 100% rename from assets/keymaps/experiments/.gitkeep rename to styles/src/themes/staff/.gitkeep diff --git a/styles/src/themes/experiments/abruzzo.ts b/styles/src/themes/staff/abruzzo.ts similarity index 100% rename from styles/src/themes/experiments/abruzzo.ts rename to styles/src/themes/staff/abruzzo.ts diff --git a/styles/src/themes/internal/atelier-dune.ts b/styles/src/themes/staff/atelier-dune.ts similarity index 100% rename from styles/src/themes/internal/atelier-dune.ts rename to styles/src/themes/staff/atelier-dune.ts diff --git a/styles/src/themes/internal/atelier-heath.ts b/styles/src/themes/staff/atelier-heath.ts similarity index 100% rename from styles/src/themes/internal/atelier-heath.ts rename to styles/src/themes/staff/atelier-heath.ts diff --git a/styles/src/themes/internal/atelier-seaside.ts b/styles/src/themes/staff/atelier-seaside.ts similarity index 100% rename from styles/src/themes/internal/atelier-seaside.ts rename to styles/src/themes/staff/atelier-seaside.ts diff --git a/styles/src/themes/internal/ayu-mirage.ts b/styles/src/themes/staff/ayu-mirage.ts similarity index 100% rename from styles/src/themes/internal/ayu-mirage.ts rename to styles/src/themes/staff/ayu-mirage.ts diff --git a/styles/src/themes/internal/ayu.ts b/styles/src/themes/staff/ayu.ts similarity index 100% rename from styles/src/themes/internal/ayu.ts rename to styles/src/themes/staff/ayu.ts diff --git a/styles/src/themes/experiments/brushtrees.ts b/styles/src/themes/staff/brushtrees.ts similarity index 100% rename from styles/src/themes/experiments/brushtrees.ts rename to styles/src/themes/staff/brushtrees.ts diff --git a/styles/src/themes/internal/dracula.ts b/styles/src/themes/staff/dracula.ts similarity index 100% rename from styles/src/themes/internal/dracula.ts rename to styles/src/themes/staff/dracula.ts diff --git a/styles/src/themes/internal/gruvbox-medium.ts b/styles/src/themes/staff/gruvbox-medium.ts similarity index 100% rename from styles/src/themes/internal/gruvbox-medium.ts rename to styles/src/themes/staff/gruvbox-medium.ts diff --git a/styles/src/themes/internal/monokai.ts b/styles/src/themes/staff/monokai.ts similarity index 100% rename from styles/src/themes/internal/monokai.ts rename to styles/src/themes/staff/monokai.ts diff --git a/styles/src/themes/internal/nord.ts b/styles/src/themes/staff/nord.ts similarity index 100% rename from styles/src/themes/internal/nord.ts rename to styles/src/themes/staff/nord.ts diff --git a/styles/src/themes/internal/seti-ui.ts b/styles/src/themes/staff/seti-ui.ts similarity index 100% rename from styles/src/themes/internal/seti-ui.ts rename to styles/src/themes/staff/seti-ui.ts diff --git a/styles/src/themes/internal/tokyo-night-storm.ts b/styles/src/themes/staff/tokyo-night-storm.ts similarity index 100% rename from styles/src/themes/internal/tokyo-night-storm.ts rename to styles/src/themes/staff/tokyo-night-storm.ts diff --git a/styles/src/themes/internal/tokyo-night.ts b/styles/src/themes/staff/tokyo-night.ts similarity index 100% rename from styles/src/themes/internal/tokyo-night.ts rename to styles/src/themes/staff/tokyo-night.ts diff --git a/styles/src/themes/internal/zed-pro.ts b/styles/src/themes/staff/zed-pro.ts similarity index 100% rename from styles/src/themes/internal/zed-pro.ts rename to styles/src/themes/staff/zed-pro.ts diff --git a/styles/src/themes/internal/zenburn.ts b/styles/src/themes/staff/zenburn.ts similarity index 100% rename from styles/src/themes/internal/zenburn.ts rename to styles/src/themes/staff/zenburn.ts From 2802e3a1c68f3585c5679052920a63e8f2203aa4 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 23 Jan 2023 15:12:31 -0800 Subject: [PATCH 059/180] Fixed failling tests --- crates/client/src/user.rs | 9 ++++----- crates/theme_selector/src/theme_selector.rs | 2 +- crates/zed/src/zed.rs | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index ab95261461..98ca06e1fc 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -149,12 +149,11 @@ impl UserStore { ); cx.update(|cx| { - cx.update_global::(|staff_mode, _| { - *staff_mode = info - .as_ref() + cx.set_global( + info.as_ref() .map(|info| StaffMode(info.staff)) - .unwrap_or(StaffMode(false)); - }) + .unwrap_or(StaffMode(false)), + ); }); current_user_tx.send(user).await.ok(); diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 45dc36a3ad..ad22baf8b9 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -45,7 +45,7 @@ impl ThemeSelector { let original_theme = settings.theme.clone(); let mut theme_names = registry - .list(**cx.global::()) + .list(**cx.try_global::().unwrap_or(&StaffMode(false))) .collect::>(); theme_names.sort_unstable_by(|a, b| { a.is_light diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index af7dfdaee0..d1ec3f2451 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -303,7 +303,7 @@ pub fn initialize_workspace( let theme_names = app_state .themes - .list(**cx.global::()) + .list(**cx.try_global::().unwrap_or(&StaffMode(false))) .map(|meta| meta.name) .collect(); let language_names = app_state.languages.language_names(); From 2d889f59bf65d02888053199a43c6f070392e8cd Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 23 Jan 2023 16:02:58 -0800 Subject: [PATCH 060/180] Rewrite license documentation to be more clear --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index af8eeffb9c..ff9b0fba15 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,12 @@ If you trigger `cmd-alt-i`, Zed will copy a JSON representation of the current w ### Licensing -We use cargo-about to automatically comply with open source licenses. If CI is failing due to an unsupported license, first check whether this system is able to support this license type; ask a lawyer if you're unsure. Once you've verified the license, go to `script/licenses/zed-licenses.toml` and add the associated `accepted` SPDX identifier. If cargo about cannot find the license for the dependency at all, add a clarification field at the end of the file, as specified in the [cargo-about book](https://embarkstudios.github.io/cargo-about/cli/generate/config.html#crate-configuration). +We use `[cargo-about](https://github.com/EmbarkStudios/cargo-about)` to automatically comply with open source licenses. If CI is failing, check the following: + +- Is it showing a `no license specified` error for a crate you've created? If so, add `publish = false` under `[package]` in your crate's Cargo.toml. +- Is the error `failed to satisfy license requirements` for a dependency? If so, first determine what license the project has and whether this system is sufficient to comply with this license's requirements. If you're unsure, ask a lawyer. Once you've verified that this system is acceptable add the license's SPDX identifier to the `accepted` array in `script/licenses/zed-licenses.toml`. +- Is `cargo-about` unable to find the license for a dependency? If so, add a clarification field at the end of `script/licenses/zed-licenses.toml`, as specified in the [cargo-about book](https://embarkstudios.github.io/cargo-about/cli/generate/config.html#crate-configuration). + ### Wasm Plugins From 57781fd7aa1860739bf3dbd93458907ae0b7c7ca Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 27 Jan 2023 15:37:36 -0800 Subject: [PATCH 061/180] Move StaffMode declaration out of paths --- crates/client/src/user.rs | 17 +++++++++++------ crates/theme_selector/src/theme_selector.rs | 4 ++-- crates/util/src/lib.rs | 11 +++++++++++ crates/util/src/paths.rs | 13 +------------ crates/zed/src/main.rs | 8 +------- crates/zed/src/zed.rs | 8 ++------ 6 files changed, 28 insertions(+), 33 deletions(-) diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 98ca06e1fc..01fd1773c4 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -7,7 +7,7 @@ use postage::{sink::Sink, watch}; use rpc::proto::{RequestMessage, UsersResponse}; use settings::Settings; use std::sync::{Arc, Weak}; -use util::{paths::StaffMode, TryFutureExt as _}; +use util::{StaffMode, TryFutureExt as _}; #[derive(Default, Debug)] pub struct User { @@ -149,11 +149,16 @@ impl UserStore { ); cx.update(|cx| { - cx.set_global( - info.as_ref() - .map(|info| StaffMode(info.staff)) - .unwrap_or(StaffMode(false)), - ); + cx.update_default_global(|staff_mode: &mut StaffMode, _| { + if !staff_mode.0 { + *staff_mode = StaffMode( + info.as_ref() + .map(|info| info.staff) + .unwrap_or_default(), + ) + } + () + }); }); current_user_tx.send(user).await.ok(); diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index ad22baf8b9..d999730a0d 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -7,7 +7,7 @@ use picker::{Picker, PickerDelegate}; use settings::{settings_file::SettingsFile, Settings}; use std::sync::Arc; use theme::{Theme, ThemeMeta, ThemeRegistry}; -use util::paths::StaffMode; +use util::StaffMode; use workspace::{AppState, Workspace}; pub struct ThemeSelector { @@ -45,7 +45,7 @@ impl ThemeSelector { let original_theme = settings.theme.clone(); let mut theme_names = registry - .list(**cx.try_global::().unwrap_or(&StaffMode(false))) + .list(**cx.default_global::()) .collect::>(); theme_names.sort_unstable_by(|a, b| { a.is_light diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index e79cc269c9..8cdbfc6438 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -13,6 +13,17 @@ use std::{ task::{Context, Poll}, }; +#[derive(Debug, Default)] +pub struct StaffMode(pub bool); + +impl std::ops::Deref for StaffMode { + type Target = bool; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[macro_export] macro_rules! debug_panic { ( $($fmt_arg:tt)* ) => { diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 94af0aa75d..8698d6891e 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -1,15 +1,4 @@ -use std::{ops::Deref, path::PathBuf}; - -#[derive(Debug)] -pub struct StaffMode(pub bool); - -impl Deref for StaffMode { - type Target = bool; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} +use std::path::PathBuf; lazy_static::lazy_static! { pub static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory"); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 4cef2c0775..98d2aa879f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -39,11 +39,7 @@ use terminal_view::{get_working_directory, TerminalView}; use fs::RealFs; use settings::watched_json::{watch_keymap_file, watch_settings_file, WatchedJsonFile}; use theme::ThemeRegistry; -use util::{ - channel::RELEASE_CHANNEL, - paths::{self, StaffMode}, - ResultExt, TryFutureExt, -}; +use util::{channel::RELEASE_CHANNEL, paths, ResultExt, StaffMode, TryFutureExt}; use workspace::{ self, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, OpenPaths, Workspace, }; @@ -109,8 +105,6 @@ fn main() { app.run(move |cx| { cx.set_global(*RELEASE_CHANNEL); - #[cfg(not(debug_assertions))] - cx.set_global(StaffMode(false)); #[cfg(debug_assertions)] cx.set_global(StaffMode(true)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d1ec3f2451..fc7f96a384 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -32,11 +32,7 @@ use serde::Deserialize; use serde_json::to_string_pretty; use settings::{keymap_file_json_schema, settings_file_json_schema, Settings}; use std::{borrow::Cow, env, path::Path, str, sync::Arc}; -use util::{ - channel::ReleaseChannel, - paths::{self, StaffMode}, - ResultExt, -}; +use util::{channel::ReleaseChannel, paths, ResultExt, StaffMode}; use uuid::Uuid; pub use workspace; use workspace::{sidebar::SidebarSide, AppState, Workspace}; @@ -303,7 +299,7 @@ pub fn initialize_workspace( let theme_names = app_state .themes - .list(**cx.try_global::().unwrap_or(&StaffMode(false))) + .list(**cx.default_global::()) .map(|meta| meta.name) .collect(); let language_names = app_state.languages.language_names(); From 248161aa6364dcc29511766fcb2b29e79cb0208b Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 30 Jan 2023 14:13:25 -0500 Subject: [PATCH 062/180] Fix version for feedback-related commands Co-Authored-By: Max Brunsfeld --- crates/auto_update/src/auto_update.rs | 11 +---- crates/client/src/client.rs | 7 +++- crates/feedback/src/feedback.rs | 36 ++++++++-------- crates/feedback/src/feedback_editor.rs | 33 +++++++++------ crates/feedback/src/system_specs.rs | 57 ++++++++++++++++++-------- crates/zed/src/main.rs | 3 +- 6 files changed, 88 insertions(+), 59 deletions(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index d3fcc36c2f..19f4652005 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -2,15 +2,15 @@ mod update_notification; use anyhow::{anyhow, Context, Result}; use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN}; +use client::{ZED_APP_PATH, ZED_APP_VERSION}; use db::kvp::KEY_VALUE_STORE; use gpui::{ actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, WeakViewHandle, }; -use lazy_static::lazy_static; use serde::Deserialize; use smol::{fs::File, io::AsyncReadExt, process::Command}; -use std::{env, ffi::OsString, path::PathBuf, sync::Arc, time::Duration}; +use std::{ffi::OsString, sync::Arc, time::Duration}; use update_notification::UpdateNotification; use util::channel::ReleaseChannel; use workspace::Workspace; @@ -18,13 +18,6 @@ use workspace::Workspace; const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60); -lazy_static! { - pub static ref ZED_APP_VERSION: Option = env::var("ZED_APP_VERSION") - .ok() - .and_then(|v| v.parse().ok()); - pub static ref ZED_APP_PATH: Option = env::var("ZED_APP_PATH").ok().map(PathBuf::from); -} - actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes]); #[derive(Clone, Copy, PartialEq, Eq)] diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 4d129fab2e..08a3223eb0 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -15,7 +15,7 @@ use futures::{future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamEx use gpui::{ actions, serde_json::{self, Value}, - AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, + AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AppVersion, AsyncAppContext, Entity, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle, }; use http::HttpClient; @@ -55,6 +55,11 @@ lazy_static! { pub static ref ADMIN_API_TOKEN: Option = std::env::var("ZED_ADMIN_API_TOKEN") .ok() .and_then(|s| if s.is_empty() { None } else { Some(s) }); + pub static ref ZED_APP_VERSION: Option = std::env::var("ZED_APP_VERSION") + .ok() + .and_then(|v| v.parse().ok()); + pub static ref ZED_APP_PATH: Option = + std::env::var("ZED_APP_PATH").ok().map(PathBuf::from); } pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894"; diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index f47f95d4f3..680155b657 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -2,7 +2,7 @@ use std::sync::Arc; pub mod feedback_editor; mod system_specs; -use gpui::{actions, impl_actions, ClipboardItem, ViewContext}; +use gpui::{actions, impl_actions, ClipboardItem, MutableAppContext, PromptLevel, ViewContext}; use serde::Deserialize; use system_specs::SystemSpecs; use workspace::{AppState, Workspace}; @@ -16,23 +16,32 @@ impl_actions!(zed, [OpenBrowser]); actions!( zed, - [CopySystemSpecsIntoClipboard, FileBugReport, RequestFeature,] + [CopySystemSpecsIntoClipboard, FileBugReport, RequestFeature] ); -pub fn init(app_state: Arc, cx: &mut gpui::MutableAppContext) { - feedback_editor::init(app_state, cx); +pub fn init(app_state: Arc, cx: &mut MutableAppContext) { + let system_specs = SystemSpecs::new(&cx); + let system_specs_text = system_specs.to_string(); + + feedback_editor::init(system_specs, app_state, cx); cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); + let url = format!( + "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", + urlencoding::encode(&system_specs_text) + ); + cx.add_action( - |_: &mut Workspace, _: &CopySystemSpecsIntoClipboard, cx: &mut ViewContext| { - let system_specs = SystemSpecs::new(cx).to_string(); - let item = ClipboardItem::new(system_specs.clone()); + move |_: &mut Workspace, + _: &CopySystemSpecsIntoClipboard, + cx: &mut ViewContext| { cx.prompt( - gpui::PromptLevel::Info, - &format!("Copied into clipboard:\n\n{system_specs}"), + PromptLevel::Info, + &format!("Copied into clipboard:\n\n{system_specs_text}"), &["OK"], ); + let item = ClipboardItem::new(system_specs_text.clone()); cx.write_to_clipboard(item); }, ); @@ -47,14 +56,9 @@ pub fn init(app_state: Arc, cx: &mut gpui::MutableAppContext) { ); cx.add_action( - |_: &mut Workspace, _: &FileBugReport, cx: &mut ViewContext| { - let system_specs_text = SystemSpecs::new(cx).to_string(); - let url = format!( - "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", - urlencoding::encode(&system_specs_text) - ); + move |_: &mut Workspace, _: &FileBugReport, cx: &mut ViewContext| { cx.dispatch_action(OpenBrowser { - url: url.into(), + url: url.clone().into(), }); }, ); diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index a4c10d8fc2..56c1def935 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -5,7 +5,7 @@ use std::{ }; use anyhow::bail; -use client::{Client, ZED_SECRET_CLIENT_TOKEN}; +use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use editor::{Anchor, Editor}; use futures::AsyncReadExt; use gpui::{ @@ -19,7 +19,6 @@ use isahc::Request; use language::Buffer; use postage::prelude::Stream; -use lazy_static::lazy_static; use project::Project; use serde::Serialize; use settings::Settings; @@ -31,11 +30,6 @@ use workspace::{ use crate::system_specs::SystemSpecs; -lazy_static! { - pub static ref ZED_SERVER_URL: String = - std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string()); -} - const FEEDBACK_CHAR_LIMIT: RangeInclusive = 10..=5000; const FEEDBACK_PLACEHOLDER_TEXT: &str = "Thanks for spending time with Zed. Enter your feedback here as Markdown. Save the tab to submit your feedback."; const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = @@ -43,10 +37,10 @@ const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = actions!(feedback, [SubmitFeedback, GiveFeedback, DeployFeedback]); -pub fn init(app_state: Arc, cx: &mut MutableAppContext) { +pub fn init(system_specs: SystemSpecs, app_state: Arc, cx: &mut MutableAppContext) { cx.add_action({ move |workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext| { - FeedbackEditor::deploy(workspace, app_state.clone(), cx); + FeedbackEditor::deploy(system_specs.clone(), workspace, app_state.clone(), cx); } }); } @@ -97,12 +91,14 @@ struct FeedbackRequestBody<'a> { #[derive(Clone)] struct FeedbackEditor { + system_specs: SystemSpecs, editor: ViewHandle, project: ModelHandle, } impl FeedbackEditor { fn new( + system_specs: SystemSpecs, project: ModelHandle, buffer: ModelHandle, cx: &mut ViewContext, @@ -117,7 +113,11 @@ impl FeedbackEditor { cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone())) .detach(); - Self { editor, project } + Self { + system_specs: system_specs.clone(), + editor, + project, + } } fn handle_save( @@ -155,7 +155,7 @@ impl FeedbackEditor { let this = cx.handle(); let client = cx.global::>().clone(); let feedback_text = self.editor.read(cx).text(cx); - let specs = SystemSpecs::new(cx); + let specs = self.system_specs.clone(); cx.spawn(|_, mut cx| async move { let answer = answer.recv().await; @@ -229,6 +229,7 @@ impl FeedbackEditor { impl FeedbackEditor { pub fn deploy( + system_specs: SystemSpecs, workspace: &mut Workspace, app_state: Arc, cx: &mut ViewContext, @@ -242,7 +243,8 @@ impl FeedbackEditor { project.create_buffer("", markdown_language, cx) }) .expect("creating buffers on a local workspace always succeeds"); - let feedback_editor = cx.add_view(|cx| FeedbackEditor::new(project, buffer, cx)); + let feedback_editor = + cx.add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx)); workspace.add_item(Box::new(feedback_editor), cx); }) .detach(); @@ -340,7 +342,12 @@ impl Item for FeedbackEditor { .as_singleton() .expect("Feedback buffer is only ever singleton"); - Some(Self::new(self.project.clone(), buffer.clone(), cx)) + Some(Self::new( + self.system_specs.clone(), + self.project.clone(), + buffer.clone(), + cx, + )) } fn serialized_item_kind() -> Option<&'static str> { diff --git a/crates/feedback/src/system_specs.rs b/crates/feedback/src/system_specs.rs index 17e51a6815..f20561826e 100644 --- a/crates/feedback/src/system_specs.rs +++ b/crates/feedback/src/system_specs.rs @@ -1,14 +1,15 @@ -use std::{env, fmt::Display}; - -use gpui::AppContext; +use client::ZED_APP_VERSION; +use gpui::{AppContext, AppVersion}; use human_bytes::human_bytes; use serde::Serialize; +use std::{env, fmt::Display}; use sysinfo::{System, SystemExt}; use util::channel::ReleaseChannel; -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct SystemSpecs { - app_version: &'static str, + #[serde(serialize_with = "serialize_app_version")] + app_version: Option, release_channel: &'static str, os_name: &'static str, os_version: Option, @@ -19,18 +20,24 @@ pub struct SystemSpecs { impl SystemSpecs { pub fn new(cx: &AppContext) -> Self { let platform = cx.platform(); + let app_version = ZED_APP_VERSION.or_else(|| platform.app_version().ok()); + let release_channel = cx.global::().dev_name(); + let os_name = platform.os_name(); let system = System::new_all(); + let memory = system.total_memory(); + let architecture = env::consts::ARCH; + let os_version = platform + .os_version() + .ok() + .map(|os_version| os_version.to_string()); SystemSpecs { - app_version: env!("CARGO_PKG_VERSION"), - release_channel: cx.global::().dev_name(), - os_name: platform.os_name(), - os_version: platform - .os_version() - .ok() - .map(|os_version| os_version.to_string()), - memory: system.total_memory(), - architecture: env::consts::ARCH, + app_version, + release_channel, + os_name, + os_version, + memory, + architecture, } } } @@ -41,14 +48,28 @@ impl Display for SystemSpecs { Some(os_version) => format!("OS: {} {}", self.os_name, os_version), None => format!("OS: {}", self.os_name), }; + let app_version_information = self + .app_version + .as_ref() + .map(|app_version| format!("Zed: v{} ({})", app_version, self.release_channel)); let system_specs = [ - format!("Zed: v{} ({})", self.app_version, self.release_channel), - os_information, - format!("Memory: {}", human_bytes(self.memory as f64)), - format!("Architecture: {}", self.architecture), + app_version_information, + Some(os_information), + Some(format!("Memory: {}", human_bytes(self.memory as f64))), + Some(format!("Architecture: {}", self.architecture)), ] + .into_iter() + .flatten() + .collect::>() .join("\n"); write!(f, "{system_specs}") } } + +fn serialize_app_version(version: &Option, serializer: S) -> Result +where + S: serde::Serializer, +{ + version.map(|v| v.to_string()).serialize(serializer) +} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 98d2aa879f..77fd516b86 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -3,7 +3,6 @@ use anyhow::{anyhow, Context, Result}; use assets::Assets; -use auto_update::ZED_APP_VERSION; use backtrace::Backtrace; use cli::{ ipc::{self, IpcSender}, @@ -12,7 +11,7 @@ use cli::{ use client::{ self, http::{self, HttpClient}, - UserStore, ZED_SECRET_CLIENT_TOKEN, + UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN, }; use futures::{ From e682e2dd729afa5c7b33ae3152a5aca1c0590e17 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 30 Jan 2023 14:38:48 -0800 Subject: [PATCH 063/180] Changed SQLez migrations to be executed eagerly Added fix for terminal working directory's sometimes getting lost co-authored-by: Kay --- Cargo.lock | 1 + crates/sqlez/Cargo.toml | 5 +- crates/sqlez/src/migrations.rs | 71 ++++++++++++++++++++++++- crates/sqlez/src/typed_statements.rs | 36 +++++++++++++ crates/terminal_view/src/persistence.rs | 20 +++++++ 5 files changed, 130 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 914e61226d..8c7f08bf9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6015,6 +6015,7 @@ dependencies = [ "libsqlite3-sys", "parking_lot 0.11.2", "smol", + "sqlez_macros", "thread_local", "uuid 1.2.2", ] diff --git a/crates/sqlez/Cargo.toml b/crates/sqlez/Cargo.toml index 716ec76644..f247f3e537 100644 --- a/crates/sqlez/Cargo.toml +++ b/crates/sqlez/Cargo.toml @@ -15,4 +15,7 @@ thread_local = "1.1.4" lazy_static = "1.4" parking_lot = "0.11.1" futures = "0.3" -uuid = { version = "1.1.2", features = ["v4"] } \ No newline at end of file +uuid = { version = "1.1.2", features = ["v4"] } + +[dev-dependencies] +sqlez_macros = { path = "../sqlez_macros"} \ No newline at end of file diff --git a/crates/sqlez/src/migrations.rs b/crates/sqlez/src/migrations.rs index 41c505f85b..b3aaef95b1 100644 --- a/crates/sqlez/src/migrations.rs +++ b/crates/sqlez/src/migrations.rs @@ -4,12 +4,36 @@ // to creating a new db?) // Otherwise any missing migrations are run on the connection -use anyhow::{anyhow, Result}; +use std::ffi::CString; + +use anyhow::{anyhow, Context, Result}; use indoc::{formatdoc, indoc}; +use libsqlite3_sys::sqlite3_exec; use crate::connection::Connection; impl Connection { + fn eager_exec(&self, sql: &str) -> anyhow::Result<()> { + let sql_str = CString::new(sql).context("Error creating cstr")?; + unsafe { + sqlite3_exec( + self.sqlite3, + sql_str.as_c_str().as_ptr(), + None, + 0 as *mut _, + 0 as *mut _, + ); + } + self.last_error() + .with_context(|| format!("Prepare call failed for query:\n{}", sql))?; + + Ok(()) + } + + /// Migrate the database, for the given domain. + /// Note: Unlike everything else in SQLez, migrations are run eagerly, without first + /// preparing the SQL statements. This makes it possible to do multi-statement schema + /// updates in a single string without running into prepare errors. pub fn migrate(&self, domain: &'static str, migrations: &[&'static str]) -> Result<()> { self.with_savepoint("migrating", || { // Setup the migrations table unconditionally @@ -47,7 +71,7 @@ impl Connection { } } - self.exec(migration)?()?; + self.eager_exec(migration)?; store_completed_migration((domain, index, *migration))?; } @@ -59,6 +83,7 @@ impl Connection { #[cfg(test)] mod test { use indoc::indoc; + use sqlez_macros::sql; use crate::connection::Connection; @@ -257,4 +282,46 @@ mod test { // Verify new migration returns error when run assert!(second_migration_result.is_err()) } + + #[test] + fn test_create_alter_drop() { + let connection = Connection::open_memory(Some("test_create_alter_drop")); + + connection + .migrate( + "first_migration", + &[sql!( CREATE TABLE table1(a TEXT) STRICT; )], + ) + .unwrap(); + + connection + .exec(sql!( INSERT INTO table1(a) VALUES ("test text"); )) + .unwrap()() + .unwrap(); + + connection + .migrate( + "second_migration", + &[sql!( + CREATE TABLE table2(b TEXT) STRICT; + + INSERT INTO table2 (b) + SELECT a FROM table1; + + DROP TABLE table1; + + ALTER TABLE table2 RENAME TO table1; + )], + ) + .unwrap(); + + let res = &connection + .select::(sql!( + SELECT b FROM table1 + )) + .unwrap()() + .unwrap()[0]; + + assert_eq!(res, "test text"); + } } diff --git a/crates/sqlez/src/typed_statements.rs b/crates/sqlez/src/typed_statements.rs index df4a2987b5..488ee27c0c 100644 --- a/crates/sqlez/src/typed_statements.rs +++ b/crates/sqlez/src/typed_statements.rs @@ -7,11 +7,23 @@ use crate::{ }; impl Connection { + /// Prepare a statement which has no bindings and returns nothing. + /// + /// Note: If there are multiple statements that depend upon each other + /// (such as those which make schema changes), preparation will fail. + /// Use a true migration instead. pub fn exec<'a>(&'a self, query: &str) -> Result Result<()>> { let mut statement = Statement::prepare(self, query)?; Ok(move || statement.exec()) } + /// Prepare a statement which takes a binding, but returns nothing. + /// The bindings for a given invocation should be passed to the returned + /// closure + /// + /// Note: If there are multiple statements that depend upon each other + /// (such as those which make schema changes), preparation will fail. + /// Use a true migration instead. pub fn exec_bound<'a, B: Bind>( &'a self, query: &str, @@ -20,6 +32,11 @@ impl Connection { Ok(move |bindings| statement.with_bindings(bindings)?.exec()) } + /// Prepare a statement which has no bindings and returns a `Vec`. + /// + /// Note: If there are multiple statements that depend upon each other + /// (such as those which make schema changes), preparation will fail. + /// Use a true migration instead. pub fn select<'a, C: Column>( &'a self, query: &str, @@ -28,6 +45,11 @@ impl Connection { Ok(move || statement.rows::()) } + /// Prepare a statement which takes a binding and returns a `Vec`. + /// + /// Note: If there are multiple statements that depend upon each other + /// (such as those which make schema changes), preparation will fail. + /// Use a true migration instead. pub fn select_bound<'a, B: Bind, C: Column>( &'a self, query: &str, @@ -36,6 +58,13 @@ impl Connection { Ok(move |bindings| statement.with_bindings(bindings)?.rows::()) } + /// Prepare a statement that selects a single row from the database. + /// Will return none if no rows are returned and will error if more than + /// 1 row + /// + /// Note: If there are multiple statements that depend upon each other + /// (such as those which make schema changes), preparation will fail. + /// Use a true migration instead. pub fn select_row<'a, C: Column>( &'a self, query: &str, @@ -44,6 +73,13 @@ impl Connection { Ok(move || statement.maybe_row::()) } + /// Prepare a statement which takes a binding and selects a single row + /// from the database. WIll return none if no rows are returned and will + /// error if more than 1 row is returned. + /// + /// Note: If there are multiple statements that depend upon each other + /// (such as those which make schema changes), preparation will fail. + /// Use a true migration instead. pub fn select_row_bound<'a, B: Bind, C: Column>( &'a self, query: &str, diff --git a/crates/terminal_view/src/persistence.rs b/crates/terminal_view/src/persistence.rs index 26bd0931fe..0da9ed4729 100644 --- a/crates/terminal_view/src/persistence.rs +++ b/crates/terminal_view/src/persistence.rs @@ -14,6 +14,26 @@ define_connection! { FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE ) STRICT; + ), + // Remove the unique constraint on the item_id table + // SQLite doesn't have a way of doing this automatically, so + // we have to do this silly copying. + sql!( + CREATE TABLE terminals2 ( + workspace_id INTEGER, + item_id INTEGER, + working_directory BLOB, + PRIMARY KEY(workspace_id, item_id), + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ) STRICT; + + INSERT INTO terminals2 (workspace_id, item_id, working_directory) + SELECT workspace_id, item_id, working_directory FROM terminals; + + DROP TABLE terminals; + + ALTER TABLE terminals2 RENAME TO terminals; )]; } From e35db69dbd684353a5f567928bcaeb189422c5a6 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 31 Jan 2023 15:00:49 -0800 Subject: [PATCH 064/180] Add call status indicator to the status bar --- crates/call/src/call.rs | 37 ++++++++++++++++++- crates/call/src/indicator.rs | 39 ++++++++++++++++++++ crates/collab_ui/src/collab_titlebar_item.rs | 21 +---------- script/start-local-collaboration | 5 ++- 4 files changed, 79 insertions(+), 23 deletions(-) create mode 100644 crates/call/src/indicator.rs diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index c63b2e0f5b..834c4949d7 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -1,3 +1,4 @@ +mod indicator; pub mod participant; pub mod room; @@ -5,18 +6,22 @@ use anyhow::{anyhow, Result}; use client::{proto, Client, TypedEnvelope, User, UserStore}; use collections::HashSet; use gpui::{ - AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, - Subscription, Task, WeakModelHandle, + actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, + Subscription, Task, ViewHandle, WeakModelHandle, }; +use indicator::SharingStatusIndicator; pub use participant::ParticipantLocation; use postage::watch; use project::Project; pub use room::Room; use std::sync::Arc; +actions!(collab, [ToggleScreenSharing]); + pub fn init(client: Arc, user_store: ModelHandle, cx: &mut MutableAppContext) { let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx)); cx.set_global(active_call); + cx.add_global_action(toggle_screen_sharing); } #[derive(Clone)] @@ -37,6 +42,7 @@ pub struct ActiveCall { ), client: Arc, user_store: ModelHandle, + sharing_status_indicator: Option<(usize, ViewHandle)>, _subscriptions: Vec, } @@ -61,6 +67,7 @@ impl ActiveCall { ], client, user_store, + sharing_status_indicator: None, } } @@ -279,6 +286,8 @@ impl ActiveCall { this.set_room(None, cx).detach_and_log_err(cx); } + this.set_sharing_status(room.read(cx).is_screen_sharing(), cx); + cx.notify(); }), cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())), @@ -303,4 +312,28 @@ impl ActiveCall { pub fn pending_invites(&self) -> &HashSet { &self.pending_invites } + + pub fn set_sharing_status(&mut self, is_screen_sharing: bool, cx: &mut MutableAppContext) { + if is_screen_sharing { + if self.sharing_status_indicator.is_none() { + self.sharing_status_indicator = + Some(cx.add_status_bar_item(|_| SharingStatusIndicator)); + } + } else if let Some((window_id, _)) = self.sharing_status_indicator.take() { + cx.remove_status_bar_item(window_id); + } + } +} + +pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut MutableAppContext) { + if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { + let toggle_screen_sharing = room.update(cx, |room, cx| { + if room.is_screen_sharing() { + Task::ready(room.unshare_screen(cx)) + } else { + room.share_screen(cx) + } + }); + toggle_screen_sharing.detach_and_log_err(cx); + } } diff --git a/crates/call/src/indicator.rs b/crates/call/src/indicator.rs new file mode 100644 index 0000000000..102ea5c551 --- /dev/null +++ b/crates/call/src/indicator.rs @@ -0,0 +1,39 @@ +use gpui::{ + color::Color, + elements::{MouseEventHandler, Svg}, + Appearance, Element, ElementBox, Entity, MouseButton, RenderContext, View, +}; + +use crate::ToggleScreenSharing; + +pub struct SharingStatusIndicator; + +impl Entity for SharingStatusIndicator { + type Event = (); +} + +impl View for SharingStatusIndicator { + fn ui_name() -> &'static str { + "SharingStatusIndicator" + } + + fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { + let color = match cx.appearance { + Appearance::Light | Appearance::VibrantLight => Color::black(), + Appearance::Dark | Appearance::VibrantDark => Color::white(), + }; + + MouseEventHandler::::new(0, cx, |_, _| { + Svg::new("icons/disable_screen_sharing_12.svg") + .with_color(color) + .constrained() + .with_width(18.) + .aligned() + .boxed() + }) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(ToggleScreenSharing); + }) + .boxed() + } +} diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 3351fb9eb9..a6f917725d 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -1,5 +1,5 @@ use crate::{contact_notification::ContactNotification, contacts_popover}; -use call::{ActiveCall, ParticipantLocation}; +use call::{ActiveCall, ParticipantLocation, ToggleScreenSharing}; use client::{proto::PeerId, Authenticate, ContactEventKind, User, UserStore}; use clock::ReplicaId; use contacts_popover::ContactsPopover; @@ -17,14 +17,10 @@ use std::ops::Range; use theme::Theme; use workspace::{FollowNextCollaborator, JoinProject, ToggleFollow, Workspace}; -actions!( - collab, - [ToggleCollaborationMenu, ToggleScreenSharing, ShareProject] -); +actions!(collab, [ToggleCollaborationMenu, ShareProject]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(CollabTitlebarItem::toggle_contacts_popover); - cx.add_action(CollabTitlebarItem::toggle_screen_sharing); cx.add_action(CollabTitlebarItem::share_project); } @@ -172,19 +168,6 @@ impl CollabTitlebarItem { cx.notify(); } - pub fn toggle_screen_sharing(&mut self, _: &ToggleScreenSharing, cx: &mut ViewContext) { - if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { - let toggle_screen_sharing = room.update(cx, |room, cx| { - if room.is_screen_sharing() { - Task::ready(room.unshare_screen(cx)) - } else { - room.share_screen(cx) - } - }); - toggle_screen_sharing.detach_and_log_err(cx); - } - } - fn render_toggle_contacts_button( &self, theme: &Theme, diff --git a/script/start-local-collaboration b/script/start-local-collaboration index 82341bf6db..168ecf7a23 100755 --- a/script/start-local-collaboration +++ b/script/start-local-collaboration @@ -31,9 +31,10 @@ scale_factor=1 if [[ $resolution_line =~ Retina ]]; then scale_factor=2; fi width=$(expr ${screen_size[0]} / 2 / $scale_factor) height=${screen_size[1] / $scale_factor} +y=$(expr $height / 2) -position_1=0,0 -position_2=${width},0 +position_1=0,${y} +position_2=${width},${y} # Authenticate using the collab server's admin secret. export ZED_STATELESS=1 From 460dc62888be6adcc599066f60363292a829d2e1 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 31 Jan 2023 15:17:16 -0800 Subject: [PATCH 065/180] start adding setting for the screen sharing status indicator --- Cargo.lock | 1 + crates/call/Cargo.toml | 1 + crates/collab_ui/src/collab_titlebar_item.rs | 2 +- crates/settings/src/settings.rs | 5 +++++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8c7f08bf9b..65733a7ff6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -828,6 +828,7 @@ dependencies = [ "media", "postage", "project", + "settings", "util", ] diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index 156925fb72..54546adb55 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -28,6 +28,7 @@ fs = { path = "../fs" } language = { path = "../language" } media = { path = "../media" } project = { path = "../project" } +settings = { path = "../settings" } util = { path = "../util" } anyhow = "1.0.38" diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index a6f917725d..116a331578 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -10,7 +10,7 @@ use gpui::{ geometry::{rect::RectF, vector::vec2f, PathBuilder}, json::{self, ToJson}, Border, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, - Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, + Subscription, View, ViewContext, ViewHandle, WeakViewHandle, }; use settings::Settings; use std::ops::Range; diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index a184c9a929..9c4b1f8044 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -35,6 +35,7 @@ pub struct Settings { pub confirm_quit: bool, pub hover_popover_enabled: bool, pub show_completions_on_input: bool, + pub show_call_status_icon: bool, pub vim_mode: bool, pub autosave: Autosave, pub default_dock_anchor: DockAnchor, @@ -287,6 +288,8 @@ pub struct SettingsFileContent { #[serde(default)] pub show_completions_on_input: Option, #[serde(default)] + pub show_call_status_icon: Option, + #[serde(default)] pub vim_mode: Option, #[serde(default)] pub autosave: Option, @@ -346,6 +349,7 @@ impl Settings { cursor_blink: defaults.cursor_blink.unwrap(), hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), show_completions_on_input: defaults.show_completions_on_input.unwrap(), + show_call_status_icon: defaults.show_call_status_icon.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), default_dock_anchor: defaults.default_dock_anchor.unwrap(), @@ -540,6 +544,7 @@ impl Settings { cursor_blink: true, hover_popover_enabled: true, show_completions_on_input: true, + show_call_status_icon: true, vim_mode: false, autosave: Autosave::Off, default_dock_anchor: DockAnchor::Bottom, From fd2a9b3df9e3ad874b586ed886f7c9da85a18c02 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 1 Feb 2023 13:45:06 -0500 Subject: [PATCH 066/180] v0.73.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c7f08bf9b..c40329573d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8228,7 +8228,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.72.0" +version = "0.73.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 7cb1158a06..b5df007129 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.72.0" +version = "0.73.0" publish = false [lib] From d6962d957b648d142264896efba3f858476421e4 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 1 Feb 2023 16:26:08 -0500 Subject: [PATCH 067/180] Add note to base16.ts --- styles/src/themes/common/base16.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/styles/src/themes/common/base16.ts b/styles/src/themes/common/base16.ts index c5b914d62b..23ccc57fd4 100644 --- a/styles/src/themes/common/base16.ts +++ b/styles/src/themes/common/base16.ts @@ -1,3 +1,6 @@ +// NOTE – This should be removed +// I (Nate) need to come back and check if we are still using this anywhere + import chroma, { Color, Scale } from "chroma-js"; import { fontWeights } from "../../common"; import { withOpacity } from "../../utils/color"; From 62d32db66c9acb18f37df76ae2bdee7665b89a65 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 1 Feb 2023 14:59:43 -0800 Subject: [PATCH 068/180] Make display uuid optional if the display is disconnected --- crates/gpui/src/app.rs | 32 ++++++++++++++------------ crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/mac/screen.rs | 12 ++++++---- crates/gpui/src/platform/test.rs | 4 ++-- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 95967ed485..a9468ada6c 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -914,7 +914,7 @@ impl MutableAppContext { self.presenters_and_platform_windows[&window_id].1.bounds() } - pub fn window_display_uuid(&self, window_id: usize) -> Uuid { + pub fn window_display_uuid(&self, window_id: usize) -> Option { self.presenters_and_platform_windows[&window_id] .1 .screen() @@ -2375,12 +2375,13 @@ impl MutableAppContext { callback(is_fullscreen, this) }); - let bounds = this.window_bounds(window_id); - let uuid = this.window_display_uuid(window_id); - let mut bounds_observations = this.window_bounds_observations.clone(); - bounds_observations.emit(window_id, this, |callback, this| { - callback(bounds, uuid, this) - }); + if let Some(uuid) = this.window_display_uuid(window_id) { + let bounds = this.window_bounds(window_id); + let mut bounds_observations = this.window_bounds_observations.clone(); + bounds_observations.emit(window_id, this, |callback, this| { + callback(bounds, uuid, this) + }); + } Some(()) }); @@ -2559,14 +2560,15 @@ impl MutableAppContext { } fn handle_window_moved(&mut self, window_id: usize) { - let bounds = self.window_bounds(window_id); - let display = self.window_display_uuid(window_id); - self.window_bounds_observations - .clone() - .emit(window_id, self, move |callback, this| { - callback(bounds, display, this); - true - }); + if let Some(display) = self.window_display_uuid(window_id) { + let bounds = self.window_bounds(window_id); + self.window_bounds_observations + .clone() + .emit(window_id, self, move |callback, this| { + callback(bounds, display, this); + true + }); + } } pub fn focus(&mut self, window_id: usize, view_id: Option) { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index d1aaed7f47..57e8f89539 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -124,7 +124,7 @@ pub trait InputHandler { pub trait Screen: Debug { fn as_any(&self) -> &dyn Any; fn bounds(&self) -> RectF; - fn display_uuid(&self) -> Uuid; + fn display_uuid(&self) -> Option; } pub trait Window { diff --git a/crates/gpui/src/platform/mac/screen.rs b/crates/gpui/src/platform/mac/screen.rs index 27fb4b12d1..98b6a66f03 100644 --- a/crates/gpui/src/platform/mac/screen.rs +++ b/crates/gpui/src/platform/mac/screen.rs @@ -35,7 +35,7 @@ impl Screen { .map(|ix| Screen { native_screen: native_screens.objectAtIndex(ix), }) - .find(|screen| platform::Screen::display_uuid(screen) == uuid) + .find(|screen| platform::Screen::display_uuid(screen) == Some(uuid)) } } @@ -58,7 +58,7 @@ impl platform::Screen for Screen { self } - fn display_uuid(&self) -> uuid::Uuid { + fn display_uuid(&self) -> Option { unsafe { // Screen ids are not stable. Further, the default device id is also unstable across restarts. // CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use. @@ -74,8 +74,12 @@ impl platform::Screen for Screen { (&mut device_id) as *mut _ as *mut c_void, ); let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID); + if cfuuid.is_null() { + return None; + } + let bytes = CFUUIDGetUUIDBytes(cfuuid); - Uuid::from_bytes([ + Some(Uuid::from_bytes([ bytes.byte0, bytes.byte1, bytes.byte2, @@ -92,7 +96,7 @@ impl platform::Screen for Screen { bytes.byte13, bytes.byte14, bytes.byte15, - ]) + ])) } } diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 6c8637948e..aa73aebc90 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -238,8 +238,8 @@ impl super::Screen for Screen { RectF::new(Vector2F::zero(), Vector2F::new(1920., 1080.)) } - fn display_uuid(&self) -> uuid::Uuid { - uuid::Uuid::new_v4() + fn display_uuid(&self) -> Option { + Some(uuid::Uuid::new_v4()) } } From a50f0181fba73ecd7813d32388265f44ef20b034 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 1 Feb 2023 16:21:53 -0800 Subject: [PATCH 069/180] Add setting to disable the call icon --- assets/settings/default.json | 2 ++ crates/call/src/call.rs | 14 ++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 1ef2ac8a16..6c784a067c 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -20,6 +20,8 @@ // Whether to pop the completions menu while typing in an editor without // explicitly requesting it. "show_completions_on_input": true, + // Whether the screen sharing icon is showed in the os status bar. + "show_call_status_icon": true, // Whether new projects should start out 'online'. Online projects // appear in the contacts panel under your name, so that your contacts // can see which projects you are working on. Regardless of this diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 834c4949d7..c34d124162 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -2,19 +2,23 @@ mod indicator; pub mod participant; pub mod room; +use std::sync::Arc; + use anyhow::{anyhow, Result}; use client::{proto, Client, TypedEnvelope, User, UserStore}; use collections::HashSet; +use postage::watch; + use gpui::{ actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Subscription, Task, ViewHandle, WeakModelHandle, }; +use project::Project; +use settings::Settings; + use indicator::SharingStatusIndicator; pub use participant::ParticipantLocation; -use postage::watch; -use project::Project; pub use room::Room; -use std::sync::Arc; actions!(collab, [ToggleScreenSharing]); @@ -315,7 +319,9 @@ impl ActiveCall { pub fn set_sharing_status(&mut self, is_screen_sharing: bool, cx: &mut MutableAppContext) { if is_screen_sharing { - if self.sharing_status_indicator.is_none() { + if self.sharing_status_indicator.is_none() + && cx.global::().show_call_status_icon + { self.sharing_status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator)); } From 888145ebed9d72159d2a1f7cc0a9bf3a45aadb9a Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Thu, 2 Feb 2023 12:43:55 -0500 Subject: [PATCH 070/180] Trim leading and trailing whitespace in feedback --- crates/feedback/src/feedback_editor.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 56c1def935..e2fbc68f70 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -125,7 +125,9 @@ impl FeedbackEditor { _: ModelHandle, cx: &mut ViewContext, ) -> Task> { - let feedback_char_count = self.editor.read(cx).text(cx).chars().count(); + let feedback_text = self.editor.read(cx).text(cx); + let feedback_char_count = feedback_text.chars().count(); + let feedback_text = feedback_text.trim().to_string(); let error = if feedback_char_count < *FEEDBACK_CHAR_LIMIT.start() { Some(format!( @@ -154,7 +156,6 @@ impl FeedbackEditor { let this = cx.handle(); let client = cx.global::>().clone(); - let feedback_text = self.editor.read(cx).text(cx); let specs = self.system_specs.clone(); cx.spawn(|_, mut cx| async move { From 8f61134e7ea4e13bb099fa9205baef23135885e9 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Thu, 2 Feb 2023 20:11:07 +0200 Subject: [PATCH 071/180] Allow comparing ViewHandle to AnyViewHandle Since they both have a window_id and a view_id. Co-Authored-By: Antonio Scandurra --- crates/gpui/src/app.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 95967ed485..a72f7fc5c2 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -4915,6 +4915,12 @@ impl From> for AnyViewHandle { } } +impl PartialEq> for AnyViewHandle { + fn eq(&self, other: &ViewHandle) -> bool { + self.window_id == other.window_id && self.view_id == other.view_id + } +} + impl Drop for AnyViewHandle { fn drop(&mut self) { self.ref_counts From 2b0592da2137873870e6885e6acf7f14640c7c0a Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Thu, 2 Feb 2023 20:14:24 +0200 Subject: [PATCH 072/180] Guard against tab_bar_context_menu We don't want to have the tab_bar_context_menu as the active item of the pane where the split started from Co-Authored-By: Antonio Scandurra --- crates/workspace/src/pane.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 7e56b864bf..cc1d8f0ad2 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1527,7 +1527,7 @@ impl View for Pane { } cx.focus(active_item); - } else { + } else if focused != self.tab_bar_context_menu { self.last_focused_view_by_item .insert(active_item.id(), focused.downgrade()); } From 1afd6f859dd9f3d0a1ea6ae7de8abef9463bca77 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Thu, 2 Feb 2023 13:38:25 -0500 Subject: [PATCH 073/180] Fix discourse release action Co-Authored-By: Julia <30666851+ForLoveOfCats@users.noreply.github.com> --- .github/workflows/release_actions.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index a5949127f5..74a96d1a15 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -22,14 +22,19 @@ jobs: ${{ github.event.release.body }} ``` discourse_release: + if: ${{ ! github.event.release.prerelease }} runs-on: ubuntu-latest steps: + - uses: actions/checkout@v3 - name: Install Node uses: actions/setup-node@v2 - if: ${{ ! github.event.release.prerelease }} with: - node-version: '16' - - run: script/discourse_release ${{ secrets.DISCOURSE_RELEASES_API_KEY }} ${{ github.event.release.tag_name }} ${{ github.event.release.body }} + node-version: "19" + - run: > + node "./script/discourse_release" + ${{ secrets.DISCOURSE_RELEASES_API_KEY }} + ${{ github.event.release.tag_name }} + ${{ github.event.release.body }} mixpanel_release: runs-on: ubuntu-latest steps: From 3f95788d45595ad50c0df7b221ed2c5fdcdc0710 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Thu, 2 Feb 2023 13:38:41 -0500 Subject: [PATCH 074/180] Clean up whitespace --- .github/workflows/release_actions.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 74a96d1a15..cc1c66d949 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -13,12 +13,12 @@ jobs: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} content: | 📣 Zed ${{ github.event.release.tag_name }} was just released! - + Restart your Zed or head to https://zed.dev/releases/latest to grab it. - + ```md # Changelog - + ${{ github.event.release.body }} ``` discourse_release: @@ -45,7 +45,7 @@ jobs: architecture: "x64" cache: "pip" - run: pip install -r script/mixpanel_release/requirements.txt - - run: > + - run: > python script/mixpanel_release/main.py ${{ github.event.release.tag_name }} ${{ secrets.MIXPANEL_PROJECT_ID }} From d6b728409fbff8d6230527a40f6922d34100d15b Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Fri, 3 Feb 2023 12:14:13 +0200 Subject: [PATCH 075/180] Be consistent in the app & context menus --- crates/zed/src/menus.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index d238514917..82b72611c2 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -293,7 +293,7 @@ pub fn menus() -> Vec> { action: Box::new(editor::GoToTypeDefinition), }, MenuItem::Action { - name: "Go to References", + name: "Find All References", action: Box::new(editor::FindAllReferences), }, MenuItem::Action { From 3014cc52996f45a2554d195c69481982a233f403 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Fri, 3 Feb 2023 12:16:09 +0200 Subject: [PATCH 076/180] Do not capitalize prepositions in title case This also match the app menu --- crates/editor/src/mouse_context_menu.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index d9840fd3fa..77b58d1a0b 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -52,8 +52,8 @@ pub fn deploy_context_menu( AnchorCorner::TopLeft, vec![ ContextMenuItem::item("Rename Symbol", Rename), - ContextMenuItem::item("Go To Definition", GoToDefinition), - ContextMenuItem::item("Go To Type Definition", GoToTypeDefinition), + ContextMenuItem::item("Go to Definition", GoToDefinition), + ContextMenuItem::item("Go to Type Definition", GoToTypeDefinition), ContextMenuItem::item("Find All References", FindAllReferences), ContextMenuItem::item( "Code Actions", From 9742bd7fd425a20480a2e91c43dc7e216ca550c7 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 3 Feb 2023 08:14:14 -0500 Subject: [PATCH 077/180] Reduce length of feedback placeholder text --- crates/feedback/src/feedback_editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index e2fbc68f70..4120f38cfd 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -31,7 +31,7 @@ use workspace::{ use crate::system_specs::SystemSpecs; const FEEDBACK_CHAR_LIMIT: RangeInclusive = 10..=5000; -const FEEDBACK_PLACEHOLDER_TEXT: &str = "Thanks for spending time with Zed. Enter your feedback here as Markdown. Save the tab to submit your feedback."; +const FEEDBACK_PLACEHOLDER_TEXT: &str = "Save to submit feedback as Markdown."; const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = "Feedback failed to submit, see error log for details."; From 303216291be4fdebb4da4f5fbc047001647abeed Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 3 Feb 2023 11:17:50 -0800 Subject: [PATCH 078/180] Move sharing status indicator out of the call crate and into collab_ui in order so that the model doesn't depend on the view --- crates/call/src/call.rs | 41 +--- crates/collab_ui/src/collab_titlebar_item.rs | 23 ++- crates/collab_ui/src/collab_ui.rs | 187 ++++++++++-------- .../src/sharing_status_indicator.rs} | 24 ++- 4 files changed, 149 insertions(+), 126 deletions(-) rename crates/{call/src/indicator.rs => collab_ui/src/sharing_status_indicator.rs} (53%) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index c34d124162..596a0ec853 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -1,4 +1,3 @@ -mod indicator; pub mod participant; pub mod room; @@ -10,22 +9,17 @@ use collections::HashSet; use postage::watch; use gpui::{ - actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, - Subscription, Task, ViewHandle, WeakModelHandle, + AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, + Subscription, Task, WeakModelHandle, }; use project::Project; -use settings::Settings; -use indicator::SharingStatusIndicator; pub use participant::ParticipantLocation; pub use room::Room; -actions!(collab, [ToggleScreenSharing]); - pub fn init(client: Arc, user_store: ModelHandle, cx: &mut MutableAppContext) { let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx)); cx.set_global(active_call); - cx.add_global_action(toggle_screen_sharing); } #[derive(Clone)] @@ -36,6 +30,7 @@ pub struct IncomingCall { pub initial_project: Option, } +/// Singleton global maintaining the user's participation in a room across workspaces. pub struct ActiveCall { room: Option<(ModelHandle, Vec)>, location: Option>, @@ -46,7 +41,6 @@ pub struct ActiveCall { ), client: Arc, user_store: ModelHandle, - sharing_status_indicator: Option<(usize, ViewHandle)>, _subscriptions: Vec, } @@ -71,7 +65,6 @@ impl ActiveCall { ], client, user_store, - sharing_status_indicator: None, } } @@ -290,8 +283,6 @@ impl ActiveCall { this.set_room(None, cx).detach_and_log_err(cx); } - this.set_sharing_status(room.read(cx).is_screen_sharing(), cx); - cx.notify(); }), cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())), @@ -316,30 +307,4 @@ impl ActiveCall { pub fn pending_invites(&self) -> &HashSet { &self.pending_invites } - - pub fn set_sharing_status(&mut self, is_screen_sharing: bool, cx: &mut MutableAppContext) { - if is_screen_sharing { - if self.sharing_status_indicator.is_none() - && cx.global::().show_call_status_icon - { - self.sharing_status_indicator = - Some(cx.add_status_bar_item(|_| SharingStatusIndicator)); - } - } else if let Some((window_id, _)) = self.sharing_status_indicator.take() { - cx.remove_status_bar_item(window_id); - } - } -} - -pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut MutableAppContext) { - if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { - let toggle_screen_sharing = room.update(cx, |room, cx| { - if room.is_screen_sharing() { - Task::ready(room.unshare_screen(cx)) - } else { - room.share_screen(cx) - } - }); - toggle_screen_sharing.detach_and_log_err(cx); - } } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 116a331578..778aa4a71e 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -1,5 +1,5 @@ use crate::{contact_notification::ContactNotification, contacts_popover}; -use call::{ActiveCall, ParticipantLocation, ToggleScreenSharing}; +use call::{ActiveCall, ParticipantLocation}; use client::{proto::PeerId, Authenticate, ContactEventKind, User, UserStore}; use clock::ReplicaId; use contacts_popover::ContactsPopover; @@ -10,17 +10,21 @@ use gpui::{ geometry::{rect::RectF, vector::vec2f, PathBuilder}, json::{self, ToJson}, Border, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, - Subscription, View, ViewContext, ViewHandle, WeakViewHandle, + Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use settings::Settings; use std::ops::Range; use theme::Theme; use workspace::{FollowNextCollaborator, JoinProject, ToggleFollow, Workspace}; -actions!(collab, [ToggleCollaborationMenu, ShareProject]); +actions!( + collab, + [ToggleCollaborationMenu, ToggleScreenSharing, ShareProject] +); pub fn init(cx: &mut MutableAppContext) { cx.add_action(CollabTitlebarItem::toggle_contacts_popover); + cx.add_global_action(CollabTitlebarItem::toggle_screen_sharing); cx.add_action(CollabTitlebarItem::share_project); } @@ -168,6 +172,19 @@ impl CollabTitlebarItem { cx.notify(); } + pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut MutableAppContext) { + if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { + let toggle_screen_sharing = room.update(cx, |room, cx| { + if room.is_screen_sharing() { + Task::ready(room.unshare_screen(cx)) + } else { + room.share_screen(cx) + } + }); + toggle_screen_sharing.detach_and_log_err(cx); + } + } + fn render_toggle_contacts_button( &self, theme: &Theme, diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 38a47e87dc..d26e2c99cc 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -6,14 +6,17 @@ mod contacts_popover; mod incoming_call_notification; mod notifications; mod project_shared_notification; +mod sharing_status_indicator; use anyhow::anyhow; use call::ActiveCall; pub use collab_titlebar_item::{CollabTitlebarItem, ToggleCollaborationMenu}; -use gpui::MutableAppContext; +use gpui::{actions, MutableAppContext, Task}; use std::sync::Arc; use workspace::{AppState, JoinProject, ToggleFollow, Workspace}; +actions!(collab, [ToggleScreenSharing]); + pub fn init(app_state: Arc, cx: &mut MutableAppContext) { collab_titlebar_item::init(cx); contact_notification::init(cx); @@ -22,89 +25,107 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { contacts_popover::init(cx); incoming_call_notification::init(cx); project_shared_notification::init(cx); + sharing_status_indicator::init(cx); + cx.add_global_action(toggle_screen_sharing); cx.add_global_action(move |action: &JoinProject, cx| { - let project_id = action.project_id; - let follow_user_id = action.follow_user_id; - let app_state = app_state.clone(); - cx.spawn(|mut cx| async move { - let existing_workspace = cx.update(|cx| { - cx.window_ids() - .filter_map(|window_id| cx.root_view::(window_id)) - .find(|workspace| { - workspace.read(cx).project().read(cx).remote_id() == Some(project_id) - }) - }); - - let workspace = if let Some(existing_workspace) = existing_workspace { - existing_workspace - } else { - let active_call = cx.read(ActiveCall::global); - let room = active_call - .read_with(&cx, |call, _| call.room().cloned()) - .ok_or_else(|| anyhow!("not in a call"))?; - let project = room - .update(&mut cx, |room, cx| { - room.join_project( - project_id, - app_state.languages.clone(), - app_state.fs.clone(), - cx, - ) - }) - .await?; - - let (_, workspace) = cx.add_window( - (app_state.build_window_options)(None, None, cx.platform().as_ref()), - |cx| { - let mut workspace = Workspace::new( - Default::default(), - 0, - project, - app_state.dock_default_item_factory, - cx, - ); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - workspace - }, - ); - workspace - }; - - cx.activate_window(workspace.window_id()); - cx.platform().activate(true); - - workspace.update(&mut cx, |workspace, cx| { - if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { - let follow_peer_id = room - .read(cx) - .remote_participants() - .iter() - .find(|(_, participant)| participant.user.id == follow_user_id) - .map(|(_, p)| p.peer_id) - .or_else(|| { - // If we couldn't follow the given user, follow the host instead. - let collaborator = workspace - .project() - .read(cx) - .collaborators() - .values() - .find(|collaborator| collaborator.replica_id == 0)?; - Some(collaborator.peer_id) - }); - - if let Some(follow_peer_id) = follow_peer_id { - if !workspace.is_following(follow_peer_id) { - workspace - .toggle_follow(&ToggleFollow(follow_peer_id), cx) - .map(|follow| follow.detach_and_log_err(cx)); - } - } - } - }); - - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + join_project(action, app_state.clone(), cx); }); } + +pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut MutableAppContext) { + if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { + let toggle_screen_sharing = room.update(cx, |room, cx| { + if room.is_screen_sharing() { + Task::ready(room.unshare_screen(cx)) + } else { + room.share_screen(cx) + } + }); + toggle_screen_sharing.detach_and_log_err(cx); + } +} + +fn join_project(action: &JoinProject, app_state: Arc, cx: &mut MutableAppContext) { + let project_id = action.project_id; + let follow_user_id = action.follow_user_id; + cx.spawn(|mut cx| async move { + let existing_workspace = cx.update(|cx| { + cx.window_ids() + .filter_map(|window_id| cx.root_view::(window_id)) + .find(|workspace| { + workspace.read(cx).project().read(cx).remote_id() == Some(project_id) + }) + }); + + let workspace = if let Some(existing_workspace) = existing_workspace { + existing_workspace + } else { + let active_call = cx.read(ActiveCall::global); + let room = active_call + .read_with(&cx, |call, _| call.room().cloned()) + .ok_or_else(|| anyhow!("not in a call"))?; + let project = room + .update(&mut cx, |room, cx| { + room.join_project( + project_id, + app_state.languages.clone(), + app_state.fs.clone(), + cx, + ) + }) + .await?; + + let (_, workspace) = cx.add_window( + (app_state.build_window_options)(None, None, cx.platform().as_ref()), + |cx| { + let mut workspace = Workspace::new( + Default::default(), + 0, + project, + app_state.dock_default_item_factory, + cx, + ); + (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + workspace + }, + ); + workspace + }; + + cx.activate_window(workspace.window_id()); + cx.platform().activate(true); + + workspace.update(&mut cx, |workspace, cx| { + if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { + let follow_peer_id = room + .read(cx) + .remote_participants() + .iter() + .find(|(_, participant)| participant.user.id == follow_user_id) + .map(|(_, p)| p.peer_id) + .or_else(|| { + // If we couldn't follow the given user, follow the host instead. + let collaborator = workspace + .project() + .read(cx) + .collaborators() + .values() + .find(|collaborator| collaborator.replica_id == 0)?; + Some(collaborator.peer_id) + }); + + if let Some(follow_peer_id) = follow_peer_id { + if !workspace.is_following(follow_peer_id) { + workspace + .toggle_follow(&ToggleFollow(follow_peer_id), cx) + .map(|follow| follow.detach_and_log_err(cx)); + } + } + } + }); + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); +} diff --git a/crates/call/src/indicator.rs b/crates/collab_ui/src/sharing_status_indicator.rs similarity index 53% rename from crates/call/src/indicator.rs rename to crates/collab_ui/src/sharing_status_indicator.rs index 102ea5c551..42c2aa59f2 100644 --- a/crates/call/src/indicator.rs +++ b/crates/collab_ui/src/sharing_status_indicator.rs @@ -1,10 +1,30 @@ +use call::ActiveCall; use gpui::{ color::Color, elements::{MouseEventHandler, Svg}, - Appearance, Element, ElementBox, Entity, MouseButton, RenderContext, View, + Appearance, Element, ElementBox, Entity, MouseButton, MutableAppContext, RenderContext, View, }; +use settings::Settings; -use crate::ToggleScreenSharing; +use crate::collab_titlebar_item::ToggleScreenSharing; + +pub fn init(cx: &mut MutableAppContext) { + let active_call = ActiveCall::global(cx); + + let mut status_indicator = None; + cx.observe(&active_call, move |call, cx| { + if let Some(room) = call.read(cx).room() { + if room.read(cx).is_screen_sharing() { + if status_indicator.is_none() && cx.global::().show_call_status_icon { + status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator)); + } + } else if let Some((window_id, _)) = status_indicator.take() { + cx.remove_status_bar_item(window_id); + } + } + }) + .detach(); +} pub struct SharingStatusIndicator; From 3e92e4d1100591efeb935752e5421d3c6077c67d Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 3 Feb 2023 12:47:20 -0800 Subject: [PATCH 079/180] fix unsaved change --- crates/collab_ui/src/collab_titlebar_item.rs | 23 +++---------------- .../collab_ui/src/sharing_status_indicator.rs | 2 +- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 778aa4a71e..9f2c0fbee9 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -1,4 +1,4 @@ -use crate::{contact_notification::ContactNotification, contacts_popover}; +use crate::{contact_notification::ContactNotification, contacts_popover, ToggleScreenSharing}; use call::{ActiveCall, ParticipantLocation}; use client::{proto::PeerId, Authenticate, ContactEventKind, User, UserStore}; use clock::ReplicaId; @@ -10,21 +10,17 @@ use gpui::{ geometry::{rect::RectF, vector::vec2f, PathBuilder}, json::{self, ToJson}, Border, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, - Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, + Subscription, View, ViewContext, ViewHandle, WeakViewHandle, }; use settings::Settings; use std::ops::Range; use theme::Theme; use workspace::{FollowNextCollaborator, JoinProject, ToggleFollow, Workspace}; -actions!( - collab, - [ToggleCollaborationMenu, ToggleScreenSharing, ShareProject] -); +actions!(collab, [ToggleCollaborationMenu, ShareProject]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(CollabTitlebarItem::toggle_contacts_popover); - cx.add_global_action(CollabTitlebarItem::toggle_screen_sharing); cx.add_action(CollabTitlebarItem::share_project); } @@ -172,19 +168,6 @@ impl CollabTitlebarItem { cx.notify(); } - pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut MutableAppContext) { - if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { - let toggle_screen_sharing = room.update(cx, |room, cx| { - if room.is_screen_sharing() { - Task::ready(room.unshare_screen(cx)) - } else { - room.share_screen(cx) - } - }); - toggle_screen_sharing.detach_and_log_err(cx); - } - } - fn render_toggle_contacts_button( &self, theme: &Theme, diff --git a/crates/collab_ui/src/sharing_status_indicator.rs b/crates/collab_ui/src/sharing_status_indicator.rs index 42c2aa59f2..541194ec66 100644 --- a/crates/collab_ui/src/sharing_status_indicator.rs +++ b/crates/collab_ui/src/sharing_status_indicator.rs @@ -6,7 +6,7 @@ use gpui::{ }; use settings::Settings; -use crate::collab_titlebar_item::ToggleScreenSharing; +use crate::ToggleScreenSharing; pub fn init(cx: &mut MutableAppContext) { let active_call = ActiveCall::global(cx); From 83e21387af8bb8bf136b99762fc44b13e82ae32f Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Sat, 4 Feb 2023 22:18:07 -0500 Subject: [PATCH 080/180] Inform user that telemetry can be disabled --- crates/zed/src/zed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fc7f96a384..b6a2be6e72 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -612,7 +612,7 @@ fn open_telemetry_log_file( 0..0, concat!( "// Zed collects anonymous usage data to help us understand how people are using the app.\n", - "// After the beta release, we'll provide the ability to opt out of this telemetry.\n", + "// Telemetry can be disabled via the `settings.json` file.\n", "// Here is the data that has been reported for the current session:\n", "\n" ), From 4642817e72d96cef5af57253c05dd9023e8a0dbd Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sun, 5 Feb 2023 23:21:29 -0800 Subject: [PATCH 081/180] Add lua syntax highlighting and lsp support --- Cargo.lock | 109 +++++++++++ crates/lsp/src/lsp.rs | 7 +- crates/zed/Cargo.toml | 2 + crates/zed/src/languages.rs | 6 + crates/zed/src/languages/lua.rs | 114 ++++++++++++ crates/zed/src/languages/lua/brackets.scm | 3 + crates/zed/src/languages/lua/config.toml | 15 ++ crates/zed/src/languages/lua/highlights.scm | 192 ++++++++++++++++++++ crates/zed/src/languages/lua/indents.scm | 10 + crates/zed/src/languages/lua/outline.scm | 3 + 10 files changed, 458 insertions(+), 3 deletions(-) create mode 100644 crates/zed/src/languages/lua.rs create mode 100644 crates/zed/src/languages/lua/brackets.scm create mode 100644 crates/zed/src/languages/lua/config.toml create mode 100644 crates/zed/src/languages/lua/highlights.scm create mode 100644 crates/zed/src/languages/lua/indents.scm create mode 100644 crates/zed/src/languages/lua/outline.scm diff --git a/Cargo.lock b/Cargo.lock index 1b7d2038b5..203ceff0df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,6 +259,21 @@ dependencies = [ "futures-lite", ] +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + [[package]] name = "async-io" version = "1.12.0" @@ -350,6 +365,32 @@ dependencies = [ "syn", ] +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils 0.8.14", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite 0.2.9", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.3" @@ -371,6 +412,20 @@ dependencies = [ "syn", ] +[[package]] +name = "async-tar" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c49359998a76e32ef6e870dbc079ebad8f1e53e8441c5dd39d27b44493fe331" +dependencies = [ + "async-std", + "filetime", + "libc", + "pin-project", + "redox_syscall", + "xattr", +] + [[package]] name = "async-task" version = "4.0.3" @@ -2080,6 +2135,18 @@ dependencies = [ "workspace", ] +[[package]] +name = "filetime" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "windows-sys 0.42.0", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2528,6 +2595,18 @@ dependencies = [ "regex", ] +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "go_to_line" version = "0.1.0" @@ -3144,6 +3223,15 @@ dependencies = [ "arrayvec 0.7.2", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "language" version = "0.1.0" @@ -7017,6 +7105,16 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-lua" +version = "0.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d489873fd1a2fa6d5f04930bfc5c081c96f0c038c1437104518b5b842c69b282" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-markdown" version = "0.0.1" @@ -8194,6 +8292,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + [[package]] name = "xml-rs" version = "0.8.4" @@ -8236,6 +8343,7 @@ dependencies = [ "assets", "async-compression", "async-recursion 0.3.2", + "async-tar", "async-trait", "auto_update", "backtrace", @@ -8313,6 +8421,7 @@ dependencies = [ "tree-sitter-go", "tree-sitter-html", "tree-sitter-json 0.20.0", + "tree-sitter-lua", "tree-sitter-markdown", "tree-sitter-python", "tree-sitter-racket", diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index b7199a5287..a535cfd252 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -1,3 +1,4 @@ +use log::warn; pub use lsp_types::request::*; pub use lsp_types::*; @@ -220,10 +221,10 @@ impl LanguageServer { } } } else { - return Err(anyhow!( - "failed to deserialize message:\n{}", + warn!( + "Failed to deserialize message:\n{}", std::str::from_utf8(&buffer)? - )); + ); } // Don't starve the main thread when receiving lots of messages at once. diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index b5df007129..d1ade12f60 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -60,6 +60,7 @@ vim = { path = "../vim" } workspace = { path = "../workspace" } anyhow = "1.0.38" async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] } +async-tar = "0.4.2" async-recursion = "0.3" async-trait = "0.1" backtrace = "0.3" @@ -109,6 +110,7 @@ tree-sitter-ruby = "0.20.0" tree-sitter-html = "0.19.0" tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"} tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} +tree-sitter-lua = "0.0.14" url = "2.2" urlencoding = "2.1.2" uuid = { version = "1.1.2", features = ["v4"] } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 548c07fb82..c0a00d1911 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -10,6 +10,7 @@ mod html; mod installation; mod json; mod language_plugin; +mod lua; mod python; mod ruby; mod rust; @@ -122,6 +123,11 @@ pub fn init(languages: Arc) { tree_sitter_racket::language(), None, // ), + ( + "lua", + tree_sitter_lua::language(), + Some(Box::new(lua::LuaLspAdapter)), + ), ] { languages.register(name, load_config(name), grammar, lsp_adapter, load_queries); } diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs new file mode 100644 index 0000000000..85a8a2f569 --- /dev/null +++ b/crates/zed/src/languages/lua.rs @@ -0,0 +1,114 @@ +use std::{any::Any, env::consts, path::PathBuf, sync::Arc}; + +use anyhow::{anyhow, bail, Result}; +use async_compression::futures::bufread::GzipDecoder; +use async_tar::Archive; +use async_trait::async_trait; +use client::http::HttpClient; +use futures::{io::BufReader, StreamExt}; +use language::LanguageServerName; +use lazy_static::lazy_static; +use regex::Regex; +use smol::fs; +use util::{async_iife, ResultExt}; + +use super::installation::{latest_github_release, GitHubLspBinaryVersion}; + +#[derive(Copy, Clone)] +pub struct LuaLspAdapter; + +lazy_static! { + static ref LUALS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap(); +} + +#[async_trait] +impl super::LspAdapter for LuaLspAdapter { + async fn name(&self) -> LanguageServerName { + LanguageServerName("lua-language-server".into()) + } + + async fn server_args(&self) -> Vec { + vec![ + "--logpath=~/lua-language-server.log".into(), + "--loglevel=trace".into(), + ] + } + + async fn fetch_latest_server_version( + &self, + http: Arc, + ) -> Result> { + let release = latest_github_release("LuaLS/lua-language-server", http).await?; + let version = release.name.clone(); + let platform = match consts::ARCH { + "x86_64" => "x64", + "aarch64" => "arm64", + other => bail!("Running on unsupported platform: {other}"), + }; + let asset_name = format!("lua-language-server-{version}-darwin-{platform}.tar.gz"); + 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.clone(), + url: asset.browser_download_url.clone(), + }; + Ok(Box::new(version) as Box<_>) + } + + async fn fetch_server_binary( + &self, + version: Box, + http: Arc, + container_dir: PathBuf, + ) -> Result { + let version = version.downcast::().unwrap(); + + let binary_path = container_dir.join("bin/lua-language-server"); + + if fs::metadata(&binary_path).await.is_err() { + let mut response = http + .get(&version.url, Default::default(), true) + .await + .map_err(|err| anyhow!("error downloading release: {}", err))?; + let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); + let archive = Archive::new(decompressed_bytes); + archive.unpack(container_dir).await?; + } + + fs::set_permissions( + &binary_path, + ::from_mode(0o755), + ) + .await?; + Ok(binary_path) + } + + async fn cached_server_binary(&self, 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(path) + } else { + Err(anyhow!("no cached binary")) + } + }) + .await + .log_err() + } +} diff --git a/crates/zed/src/languages/lua/brackets.scm b/crates/zed/src/languages/lua/brackets.scm new file mode 100644 index 0000000000..5f5bd60b93 --- /dev/null +++ b/crates/zed/src/languages/lua/brackets.scm @@ -0,0 +1,3 @@ +("[" @open "]" @close) +("{" @open "}" @close) +("(" @open ")" @close) \ No newline at end of file diff --git a/crates/zed/src/languages/lua/config.toml b/crates/zed/src/languages/lua/config.toml new file mode 100644 index 0000000000..effb37f945 --- /dev/null +++ b/crates/zed/src/languages/lua/config.toml @@ -0,0 +1,15 @@ +name = "Lua" +path_suffixes = ["lua"] +line_comment = "-- " +autoclose_before = ",]}" +brackets = [ +{ start = "{", end = "}", close = true, newline = true }, +{ start = "[", end = "]", close = true, newline = true }, +{ start = "\"", end = "\"", close = true, newline = false }, +] + +[overrides.string] +brackets = [ +{ start = "{", end = "}", close = true, newline = true }, +{ start = "[", end = "]", close = true, newline = true }, +] \ No newline at end of file diff --git a/crates/zed/src/languages/lua/highlights.scm b/crates/zed/src/languages/lua/highlights.scm new file mode 100644 index 0000000000..96389c79b4 --- /dev/null +++ b/crates/zed/src/languages/lua/highlights.scm @@ -0,0 +1,192 @@ +;; Keywords + +"return" @keyword + +[ + "goto" + "in" + "local" +] @keyword + +(break_statement) @keyword + +(do_statement +[ + "do" + "end" +] @keyword) + +(while_statement +[ + "while" + "do" + "end" +] @keyword) + +(repeat_statement +[ + "repeat" + "until" +] @keyword) + +(if_statement +[ + "if" + "elseif" + "else" + "then" + "end" +] @keyword) + +(elseif_statement +[ + "elseif" + "then" + "end" +] @keyword) + +(else_statement +[ + "else" + "end" +] @keyword) + +(for_statement +[ + "for" + "do" + "end" +] @keyword) + +(function_declaration +[ + "function" + "end" +] @keyword) + +(function_definition +[ + "function" + "end" +] @keyword) + +;; Operators + +[ + "and" + "not" + "or" +] @operator + +[ + "+" + "-" + "*" + "/" + "%" + "^" + "#" + "==" + "~=" + "<=" + ">=" + "<" + ">" + "=" + "&" + "~" + "|" + "<<" + ">>" + "//" + ".." +] @operator + +;; Punctuations + +[ + ";" + ":" + "," + "." +] @punctuation.delimiter + +;; Brackets + +[ + "(" + ")" + "[" + "]" + "{" + "}" +] @punctuation.bracket + +;; Variables + +(identifier) @variable + +((identifier) @variable.special + (#eq? @variable.special "self")) + +(variable_list + attribute: (attribute + (["<" ">"] @punctuation.bracket + (identifier) @attribute))) + +;; Constants + +((identifier) @constant + (#lua-match? @constant "^[A-Z][A-Z_0-9]*$")) + +(vararg_expression) @constant + +(nil) @constant.builtin + +[ + (false) + (true) +] @boolean + +;; Tables + +(field name: (identifier) @field) + +(dot_index_expression field: (identifier) @field) + +(table_constructor +[ + "{" + "}" +] @constructor) + +;; Functions + +(parameters (identifier) @parameter) + +(function_call name: (identifier) @function.call) +(function_declaration name: (identifier) @function) + +(function_call name: (dot_index_expression field: (identifier) @function.call)) +(function_declaration name: (dot_index_expression field: (identifier) @function)) + +(method_index_expression method: (identifier) @method) + +(function_call + (identifier) @function.builtin + (#any-of? @function.builtin + ;; built-in functions in Lua 5.1 + "assert" "collectgarbage" "dofile" "error" "getfenv" "getmetatable" "ipairs" + "load" "loadfile" "loadstring" "module" "next" "pairs" "pcall" "print" + "rawequal" "rawget" "rawset" "require" "select" "setfenv" "setmetatable" + "tonumber" "tostring" "type" "unpack" "xpcall")) + +;; Others + +(comment) @comment + +(hash_bang_line) @preproc + +(number) @number + +(string) @string \ No newline at end of file diff --git a/crates/zed/src/languages/lua/indents.scm b/crates/zed/src/languages/lua/indents.scm new file mode 100644 index 0000000000..71e15a0c33 --- /dev/null +++ b/crates/zed/src/languages/lua/indents.scm @@ -0,0 +1,10 @@ +(if_statement "end" @end) @indent +(do_statement "end" @end) @indent +(while_statement "end" @end) @indent +(for_statement "end" @end) @indent +(repeat_statement "until" @end) @indent +(function_declaration "end" @end) @indent + +(_ "[" "]" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent \ No newline at end of file diff --git a/crates/zed/src/languages/lua/outline.scm b/crates/zed/src/languages/lua/outline.scm new file mode 100644 index 0000000000..8bd8d88070 --- /dev/null +++ b/crates/zed/src/languages/lua/outline.scm @@ -0,0 +1,3 @@ +(function_declaration + "function" @context + name: (_) @name) @item \ No newline at end of file From 035901127abace0886095ce03cd7af1c7255a60b Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sun, 5 Feb 2023 23:25:20 -0800 Subject: [PATCH 082/180] remove unused version regex --- crates/zed/src/languages/lua.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index 85a8a2f569..4bcffca908 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -7,8 +7,6 @@ use async_trait::async_trait; use client::http::HttpClient; use futures::{io::BufReader, StreamExt}; use language::LanguageServerName; -use lazy_static::lazy_static; -use regex::Regex; use smol::fs; use util::{async_iife, ResultExt}; @@ -17,10 +15,6 @@ use super::installation::{latest_github_release, GitHubLspBinaryVersion}; #[derive(Copy, Clone)] pub struct LuaLspAdapter; -lazy_static! { - static ref LUALS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap(); -} - #[async_trait] impl super::LspAdapter for LuaLspAdapter { async fn name(&self) -> LanguageServerName { From d4d9a142fcb2507f3a892b225d84e95cda3d6470 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 6 Feb 2023 17:41:36 -0500 Subject: [PATCH 083/180] Implement a button for submitting feedback Co-Authored-By: Kay Simmons <3323631+Kethku@users.noreply.github.com> --- crates/feedback/src/feedback_editor.rs | 98 +++++++++++++++++++++----- crates/theme/src/theme.rs | 7 ++ crates/zed/src/zed.rs | 5 +- styles/src/styleTree/app.ts | 2 + styles/src/styleTree/feedback.ts | 37 ++++++++++ 5 files changed, 132 insertions(+), 17 deletions(-) create mode 100644 styles/src/styleTree/feedback.ts diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 4120f38cfd..bd9f32e73c 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -25,7 +25,7 @@ use settings::Settings; use workspace::{ item::{Item, ItemHandle}, searchable::{SearchableItem, SearchableItemHandle}, - AppState, StatusItemView, Workspace, + AppState, StatusItemView, ToolbarItemLocation, ToolbarItemView, Workspace, }; use crate::system_specs::SystemSpecs; @@ -35,7 +35,7 @@ const FEEDBACK_PLACEHOLDER_TEXT: &str = "Save to submit feedback as Markdown."; const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = "Feedback failed to submit, see error log for details."; -actions!(feedback, [SubmitFeedback, GiveFeedback, DeployFeedback]); +actions!(feedback, [GiveFeedback, SubmitFeedback]); pub fn init(system_specs: SystemSpecs, app_state: Arc, cx: &mut MutableAppContext) { cx.add_action({ @@ -43,17 +43,27 @@ pub fn init(system_specs: SystemSpecs, app_state: Arc, cx: &mut Mutabl FeedbackEditor::deploy(system_specs.clone(), workspace, app_state.clone(), cx); } }); + + cx.add_async_action( + |toolbar_button: &mut ToolbarButton, _: &SubmitFeedback, cx| { + if let Some(active_item) = toolbar_button.active_item.as_ref() { + Some(active_item.update(cx, |feedback_editor, cx| feedback_editor.handle_save(cx))) + } else { + None + } + }, + ); } -pub struct FeedbackButton; +pub struct StatusBarButton; -impl Entity for FeedbackButton { +impl Entity for StatusBarButton { type Event = (); } -impl View for FeedbackButton { +impl View for StatusBarButton { fn ui_name() -> &'static str { - "FeedbackButton" + "StatusBarButton" } fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { @@ -77,7 +87,7 @@ impl View for FeedbackButton { } } -impl StatusItemView for FeedbackButton { +impl StatusItemView for StatusBarButton { fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext) {} } @@ -120,11 +130,7 @@ impl FeedbackEditor { } } - fn handle_save( - &mut self, - _: ModelHandle, - cx: &mut ViewContext, - ) -> Task> { + fn handle_save(&mut self, cx: &mut ViewContext) -> Task> { let feedback_text = self.editor.read(cx).text(cx); let feedback_char_count = feedback_text.chars().count(); let feedback_text = feedback_text.trim().to_string(); @@ -304,19 +310,19 @@ impl Item for FeedbackEditor { fn save( &mut self, - project: ModelHandle, + _: ModelHandle, cx: &mut ViewContext, ) -> Task> { - self.handle_save(project, cx) + self.handle_save(cx) } fn save_as( &mut self, - project: ModelHandle, + _: ModelHandle, _: std::path::PathBuf, cx: &mut ViewContext, ) -> Task> { - self.handle_save(project, cx) + self.handle_save(cx) } fn reload( @@ -435,3 +441,63 @@ impl SearchableItem for FeedbackEditor { .update(cx, |editor, cx| editor.active_match_index(matches, cx)) } } + +pub struct ToolbarButton { + active_item: Option>, +} + +impl ToolbarButton { + pub fn new() -> Self { + Self { + active_item: Default::default(), + } + } +} + +impl Entity for ToolbarButton { + type Event = (); +} + +impl View for ToolbarButton { + fn ui_name() -> &'static str { + "ToolbarButton" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + let theme = cx.global::().theme.clone(); + enum SubmitFeedbackButton {} + MouseEventHandler::::new(0, cx, |state, _| { + let style = theme.feedback.submit_button.style_for(state, false); + Label::new("Submit as Markdown".into(), style.text.clone()) + .contained() + .with_style(style.container) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(SubmitFeedback) + }) + .aligned() + .contained() + .with_margin_left(theme.feedback.button_margin) + .boxed() + } +} + +impl ToolbarItemView for ToolbarButton { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut ViewContext, + ) -> workspace::ToolbarItemLocation { + cx.notify(); + if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::()) + { + self.active_item = Some(feedback_editor); + ToolbarItemLocation::PrimaryRight { flex: None } + } else { + self.active_item = None; + ToolbarItemLocation::Hidden + } + } +} diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index e463310b98..b8ec3e0329 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -36,6 +36,7 @@ pub struct Theme { pub incoming_call_notification: IncomingCallNotification, pub tooltip: TooltipStyle, pub terminal: TerminalStyle, + pub feedback: FeedbackStyle, pub color_scheme: ColorScheme, } @@ -806,6 +807,12 @@ pub struct TerminalStyle { pub dim_foreground: Color, } +#[derive(Clone, Deserialize, Default)] +pub struct FeedbackStyle { + pub submit_button: Interactive, + pub button_margin: f32, +} + #[derive(Clone, Deserialize, Default)] pub struct ColorScheme { pub name: String, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fc7f96a384..c118951b34 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -11,6 +11,7 @@ use collections::VecDeque; pub use editor; use editor::{Editor, MultiBuffer}; +use feedback::feedback_editor::ToolbarButton; use futures::StreamExt; use gpui::{ actions, @@ -287,6 +288,8 @@ pub fn initialize_workspace( toolbar.add_item(buffer_search_bar, cx); let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); toolbar.add_item(project_search_bar, cx); + let submit_feedback_button = cx.add_view(|_| ToolbarButton::new()); + toolbar.add_item(submit_feedback_button, cx); }) }); } @@ -344,7 +347,7 @@ pub fn initialize_workspace( let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - let feedback_button = cx.add_view(|_| feedback::feedback_editor::FeedbackButton {}); + let feedback_button = cx.add_view(|_| feedback::feedback_editor::StatusBarButton {}); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index 267d830506..5d04050fe1 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -19,6 +19,7 @@ import terminal from "./terminal"; import contactList from "./contactList"; import incomingCallNotification from "./incomingCallNotification"; import { ColorScheme } from "../themes/common/colorScheme"; +import feedback from "./feedback"; export default function app(colorScheme: ColorScheme): Object { return { @@ -51,6 +52,7 @@ export default function app(colorScheme: ColorScheme): Object { simpleMessageNotification: simpleMessageNotification(colorScheme), tooltip: tooltip(colorScheme), terminal: terminal(colorScheme), + feedback: feedback(colorScheme), colorScheme: { ...colorScheme, players: Object.values(colorScheme.players), diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts new file mode 100644 index 0000000000..daee2bb969 --- /dev/null +++ b/styles/src/styleTree/feedback.ts @@ -0,0 +1,37 @@ + +import { ColorScheme } from "../themes/common/colorScheme"; +import { background, border, text } from "./components"; + +export default function search(colorScheme: ColorScheme) { + let layer = colorScheme.highest; + + // Currently feedback only needs style for the submit feedback button + return { + submit_button: { + ...text(layer, "mono", "on"), + background: background(layer, "on"), + cornerRadius: 6, + border: border(layer, "on"), + margin: { + right: 4, + }, + padding: { + bottom: 2, + left: 10, + right: 10, + top: 2, + }, + clicked: { + ...text(layer, "mono", "on", "pressed"), + background: background(layer, "on", "pressed"), + border: border(layer, "on", "pressed"), + }, + hover: { + ...text(layer, "mono", "on", "hovered"), + background: background(layer, "on", "hovered"), + border: border(layer, "on", "hovered"), + }, + }, + button_margin: 8 + }; +} From 8228618b9ef4ea1d3fff3134862f501ec5ad31f4 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 6 Feb 2023 18:19:15 -0500 Subject: [PATCH 084/180] Correct theme function name --- styles/src/styleTree/feedback.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts index daee2bb969..eb0f61ecfd 100644 --- a/styles/src/styleTree/feedback.ts +++ b/styles/src/styleTree/feedback.ts @@ -2,7 +2,7 @@ import { ColorScheme } from "../themes/common/colorScheme"; import { background, border, text } from "./components"; -export default function search(colorScheme: ColorScheme) { +export default function feedback(colorScheme: ColorScheme) { let layer = colorScheme.highest; // Currently feedback only needs style for the submit feedback button From 926b59b15d218209d503e06e2a7c43a53454adb9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 6 Feb 2023 15:42:14 -0800 Subject: [PATCH 085/180] Fixed a bug where the command palette wouldn't check the keymap context when showing available actions Fixed a bug where context menus wouldn't show action keystrokes WIP Fixing a bug where tooltips won't show action keystrokes Co-Authored-By: Max --- crates/context_menu/src/context_menu.rs | 10 +++ crates/gpui/src/app.rs | 74 +++++++++++-------- crates/gpui/src/elements/keystroke_label.rs | 11 ++- crates/gpui/src/elements/tooltip.rs | 16 +++- crates/gpui/src/keymap_matcher/binding.rs | 2 +- .../gpui/src/keymap_matcher/keymap_context.rs | 2 +- crates/gpui/src/presenter.rs | 9 --- 7 files changed, 80 insertions(+), 44 deletions(-) diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 0dc0ce6f42..6d5a5cb549 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -63,6 +63,7 @@ pub struct ContextMenu { visible: bool, previously_focused_view_id: Option, clicked: bool, + parent_view_id: usize, _actions_observation: Subscription, } @@ -114,6 +115,8 @@ impl View for ContextMenu { impl ContextMenu { pub fn new(cx: &mut ViewContext) -> Self { + let parent_view_id = cx.parent().unwrap(); + Self { show_count: 0, anchor_position: Default::default(), @@ -123,6 +126,7 @@ impl ContextMenu { visible: Default::default(), previously_focused_view_id: Default::default(), clicked: false, + parent_view_id, _actions_observation: cx.observe_actions(Self::action_dispatched), } } @@ -251,6 +255,7 @@ impl ContextMenu { } fn render_menu_for_measurement(&self, cx: &mut RenderContext) -> impl Element { + let window_id = cx.window_id(); let style = cx.global::().theme.context_menu.clone(); Flex::row() .with_child( @@ -289,6 +294,8 @@ impl ContextMenu { Some(ix) == self.selected_index, ); KeystrokeLabel::new( + window_id, + self.parent_view_id, action.boxed_clone(), style.keystroke.container, style.keystroke.text.clone(), @@ -318,6 +325,7 @@ impl ContextMenu { let style = cx.global::().theme.context_menu.clone(); + let window_id = cx.window_id(); MouseEventHandler::::new(0, cx, |_, cx| { Flex::column() .with_children(self.items.iter().enumerate().map(|(ix, item)| { @@ -337,6 +345,8 @@ impl ContextMenu { ) .with_child({ KeystrokeLabel::new( + window_id, + self.parent_view_id, action.boxed_clone(), style.keystroke.container, style.keystroke.text.clone(), diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 23f5cc26a5..e2490d8dd6 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1333,6 +1333,31 @@ impl MutableAppContext { self.action_deserializers.keys().copied() } + /// Return keystrokes that would dispatch the given action on the given view. + pub(crate) fn keystrokes_for_action( + &mut self, + window_id: usize, + view_id: usize, + action: &dyn Action, + ) -> Option> { + let mut contexts = Vec::new(); + for view_id in self.ancestors(window_id, view_id) { + if let Some(view) = self.views.get(&(window_id, view_id)) { + contexts.push(view.keymap_context(self)); + } + } + + self.keystroke_matcher + .bindings_for_action_type(action.as_any().type_id()) + .find_map(|b| { + if b.match_context(&contexts) { + b.keystrokes().map(|s| s.into()) + } else { + None + } + }) + } + pub fn available_actions( &self, window_id: usize, @@ -1340,8 +1365,10 @@ impl MutableAppContext { ) -> impl Iterator, SmallVec<[&Binding; 1]>)> { let mut action_types: HashSet<_> = self.global_actions.keys().copied().collect(); + let mut contexts = Vec::new(); for view_id in self.ancestors(window_id, view_id) { if let Some(view) = self.views.get(&(window_id, view_id)) { + contexts.push(view.keymap_context(self)); let view_type = view.as_any().type_id(); if let Some(actions) = self.actions.get(&view_type) { action_types.extend(actions.keys().copied()); @@ -1358,6 +1385,7 @@ impl MutableAppContext { deserialize("{}").ok()?, self.keystroke_matcher .bindings_for_action_type(*type_id) + .filter(|b| b.match_context(&contexts)) .collect(), )) } else { @@ -1385,34 +1413,6 @@ impl MutableAppContext { self.global_actions.contains_key(&action_type) } - /// Return keystrokes that would dispatch the given action closest to the focused view, if there are any. - pub(crate) fn keystrokes_for_action( - &mut self, - window_id: usize, - view_stack: &[usize], - action: &dyn Action, - ) -> Option> { - self.keystroke_matcher.contexts.clear(); - for view_id in view_stack.iter().rev() { - let view = self - .cx - .views - .get(&(window_id, *view_id)) - .expect("view in responder chain does not exist"); - self.keystroke_matcher - .contexts - .push(view.keymap_context(self.as_ref())); - let keystrokes = self - .keystroke_matcher - .keystrokes_for_action(action, &self.keystroke_matcher.contexts); - if keystrokes.is_some() { - return keystrokes; - } - } - - None - } - // Traverses the parent tree. Walks down the tree toward the passed // view calling visit with true. Then walks back up the tree calling visit with false. // If `visit` returns false this function will immediately return. @@ -1916,10 +1916,11 @@ impl MutableAppContext { { self.update(|this| { let view_id = post_inc(&mut this.next_entity_id); + // Make sure we can tell child views about their parent + this.cx.parents.insert((window_id, view_id), parent_id); let mut cx = ViewContext::new(this, window_id, view_id); let handle = if let Some(view) = build_view(&mut cx) { this.cx.views.insert((window_id, view_id), Box::new(view)); - this.cx.parents.insert((window_id, view_id), parent_id); if let Some(window) = this.cx.windows.get_mut(&window_id) { window .invalidation @@ -1929,6 +1930,7 @@ impl MutableAppContext { } Some(ViewHandle::new(window_id, view_id, &this.cx.ref_counts)) } else { + this.cx.parents.remove(&(window_id, view_id)); None }; handle @@ -2810,6 +2812,16 @@ impl AppContext { })) } + /// Returns the id of the parent of the given view, or none if the given + /// view is the root. + fn parent(&self, window_id: usize, view_id: usize) -> Option { + if let Some(ParentId::View(view_id)) = self.parents.get(&(window_id, view_id)) { + Some(*view_id) + } else { + None + } + } + pub fn is_child_focused(&self, view: impl Into) -> bool { let view = view.into(); if let Some(focused_view_id) = self.focused_view_id(view.window_id) { @@ -3853,6 +3865,10 @@ impl<'a, T: View> ViewContext<'a, T> { .build_and_insert_view(self.window_id, ParentId::View(self.view_id), build_view) } + pub fn parent(&mut self) -> Option { + self.cx.parent(self.window_id, self.view_id) + } + pub fn reparent(&mut self, view_handle: impl Into) { let view_handle = view_handle.into(); if self.window_id != view_handle.window_id { diff --git a/crates/gpui/src/elements/keystroke_label.rs b/crates/gpui/src/elements/keystroke_label.rs index ca317d9e11..6553b2fa8d 100644 --- a/crates/gpui/src/elements/keystroke_label.rs +++ b/crates/gpui/src/elements/keystroke_label.rs @@ -12,15 +12,21 @@ pub struct KeystrokeLabel { action: Box, container_style: ContainerStyle, text_style: TextStyle, + window_id: usize, + view_id: usize, } impl KeystrokeLabel { pub fn new( + window_id: usize, + view_id: usize, action: Box, container_style: ContainerStyle, text_style: TextStyle, ) -> Self { Self { + window_id, + view_id, action, container_style, text_style, @@ -37,7 +43,10 @@ impl Element for KeystrokeLabel { constraint: SizeConstraint, cx: &mut LayoutContext, ) -> (Vector2F, ElementBox) { - let mut element = if let Some(keystrokes) = cx.keystrokes_for_action(self.action.as_ref()) { + let mut element = if let Some(keystrokes) = + cx.app + .keystrokes_for_action(self.window_id, self.view_id, self.action.as_ref()) + { Flex::row() .with_children(keystrokes.iter().map(|keystroke| { Label::new(keystroke.to_string(), self.text_style.clone()) diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs index dbcecf9c24..e79eefc5f4 100644 --- a/crates/gpui/src/elements/tooltip.rs +++ b/crates/gpui/src/elements/tooltip.rs @@ -61,11 +61,14 @@ impl Tooltip { ) -> Self { struct ElementState(Tag); struct MouseEventHandlerState(Tag); + let focused_view_id = cx.focused_view_id(cx.window_id).unwrap(); let state_handle = cx.default_element_state::, Rc>(id); let state = state_handle.read(cx).clone(); let tooltip = if state.visible.get() { let mut collapsed_tooltip = Self::render_tooltip( + cx.window_id, + focused_view_id, text.clone(), style.clone(), action.as_ref().map(|a| a.boxed_clone()), @@ -74,7 +77,7 @@ impl Tooltip { .boxed(); Some( Overlay::new( - Self::render_tooltip(text, style, action, false) + Self::render_tooltip(cx.window_id, focused_view_id, text, style, action, false) .constrained() .dynamically(move |constraint, cx| { SizeConstraint::strict_along( @@ -128,6 +131,8 @@ impl Tooltip { } pub fn render_tooltip( + window_id: usize, + focused_view_id: usize, text: String, style: TooltipStyle, action: Option>, @@ -145,8 +150,13 @@ impl Tooltip { } }) .with_children(action.map(|action| { - let keystroke_label = - KeystrokeLabel::new(action, style.keystroke.container, style.keystroke.text); + let keystroke_label = KeystrokeLabel::new( + window_id, + focused_view_id, + action, + style.keystroke.container, + style.keystroke.text, + ); if measure { keystroke_label.boxed() } else { diff --git a/crates/gpui/src/keymap_matcher/binding.rs b/crates/gpui/src/keymap_matcher/binding.rs index afd65d4f04..8146437884 100644 --- a/crates/gpui/src/keymap_matcher/binding.rs +++ b/crates/gpui/src/keymap_matcher/binding.rs @@ -41,7 +41,7 @@ impl Binding { }) } - fn match_context(&self, contexts: &[KeymapContext]) -> bool { + pub fn match_context(&self, contexts: &[KeymapContext]) -> bool { self.context_predicate .as_ref() .map(|predicate| predicate.eval(contexts)) diff --git a/crates/gpui/src/keymap_matcher/keymap_context.rs b/crates/gpui/src/keymap_matcher/keymap_context.rs index 28f5f80c83..b19989b210 100644 --- a/crates/gpui/src/keymap_matcher/keymap_context.rs +++ b/crates/gpui/src/keymap_matcher/keymap_context.rs @@ -43,7 +43,7 @@ impl KeymapContextPredicate { pub fn eval(&self, contexts: &[KeymapContext]) -> bool { let Some(context) = contexts.first() else { return false }; match self { - Self::Identifier(name) => context.set.contains(name.as_str()), + Self::Identifier(name) => (&context.set).contains(name.as_str()), Self::Equal(left, right) => context .map .get(left) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 66c991f0a8..c0785e11f3 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -4,7 +4,6 @@ use crate::{ font_cache::FontCache, geometry::rect::RectF, json::{self, ToJson}, - keymap_matcher::Keystroke, platform::{CursorStyle, Event}, scene::{ CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, @@ -604,14 +603,6 @@ pub struct LayoutContext<'a> { } impl<'a> LayoutContext<'a> { - pub(crate) fn keystrokes_for_action( - &mut self, - action: &dyn Action, - ) -> Option> { - self.app - .keystrokes_for_action(self.window_id, &self.view_stack, action) - } - fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F { let print_error = |view_id| { format!( From f0653997995cc14904c69e8402d1186c716e1da0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 6 Feb 2023 16:44:06 -0800 Subject: [PATCH 086/180] Fix crash when unplugging display containing a zed window Co-authored-by: Kay Simmons --- crates/gpui/src/app.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 23f5cc26a5..1cacfa26a1 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -910,15 +910,14 @@ impl MutableAppContext { .map_or(false, |window| window.is_fullscreen) } - pub fn window_bounds(&self, window_id: usize) -> WindowBounds { - self.presenters_and_platform_windows[&window_id].1.bounds() + pub fn window_bounds(&self, window_id: usize) -> Option { + let (_, window) = self.presenters_and_platform_windows.get(&window_id)?; + Some(window.bounds()) } pub fn window_display_uuid(&self, window_id: usize) -> Option { - self.presenters_and_platform_windows[&window_id] - .1 - .screen() - .display_uuid() + let (_, window) = self.presenters_and_platform_windows.get(&window_id)?; + window.screen().display_uuid() } pub fn render_view(&mut self, params: RenderParams) -> Result { @@ -2375,8 +2374,10 @@ impl MutableAppContext { callback(is_fullscreen, this) }); - if let Some(uuid) = this.window_display_uuid(window_id) { - let bounds = this.window_bounds(window_id); + if let Some((uuid, bounds)) = this + .window_display_uuid(window_id) + .zip(this.window_bounds(window_id)) + { let mut bounds_observations = this.window_bounds_observations.clone(); bounds_observations.emit(window_id, this, |callback, this| { callback(bounds, uuid, this) @@ -2560,8 +2561,10 @@ impl MutableAppContext { } fn handle_window_moved(&mut self, window_id: usize) { - if let Some(display) = self.window_display_uuid(window_id) { - let bounds = self.window_bounds(window_id); + if let Some((display, bounds)) = self + .window_display_uuid(window_id) + .zip(self.window_bounds(window_id)) + { self.window_bounds_observations .clone() .emit(window_id, self, move |callback, this| { @@ -3733,10 +3736,6 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.toggle_window_full_screen(self.window_id) } - pub fn window_bounds(&self) -> WindowBounds { - self.app.window_bounds(self.window_id) - } - pub fn prompt( &self, level: PromptLevel, From b020955ac4e3b750d52696f9e43a347f50ee61ba Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 7 Feb 2023 00:10:11 -0800 Subject: [PATCH 087/180] show notification if no recent projects --- crates/recent_projects/src/recent_projects.rs | 27 +++++++++++++------ crates/workspace/src/notifications.rs | 4 +-- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index d7de7ae718..f613ba4df2 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -11,7 +11,10 @@ use highlighted_workspace_location::HighlightedWorkspaceLocation; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate}; use settings::Settings; -use workspace::{OpenPaths, Workspace, WorkspaceLocation, WORKSPACE_DB}; +use workspace::{ + notifications::simple_message_notification::MessageNotification, OpenPaths, Workspace, + WorkspaceLocation, WORKSPACE_DB, +}; actions!(projects, [OpenRecent]); @@ -42,7 +45,7 @@ impl RecentProjectsView { fn toggle(_: &mut Workspace, _: &OpenRecent, cx: &mut ViewContext) { cx.spawn(|workspace, mut cx| async move { - let workspace_locations = cx + let workspace_locations: Vec<_> = cx .background() .spawn(async { WORKSPACE_DB @@ -56,12 +59,20 @@ impl RecentProjectsView { .await; workspace.update(&mut cx, |workspace, cx| { - workspace.toggle_modal(cx, |_, cx| { - let view = cx.add_view(|cx| Self::new(workspace_locations, cx)); - cx.subscribe(&view, Self::on_event).detach(); - view - }); - }) + if !workspace_locations.is_empty() { + workspace.toggle_modal(cx, |_, cx| { + let view = cx.add_view(|cx| Self::new(workspace_locations, cx)); + cx.subscribe(&view, Self::on_event).detach(); + view + }); + } else { + workspace.show_notification(0, cx, |cx| { + cx.add_view(|_| { + MessageNotification::new_message("No recent projects to open.") + }) + }) + } + }); }) .detach(); } diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 43feede190..72dec114d9 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -174,7 +174,7 @@ pub mod simple_message_notification { } impl MessageNotification { - pub fn new_messsage>(message: S) -> MessageNotification { + pub fn new_message>(message: S) -> MessageNotification { Self { message: message.as_ref().to_string(), click_action: None, @@ -320,7 +320,7 @@ where Err(err) => { workspace.show_notification(0, cx, |cx| { cx.add_view(|_cx| { - simple_message_notification::MessageNotification::new_messsage(format!( + simple_message_notification::MessageNotification::new_message(format!( "Error: {:?}", err, )) From de0b136be2eaf3e65ea6a8177a4875346b35f0c2 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 7 Feb 2023 01:00:50 -0800 Subject: [PATCH 088/180] wip yaml highlighting --- Cargo.lock | 10 ++++++ crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 5 +++ crates/zed/src/languages/yaml/brackets.scm | 3 ++ crates/zed/src/languages/yaml/config.toml | 15 ++++++++ crates/zed/src/languages/yaml/highlights.scm | 37 ++++++++++++++++++++ 6 files changed, 71 insertions(+) create mode 100644 crates/zed/src/languages/yaml/brackets.scm create mode 100644 crates/zed/src/languages/yaml/config.toml create mode 100644 crates/zed/src/languages/yaml/highlights.scm diff --git a/Cargo.lock b/Cargo.lock index 203ceff0df..7300c355e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7191,6 +7191,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-yaml" +version = "0.0.1" +source = "git+https://github.com/zed-industries/tree-sitter-yaml?rev=36a64faf81931d3aaa8580a329344ac80ac0fb79#36a64faf81931d3aaa8580a329344ac80ac0fb79" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "try-lock" version = "0.2.3" @@ -8430,6 +8439,7 @@ dependencies = [ "tree-sitter-scheme", "tree-sitter-toml", "tree-sitter-typescript", + "tree-sitter-yaml", "unindent", "url", "urlencoding", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d1ade12f60..248175971c 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -110,6 +110,7 @@ tree-sitter-ruby = "0.20.0" tree-sitter-html = "0.19.0" tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"} tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} +tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "36a64faf81931d3aaa8580a329344ac80ac0fb79"} tree-sitter-lua = "0.0.14" url = "2.2" urlencoding = "2.1.2" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index c0a00d1911..00bc29086c 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -128,6 +128,11 @@ pub fn init(languages: Arc) { tree_sitter_lua::language(), Some(Box::new(lua::LuaLspAdapter)), ), + ( + "yaml", + tree_sitter_yaml::language(), + None, // + ) ] { languages.register(name, load_config(name), grammar, lsp_adapter, load_queries); } diff --git a/crates/zed/src/languages/yaml/brackets.scm b/crates/zed/src/languages/yaml/brackets.scm new file mode 100644 index 0000000000..9e8c9cd93c --- /dev/null +++ b/crates/zed/src/languages/yaml/brackets.scm @@ -0,0 +1,3 @@ +("[" @open "]" @close) +("{" @open "}" @close) +("\"" @open "\"" @close) diff --git a/crates/zed/src/languages/yaml/config.toml b/crates/zed/src/languages/yaml/config.toml new file mode 100644 index 0000000000..ec08826ea3 --- /dev/null +++ b/crates/zed/src/languages/yaml/config.toml @@ -0,0 +1,15 @@ +name = "YAML" +path_suffixes = ["yml", "yaml"] +line_comment = "# " +autoclose_before = ",]}" +brackets = [ +{ start = "{", end = "}", close = true, newline = true }, +{ start = "[", end = "]", close = true, newline = true }, +{ start = "\"", end = "\"", close = true, newline = false }, +] + +[overrides.string] +brackets = [ +{ start = "{", end = "}", close = true, newline = true }, +{ start = "[", end = "]", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/yaml/highlights.scm b/crates/zed/src/languages/yaml/highlights.scm new file mode 100644 index 0000000000..0286a371c9 --- /dev/null +++ b/crates/zed/src/languages/yaml/highlights.scm @@ -0,0 +1,37 @@ +(boolean_scalar) @boolean +(null_scalar) @constant.builtin +(double_quote_scalar) @string +(single_quote_scalar) @string +((block_scalar) @string (#set! "priority" 99)) +(string_scalar) @string +(escape_sequence) @string.escape +(integer_scalar) @number +(float_scalar) @number +(comment) @comment +(anchor_name) @type +(alias_name) @type +(tag) @type +(ERROR) @error + +[ + "," + "-" + ":" + ">" + "?" + "|" +] @punctuation.delimiter + +[ + "[" + "]" + "{" + "}" +] @punctuation.bracket + +[ + "*" + "&" + "---" + "..." +] @punctuation.special \ No newline at end of file From be0241bab13a93afde783687b9e66fd05a7bfc14 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Tue, 7 Feb 2023 19:25:07 +0200 Subject: [PATCH 089/180] Add test for string with unicode characters --- crates/util/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index 8cdbfc6438..0e270db906 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -276,4 +276,15 @@ mod tests { assert_eq!(foo, None); } + + #[test] + fn test_trancate_and_trailoff() { + const MAX_CHARS: usize = 24; + assert_eq!( + truncate_and_trailoff("ajouter un compte d'èèèès", MAX_CHARS), + "ajouter un compte d'è…" + ); + assert_eq!(truncate_and_trailoff("ajouter", MAX_CHARS), "ajouter"); + assert_eq!(truncate_and_trailoff("", MAX_CHARS), ""); + } } From 9bff82f16179b2efc4ada581f30e91374886fb46 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Tue, 7 Feb 2023 19:25:57 +0200 Subject: [PATCH 090/180] Use truncate_and_trailoff function A function that already works with unicode characters. --- crates/search/src/project_search.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index d3d5c437c5..a4a7a520ae 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -259,11 +259,7 @@ impl Item for ProjectSearchView { .boxed(), ) .with_children(self.model.read(cx).active_query.as_ref().map(|query| { - let query_text = if query.as_str().len() > MAX_TAB_TITLE_LEN { - query.as_str()[..MAX_TAB_TITLE_LEN].to_string() + "…" - } else { - query.as_str().to_string() - }; + let query_text = util::truncate_and_trailoff(query.as_str(), MAX_TAB_TITLE_LEN); Label::new(query_text, tab_theme.label.clone()) .aligned() From e15ffc85602ea51a3169cf5861bcf7d52db75079 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Tue, 7 Feb 2023 20:06:20 +0200 Subject: [PATCH 091/180] Make truncate_and_trailoff a bit more clear Co-Authored-By: Max Brunsfeld --- crates/util/src/lib.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index 0e270db906..ea8fdee2a8 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -46,10 +46,10 @@ pub fn truncate(s: &str, max_chars: usize) -> &str { pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); - if s.len() > max_chars { - format!("{}…", truncate(s, max_chars.saturating_sub(3))) - } else { - s.to_string() + let truncation_ix = s.char_indices().map(|(i, _)| i).nth(max_chars); + match truncation_ix { + Some(length) => s[..length].to_string() + "…", + None => s.to_string(), } } @@ -279,12 +279,9 @@ mod tests { #[test] fn test_trancate_and_trailoff() { - const MAX_CHARS: usize = 24; - assert_eq!( - truncate_and_trailoff("ajouter un compte d'èèèès", MAX_CHARS), - "ajouter un compte d'è…" - ); - assert_eq!(truncate_and_trailoff("ajouter", MAX_CHARS), "ajouter"); - assert_eq!(truncate_and_trailoff("", MAX_CHARS), ""); + assert_eq!(truncate_and_trailoff("", 5), ""); + assert_eq!(truncate_and_trailoff("èèèèèè", 7), "èèèèèè"); + assert_eq!(truncate_and_trailoff("èèèèèè", 6), "èèèèèè"); + assert_eq!(truncate_and_trailoff("èèèèèè", 5), "èèèèè…"); } } From 7cef4a5d405034892ce998d5f83169aa80fbeb5a Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 7 Feb 2023 13:39:48 -0500 Subject: [PATCH 092/180] Allocate theme struct directly into the heap Co-Authored-By: Max Brunsfeld Co-Authored-By: Mikayla Maki --- crates/theme/src/theme_registry.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/theme/src/theme_registry.rs b/crates/theme/src/theme_registry.rs index cc5e5490af..d47625289b 100644 --- a/crates/theme/src/theme_registry.rs +++ b/crates/theme/src/theme_registry.rs @@ -55,13 +55,13 @@ impl ThemeRegistry { .load(&asset_path) .with_context(|| format!("failed to load theme file {}", asset_path))?; - let mut theme: Theme = fonts::with_font_cache(self.font_cache.clone(), || { + // Allocate into the heap directly, the Theme struct is too large to fit in the stack. + let mut theme: Arc = fonts::with_font_cache(self.font_cache.clone(), || { serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_slice(&theme_json)) })?; // Reset name to be the file path, so that we can use it to access the stored themes - theme.meta.name = name.into(); - let theme = Arc::new(theme); + Arc::get_mut(&mut theme).unwrap().meta.name = name.into(); self.themes.lock().insert(name.to_string(), theme.clone()); Ok(theme) } From fe25994fb31823b33322de5c9a5147faf65ce93a Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 7 Feb 2023 14:20:23 -0800 Subject: [PATCH 093/180] fix highlights, indents, and tab size for yaml --- assets/settings/default.json | 3 ++ crates/zed/src/languages/python/config.toml | 2 +- crates/zed/src/languages/yaml/config.toml | 12 +++++--- crates/zed/src/languages/yaml/highlights.scm | 32 ++++++++++++++------ crates/zed/src/languages/yaml/outline.scm | 1 + 5 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 crates/zed/src/languages/yaml/outline.scm diff --git a/assets/settings/default.json b/assets/settings/default.json index 6c784a067c..bab998f72f 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -221,6 +221,9 @@ }, "TSX": { "tab_size": 2 + }, + "Yaml": { + "tab_size": 2 } }, // LSP Specific settings. diff --git a/crates/zed/src/languages/python/config.toml b/crates/zed/src/languages/python/config.toml index a817de8e3b..45f20e25a3 100644 --- a/crates/zed/src/languages/python/config.toml +++ b/crates/zed/src/languages/python/config.toml @@ -11,7 +11,7 @@ brackets = [ ] auto_indent_using_last_non_empty_line = false -increase_indent_pattern = ":$" +increase_indent_pattern = ":\\s*$" decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:" [overrides.comment] diff --git a/crates/zed/src/languages/yaml/config.toml b/crates/zed/src/languages/yaml/config.toml index ec08826ea3..08dac475b3 100644 --- a/crates/zed/src/languages/yaml/config.toml +++ b/crates/zed/src/languages/yaml/config.toml @@ -3,13 +3,15 @@ path_suffixes = ["yml", "yaml"] line_comment = "# " autoclose_before = ",]}" brackets = [ -{ start = "{", end = "}", close = true, newline = true }, -{ start = "[", end = "]", close = true, newline = true }, -{ start = "\"", end = "\"", close = true, newline = false }, + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "\"", end = "\"", close = true, newline = false }, ] +increase_indent_pattern = ":\\s*[|>]?\\s*$" + [overrides.string] brackets = [ -{ start = "{", end = "}", close = true, newline = true }, -{ start = "[", end = "]", close = true, newline = true }, + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, ] diff --git a/crates/zed/src/languages/yaml/highlights.scm b/crates/zed/src/languages/yaml/highlights.scm index 0286a371c9..06081f63cb 100644 --- a/crates/zed/src/languages/yaml/highlights.scm +++ b/crates/zed/src/languages/yaml/highlights.scm @@ -1,17 +1,29 @@ (boolean_scalar) @boolean (null_scalar) @constant.builtin -(double_quote_scalar) @string -(single_quote_scalar) @string -((block_scalar) @string (#set! "priority" 99)) -(string_scalar) @string + +[ + (double_quote_scalar) + (single_quote_scalar) + (block_scalar) + (string_scalar) +] @string + (escape_sequence) @string.escape -(integer_scalar) @number -(float_scalar) @number + +[ + (integer_scalar) + (float_scalar) +] @number + (comment) @comment -(anchor_name) @type -(alias_name) @type -(tag) @type -(ERROR) @error + +[ + (anchor_name) + (alias_name) + (tag) +] @type + +key: (flow_node (plain_scalar (string_scalar) @property)) [ "," diff --git a/crates/zed/src/languages/yaml/outline.scm b/crates/zed/src/languages/yaml/outline.scm new file mode 100644 index 0000000000..e85eb1bf8a --- /dev/null +++ b/crates/zed/src/languages/yaml/outline.scm @@ -0,0 +1 @@ +(block_mapping_pair key: (flow_node (plain_scalar (string_scalar) @name))) @item \ No newline at end of file From db2aaa43671768fdeab6f73b00545ac8bf5e665c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 7 Feb 2023 14:35:25 -0800 Subject: [PATCH 094/180] Fixed bug in setting cursor style --- crates/gpui/src/platform/mac/geometry.rs | 2 +- crates/gpui/src/platform/mac/window.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/platform/mac/geometry.rs b/crates/gpui/src/platform/mac/geometry.rs index 0f3b1f6fce..9fce8846b7 100644 --- a/crates/gpui/src/platform/mac/geometry.rs +++ b/crates/gpui/src/platform/mac/geometry.rs @@ -19,7 +19,7 @@ pub trait Vector2FExt { impl Vector2FExt for Vector2F { fn to_screen_ns_point(&self, native_window: id) -> NSPoint { unsafe { - let point = NSPoint::new(self.x() as f64, -self.y() as f64); + let point = NSPoint::new(self.x() as f64, self.y() as f64); msg_send![native_window, convertPointToScreen: point] } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index bc934703be..46e115a436 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -483,6 +483,7 @@ impl Window { let native_view: id = msg_send![VIEW_CLASS, alloc]; let native_view = NSView::init(native_view); + assert!(!native_view.is_null()); let window = Self(Rc::new(RefCell::new(WindowState { @@ -828,12 +829,11 @@ impl platform::Window for Window { let self_id = self_borrow.id; unsafe { - let window_frame = self_borrow.frame(); let app = NSApplication::sharedApplication(nil); // Convert back to screen coordinates - let screen_point = - (position + window_frame.origin()).to_screen_ns_point(self_borrow.native_window); + let screen_point = position.to_screen_ns_point(self_borrow.native_window); + let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0]; let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number]; From 20377ea4e9ed71d5ed065f708b65589326162c85 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 7 Feb 2023 18:19:27 -0500 Subject: [PATCH 095/180] Update links to community repository --- crates/feedback/src/feedback.rs | 4 ++-- crates/gpui/src/app.rs | 2 +- crates/workspace/src/workspace.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 680155b657..85c55fd2d4 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -28,7 +28,7 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); let url = format!( - "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", + "https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", urlencoding::encode(&system_specs_text) ); @@ -48,7 +48,7 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { cx.add_action( |_: &mut Workspace, _: &RequestFeature, cx: &mut ViewContext| { - let url = "https://github.com/zed-industries/feedback/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; + let url = "https://github.com/zed-industries/community/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; cx.dispatch_action(OpenBrowser { url: url.into(), }); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 1cacfa26a1..dee8d98c93 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -359,7 +359,7 @@ impl WindowInputHandler { // paths. In that case, the AppContext will already be borrowed, so any // InputHandler methods need to fail gracefully. // - // See https://github.com/zed-industries/feedback/issues/444 + // See https://github.com/zed-industries/community/issues/444 let app = self.app.try_borrow().ok()?; let view_id = app.focused_view_id(self.window_id)?; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 0abc25724c..9a65f5df8a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2491,7 +2491,7 @@ fn notify_if_database_failed(workspace: &ViewHandle, cx: &mut AsyncAp indoc::indoc! {" Failed to load any database file :( "}, - OsOpen("https://github.com/zed-industries/feedback/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml".to_string()), + OsOpen("https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml".to_string()), "Click to let us know about this error" ) }) From 87cf8ac60efb623924e8cfed5d58c56216a379d3 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 7 Feb 2023 15:26:03 -0800 Subject: [PATCH 096/180] Fixed strange y results from faulty conversion to screen coordinates co-authored-by: Nathan --- crates/gpui/src/platform/mac/geometry.rs | 6 +++--- crates/gpui/src/platform/mac/window.rs | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/platform/mac/geometry.rs b/crates/gpui/src/platform/mac/geometry.rs index 9fce8846b7..6a47968118 100644 --- a/crates/gpui/src/platform/mac/geometry.rs +++ b/crates/gpui/src/platform/mac/geometry.rs @@ -14,12 +14,12 @@ use pathfinder_geometry::{ pub trait Vector2FExt { /// Converts self to an NSPoint with y axis pointing up. - fn to_screen_ns_point(&self, native_window: id) -> NSPoint; + fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint; } impl Vector2FExt for Vector2F { - fn to_screen_ns_point(&self, native_window: id) -> NSPoint { + fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint { unsafe { - let point = NSPoint::new(self.x() as f64, self.y() as f64); + let point = NSPoint::new(self.x() as f64, window_height - self.y() as f64); msg_send![native_window, convertPointToScreen: point] } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 46e115a436..e67aa25e13 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -832,7 +832,10 @@ impl platform::Window for Window { let app = NSApplication::sharedApplication(nil); // Convert back to screen coordinates - let screen_point = position.to_screen_ns_point(self_borrow.native_window); + let screen_point = position.to_screen_ns_point( + self_borrow.native_window, + self_borrow.content_size().y() as f64, + ); let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0]; let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number]; From e8dad56af97f9c252f5aaf90e805b7c12619048a Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 7 Feb 2023 18:26:55 -0500 Subject: [PATCH 097/180] Remove release action for Discourse --- .github/workflows/release_actions.yml | 14 ---------- script/discourse_release | 38 --------------------------- 2 files changed, 52 deletions(-) delete mode 100755 script/discourse_release diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index cc1c66d949..4a9d777769 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -21,20 +21,6 @@ jobs: ${{ github.event.release.body }} ``` - discourse_release: - if: ${{ ! github.event.release.prerelease }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install Node - uses: actions/setup-node@v2 - with: - node-version: "19" - - run: > - node "./script/discourse_release" - ${{ secrets.DISCOURSE_RELEASES_API_KEY }} - ${{ github.event.release.tag_name }} - ${{ github.event.release.body }} mixpanel_release: runs-on: ubuntu-latest steps: diff --git a/script/discourse_release b/script/discourse_release deleted file mode 100755 index c233bf1872..0000000000 --- a/script/discourse_release +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env node --redirect-warnings=/dev/null - -main(); - -async function main() { - const apiKey = process.argv[2] - const zedVersion = process.argv[3] - const releaseNotes = process.argv[4] - const postBody = ` - 📣 Zed ${zedVersion} was just released! - - Restart your Zed or head to the [releases page](https://zed.dev/releases/latest) to grab it. - - --- - - ${releaseNotes} - ` - - const title = `${zedVersion} Release Notes` - - const options = { - method: "POST", - headers: { - "Api-Key": apiKey, - "Api-Username": "system" - }, - body: new URLSearchParams({ - title: title, - raw: postBody, - category: "8" - }) - }; - - fetch("https://forum.zed.dev/posts.json", options) - .then(response => response.json()) - .then(response => console.log(response)) - .catch(err => console.error(err)); -} \ No newline at end of file From 0777f459ba77cedc4a6a4f7d575243fb9dd28372 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 7 Feb 2023 15:34:27 -0800 Subject: [PATCH 098/180] Add yaml language server --- crates/zed/src/languages.rs | 6 +- crates/zed/src/languages/yaml.rs | 94 ++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 crates/zed/src/languages/yaml.rs diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 00bc29086c..a99c80c001 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -14,8 +14,8 @@ mod lua; mod python; mod ruby; mod rust; - mod typescript; +mod yaml; // 1. Add tree-sitter-{language} parser to zed crate // 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below @@ -131,8 +131,8 @@ pub fn init(languages: Arc) { ( "yaml", tree_sitter_yaml::language(), - None, // - ) + Some(Box::new(yaml::YamlLspAdapter)), + ), ] { languages.register(name, load_config(name), grammar, lsp_adapter, load_queries); } diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs new file mode 100644 index 0000000000..6900b6b965 --- /dev/null +++ b/crates/zed/src/languages/yaml.rs @@ -0,0 +1,94 @@ +use std::{any::Any, path::PathBuf, sync::Arc}; + +use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; +use client::http::HttpClient; +use futures::StreamExt; +use serde_json::json; +use smol::fs; + +use language::{LanguageServerName, LspAdapter}; +use util::ResultExt; + +use super::installation::{npm_install_packages, npm_package_latest_version}; + +pub struct YamlLspAdapter; + +impl YamlLspAdapter { + const BIN_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server"; +} + +#[async_trait] +impl LspAdapter for YamlLspAdapter { + async fn name(&self) -> LanguageServerName { + LanguageServerName("yaml-language-server".into()) + } + + async fn server_args(&self) -> Vec { + vec!["--stdio".into()] + } + + async fn fetch_latest_server_version( + &self, + _: Arc, + ) -> Result> { + Ok(Box::new(npm_package_latest_version("yaml-language-server").await?) as Box<_>) + } + + async fn fetch_server_binary( + &self, + version: Box, + _: Arc, + container_dir: PathBuf, + ) -> Result { + let version = version.downcast::().unwrap(); + let version_dir = container_dir.join(version.as_str()); + fs::create_dir_all(&version_dir) + .await + .context("failed to create version directory")?; + let binary_path = version_dir.join(Self::BIN_PATH); + + if fs::metadata(&binary_path).await.is_err() { + npm_install_packages([("yaml-language-server", version.as_str())], &version_dir) + .await?; + + if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { + while let Some(entry) = entries.next().await { + if let Some(entry) = entry.log_err() { + let entry_path = entry.path(); + if entry_path.as_path() != version_dir { + fs::remove_dir_all(&entry_path).await.log_err(); + } + } + } + } + } + + Ok(binary_path) + } + + async fn cached_server_binary(&self, container_dir: PathBuf) -> 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 bin_path = last_version_dir.join(Self::BIN_PATH); + if bin_path.exists() { + Ok(bin_path) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() + } +} From aa0a18968aa331dfd81c6076e532444cbac90377 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 7 Feb 2023 15:40:29 -0800 Subject: [PATCH 099/180] removed unused import --- crates/zed/src/languages/yaml.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index 6900b6b965..46569111f1 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -4,7 +4,6 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use serde_json::json; use smol::fs; use language::{LanguageServerName, LspAdapter}; From 55589533e2e27853522071ab630f265b324f1bb9 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Tue, 7 Feb 2023 15:51:00 -0800 Subject: [PATCH 100/180] Update yaml-tree-sitter git hash Update yaml-tree-sitter git hash --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7300c355e0..6b711bd66a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7194,7 +7194,7 @@ dependencies = [ [[package]] name = "tree-sitter-yaml" version = "0.0.1" -source = "git+https://github.com/zed-industries/tree-sitter-yaml?rev=36a64faf81931d3aaa8580a329344ac80ac0fb79#36a64faf81931d3aaa8580a329344ac80ac0fb79" +source = "git+https://github.com/zed-industries/tree-sitter-yaml?rev=9050a4a4a847ed29e25485b1292a36eab8ae3492#9050a4a4a847ed29e25485b1292a36eab8ae3492" dependencies = [ "cc", "tree-sitter", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 248175971c..368136ee17 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -110,7 +110,7 @@ tree-sitter-ruby = "0.20.0" tree-sitter-html = "0.19.0" tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"} tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} -tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "36a64faf81931d3aaa8580a329344ac80ac0fb79"} +tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "9050a4a4a847ed29e25485b1292a36eab8ae3492"} tree-sitter-lua = "0.0.14" url = "2.2" urlencoding = "2.1.2" From 317eb7535cbaa148b01d9fa28c4392e2c5dea272 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 7 Feb 2023 21:08:07 -0500 Subject: [PATCH 101/180] Fix variable names --- crates/feedback/src/feedback_editor.rs | 26 +++++++++++++------------- crates/zed/src/zed.rs | 6 +++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index bd9f32e73c..8a5e2038de 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -45,8 +45,8 @@ pub fn init(system_specs: SystemSpecs, app_state: Arc, cx: &mut Mutabl }); cx.add_async_action( - |toolbar_button: &mut ToolbarButton, _: &SubmitFeedback, cx| { - if let Some(active_item) = toolbar_button.active_item.as_ref() { + |submit_feedback_button: &mut SubmitFeedbackButton, _: &SubmitFeedback, cx| { + if let Some(active_item) = submit_feedback_button.active_item.as_ref() { Some(active_item.update(cx, |feedback_editor, cx| feedback_editor.handle_save(cx))) } else { None @@ -55,15 +55,15 @@ pub fn init(system_specs: SystemSpecs, app_state: Arc, cx: &mut Mutabl ); } -pub struct StatusBarButton; +pub struct DeployFeedbackButton; -impl Entity for StatusBarButton { +impl Entity for DeployFeedbackButton { type Event = (); } -impl View for StatusBarButton { +impl View for DeployFeedbackButton { fn ui_name() -> &'static str { - "StatusBarButton" + "DeployFeedbackButton" } fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { @@ -87,7 +87,7 @@ impl View for StatusBarButton { } } -impl StatusItemView for StatusBarButton { +impl StatusItemView for DeployFeedbackButton { fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext) {} } @@ -442,11 +442,11 @@ impl SearchableItem for FeedbackEditor { } } -pub struct ToolbarButton { +pub struct SubmitFeedbackButton { active_item: Option>, } -impl ToolbarButton { +impl SubmitFeedbackButton { pub fn new() -> Self { Self { active_item: Default::default(), @@ -454,13 +454,13 @@ impl ToolbarButton { } } -impl Entity for ToolbarButton { +impl Entity for SubmitFeedbackButton { type Event = (); } -impl View for ToolbarButton { +impl View for SubmitFeedbackButton { fn ui_name() -> &'static str { - "ToolbarButton" + "SubmitFeedbackButton" } fn render(&mut self, cx: &mut RenderContext) -> ElementBox { @@ -484,7 +484,7 @@ impl View for ToolbarButton { } } -impl ToolbarItemView for ToolbarButton { +impl ToolbarItemView for SubmitFeedbackButton { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn ItemHandle>, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c118951b34..60ec440cc8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -11,7 +11,7 @@ use collections::VecDeque; pub use editor; use editor::{Editor, MultiBuffer}; -use feedback::feedback_editor::ToolbarButton; +use feedback::feedback_editor::SubmitFeedbackButton; use futures::StreamExt; use gpui::{ actions, @@ -288,7 +288,7 @@ pub fn initialize_workspace( toolbar.add_item(buffer_search_bar, cx); let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); toolbar.add_item(project_search_bar, cx); - let submit_feedback_button = cx.add_view(|_| ToolbarButton::new()); + let submit_feedback_button = cx.add_view(|_| SubmitFeedbackButton::new()); toolbar.add_item(submit_feedback_button, cx); }) }); @@ -347,7 +347,7 @@ pub fn initialize_workspace( let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - let feedback_button = cx.add_view(|_| feedback::feedback_editor::StatusBarButton {}); + let feedback_button = cx.add_view(|_| feedback::feedback_editor::DeployFeedbackButton {}); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); From 37c052f53d467b77df607dba77261b01a72acae7 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 8 Feb 2023 10:14:18 -0500 Subject: [PATCH 102/180] Include `is_staff` boolean in in-app feedback --- crates/client/src/client.rs | 4 ++++ crates/client/src/telemetry.rs | 7 +++++++ crates/feedback/src/feedback_editor.rs | 3 +++ 3 files changed, 14 insertions(+) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 08a3223eb0..eba58304d7 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1324,6 +1324,10 @@ impl Client { pub fn metrics_id(&self) -> Option> { self.telemetry.metrics_id() } + + pub fn is_staff(&self) -> Option { + self.telemetry.is_staff() + } } impl WeakSubscriber { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 2aa33e6435..748eb48f7e 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -40,6 +40,7 @@ struct TelemetryState { next_event_id: usize, flush_task: Option>, log_file: Option, + is_staff: Option, } const MIXPANEL_EVENTS_URL: &'static str = "https://api.mixpanel.com/track"; @@ -125,6 +126,7 @@ impl Telemetry { flush_task: Default::default(), next_event_id: 0, log_file: None, + is_staff: None, }), }); @@ -202,6 +204,7 @@ impl Telemetry { let device_id = state.device_id.clone(); let metrics_id: Option> = metrics_id.map(|id| id.into()); state.metrics_id = metrics_id.clone(); + state.is_staff = Some(is_staff); drop(state); if let Some((token, device_id)) = MIXPANEL_TOKEN.as_ref().zip(device_id) { @@ -282,6 +285,10 @@ impl Telemetry { self.state.lock().metrics_id.clone() } + pub fn is_staff(self: &Arc) -> Option { + self.state.lock().is_staff + } + fn flush(self: &Arc) { let mut state = self.state.lock(); let mut events = mem::take(&mut state.queue); diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 8a5e2038de..9363cb2e3b 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -96,6 +96,7 @@ struct FeedbackRequestBody<'a> { feedback_text: &'a str, metrics_id: Option>, system_specs: SystemSpecs, + is_staff: bool, token: &'a str, } @@ -205,12 +206,14 @@ impl FeedbackEditor { let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL); let metrics_id = zed_client.metrics_id(); + let is_staff = zed_client.is_staff(); let http_client = zed_client.http_client(); let request = FeedbackRequestBody { feedback_text: &feedback_text, metrics_id, system_specs, + is_staff: is_staff.unwrap_or(false), token: ZED_SECRET_CLIENT_TOKEN, }; From ef16963772c48f09eb3222eab24b7e71afd12333 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 8 Feb 2023 10:22:36 -0500 Subject: [PATCH 103/180] Remove placeholder text --- crates/feedback/src/feedback_editor.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 9363cb2e3b..932fc64f31 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -31,7 +31,6 @@ use workspace::{ use crate::system_specs::SystemSpecs; const FEEDBACK_CHAR_LIMIT: RangeInclusive = 10..=5000; -const FEEDBACK_PLACEHOLDER_TEXT: &str = "Save to submit feedback as Markdown."; const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = "Feedback failed to submit, see error log for details."; @@ -117,7 +116,6 @@ impl FeedbackEditor { let editor = cx.add_view(|cx| { let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx); editor.set_vertical_scroll_margin(5, cx); - editor.set_placeholder_text(FEEDBACK_PLACEHOLDER_TEXT, cx); editor }); From 654ee48feb899667cbeb8979ceb363b2fad3e86f Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 8 Feb 2023 10:37:04 -0500 Subject: [PATCH 104/180] Add tooltip to submit feedback button --- crates/feedback/src/feedback_editor.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 932fc64f31..21932107e4 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -481,6 +481,13 @@ impl View for SubmitFeedbackButton { .aligned() .contained() .with_margin_left(theme.feedback.button_margin) + .with_tooltip::( + 0, + "cmd-s".into(), + Some(Box::new(SubmitFeedback)), + theme.tooltip.clone(), + cx, + ) .boxed() } } From 76c066baee95ce1b389af8c4382210f965e9be1f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 8 Feb 2023 17:22:14 +0100 Subject: [PATCH 105/180] Focus results editor only when starting a new project search Co-Authored-By: Mikayla Maki --- crates/search/src/project_search.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a4a7a520ae..71a40685e9 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -571,9 +571,9 @@ impl ProjectSearchView { self.active_match_index = None; } else { let prev_search_id = mem::replace(&mut self.search_id, self.model.read(cx).search_id); - let reset_selections = self.search_id != prev_search_id; + let is_new_search = self.search_id != prev_search_id; self.results_editor.update(cx, |editor, cx| { - if reset_selections { + if is_new_search { editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges(match_ranges.first().cloned()) }); @@ -584,7 +584,7 @@ impl ProjectSearchView { cx, ); }); - if self.query_editor.is_focused(cx) { + if is_new_search && self.query_editor.is_focused(cx) { self.focus_results_editor(cx); } } From bbe82972975c24a90a3f94fc425db3ae543fd935 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 8 Feb 2023 11:23:44 -0500 Subject: [PATCH 106/180] Add feedback information text --- crates/feedback/src/feedback_editor.rs | 52 ++++++++++++++++++++++++++ crates/zed/src/zed.rs | 4 +- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 21932107e4..4380b606d9 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -509,3 +509,55 @@ impl ToolbarItemView for SubmitFeedbackButton { } } } + +pub struct FeedbackInfoText { + active_item: Option>, +} + +impl FeedbackInfoText { + pub fn new() -> Self { + Self { + active_item: Default::default(), + } + } +} + +impl Entity for FeedbackInfoText { + type Event = (); +} + +impl View for FeedbackInfoText { + fn ui_name() -> &'static str { + "FeedbackInfoText" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + let theme = cx.global::().theme.clone(); + Label::new( + "We read whatever you submit here. For issues and discussions, visit the community repo on GitHub.".to_string(), + theme.workspace.titlebar.outdated_warning.text.clone(), + ) + .contained() + .with_style(theme.workspace.titlebar.outdated_warning.container) + .aligned() + .boxed() + } +} + +impl ToolbarItemView for FeedbackInfoText { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut ViewContext, + ) -> workspace::ToolbarItemLocation { + cx.notify(); + if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::()) + { + self.active_item = Some(feedback_editor); + ToolbarItemLocation::PrimaryLeft { flex: None } + } else { + self.active_item = None; + ToolbarItemLocation::Hidden + } + } +} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d1f861cd93..6c92d4c0b8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -11,7 +11,7 @@ use collections::VecDeque; pub use editor; use editor::{Editor, MultiBuffer}; -use feedback::feedback_editor::SubmitFeedbackButton; +use feedback::feedback_editor::{FeedbackInfoText, SubmitFeedbackButton}; use futures::StreamExt; use gpui::{ actions, @@ -290,6 +290,8 @@ pub fn initialize_workspace( toolbar.add_item(project_search_bar, cx); let submit_feedback_button = cx.add_view(|_| SubmitFeedbackButton::new()); toolbar.add_item(submit_feedback_button, cx); + let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new()); + toolbar.add_item(feedback_info_text, cx); }) }); } From 3fb14d7cafed3accecb3c4558b1fa4e99e28e0ca Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 8 Feb 2023 14:55:21 -0500 Subject: [PATCH 107/180] v0.74.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b711bd66a..8eb8d6987a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8345,7 +8345,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.73.0" +version = "0.74.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 368136ee17..8d92880c75 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.73.0" +version = "0.74.0" publish = false [lib] From 83f9d51deea68e1605e125d1b1c44528475b86da Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 8 Feb 2023 18:20:17 -0500 Subject: [PATCH 108/180] Fix layout of elements in the feedback editor's toolbar Co-Authored-By: Kay Simmons <3323631+Kethku@users.noreply.github.com> Co-Authored-By: Julia <30666851+ForLoveOfCats@users.noreply.github.com> --- crates/feedback/src/feedback_editor.rs | 6 ++- crates/gpui/src/elements.rs | 10 +++- crates/gpui/src/elements/clipped.rs | 69 ++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 crates/gpui/src/elements/clipped.rs diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 4380b606d9..f950e6edd4 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -540,6 +540,8 @@ impl View for FeedbackInfoText { .contained() .with_style(theme.workspace.titlebar.outdated_warning.container) .aligned() + .left() + .clipped() .boxed() } } @@ -554,7 +556,9 @@ impl ToolbarItemView for FeedbackInfoText { if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::()) { self.active_item = Some(feedback_editor); - ToolbarItemLocation::PrimaryLeft { flex: None } + ToolbarItemLocation::PrimaryLeft { + flex: Some((1., false)), + } } else { self.active_item = None; ToolbarItemLocation::Hidden diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 9f11f09f8e..41a802feb3 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -1,5 +1,6 @@ mod align; mod canvas; +mod clipped; mod constrained_box; mod container; mod empty; @@ -19,12 +20,12 @@ mod text; mod tooltip; mod uniform_list; -use self::expanded::Expanded; pub use self::{ align::*, canvas::*, constrained_box::*, container::*, empty::*, flex::*, hook::*, image::*, keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*, resizable::*, stack::*, svg::*, text::*, tooltip::*, uniform_list::*, }; +use self::{clipped::Clipped, expanded::Expanded}; pub use crate::presenter::ChildView; use crate::{ geometry::{ @@ -135,6 +136,13 @@ pub trait Element { Align::new(self.boxed()) } + fn clipped(self) -> Clipped + where + Self: 'static + Sized, + { + Clipped::new(self.boxed()) + } + fn contained(self) -> Container where Self: 'static + Sized, diff --git a/crates/gpui/src/elements/clipped.rs b/crates/gpui/src/elements/clipped.rs new file mode 100644 index 0000000000..2ee7b542a8 --- /dev/null +++ b/crates/gpui/src/elements/clipped.rs @@ -0,0 +1,69 @@ +use std::ops::Range; + +use pathfinder_geometry::{rect::RectF, vector::Vector2F}; +use serde_json::json; + +use crate::{ + json, DebugContext, Element, ElementBox, LayoutContext, MeasurementContext, PaintContext, + SizeConstraint, +}; + +pub struct Clipped { + child: ElementBox, +} + +impl Clipped { + pub fn new(child: ElementBox) -> Self { + Self { child } + } +} + +impl Element for Clipped { + type LayoutState = (); + type PaintState = (); + + fn layout( + &mut self, + constraint: SizeConstraint, + cx: &mut LayoutContext, + ) -> (Vector2F, Self::LayoutState) { + (self.child.layout(constraint, cx), ()) + } + + fn paint( + &mut self, + bounds: RectF, + visible_bounds: RectF, + _: &mut Self::LayoutState, + cx: &mut PaintContext, + ) -> Self::PaintState { + cx.scene.push_layer(Some(bounds)); + self.child.paint(bounds.origin(), visible_bounds, cx); + cx.scene.pop_layer(); + } + + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, cx) + } + + fn debug( + &self, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &DebugContext, + ) -> json::Value { + json!({ + "type": "Clipped", + "child": self.child.debug(cx) + }) + } +} From a9c2f42f70c3cb7ebf0d24933685cf71e1607d30 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 8 Feb 2023 18:20:43 -0500 Subject: [PATCH 109/180] Move string to variable --- crates/feedback/src/feedback_editor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index f950e6edd4..ae8ccc9151 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -533,9 +533,10 @@ impl View for FeedbackInfoText { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let theme = cx.global::().theme.clone(); + let text = "We read whatever you submit here. For issues and discussions, visit the community repo on GitHub."; Label::new( - "We read whatever you submit here. For issues and discussions, visit the community repo on GitHub.".to_string(), theme.workspace.titlebar.outdated_warning.text.clone(), + text.to_string(), ) .contained() .with_style(theme.workspace.titlebar.outdated_warning.container) From 57e10ce7c6ec85446a0a2512a2388e0f211bb4f3 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 8 Feb 2023 21:02:28 -0500 Subject: [PATCH 110/180] Style info text --- crates/feedback/src/feedback_editor.rs | 16 ++++++---------- crates/theme/src/theme.rs | 1 + styles/src/styleTree/feedback.ts | 4 ++-- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index ae8ccc9151..7681b160e9 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -534,16 +534,12 @@ impl View for FeedbackInfoText { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let theme = cx.global::().theme.clone(); let text = "We read whatever you submit here. For issues and discussions, visit the community repo on GitHub."; - Label::new( - theme.workspace.titlebar.outdated_warning.text.clone(), - text.to_string(), - ) - .contained() - .with_style(theme.workspace.titlebar.outdated_warning.container) - .aligned() - .left() - .clipped() - .boxed() + Label::new(text.to_string(), theme.feedback.info_text.text.clone()) + .contained() + .aligned() + .left() + .clipped() + .boxed() } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index b8ec3e0329..bc338bbe26 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -811,6 +811,7 @@ pub struct TerminalStyle { pub struct FeedbackStyle { pub submit_button: Interactive, pub button_margin: f32, + pub info_text: ContainedText, } #[derive(Clone, Deserialize, Default)] diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts index eb0f61ecfd..46cb867ad9 100644 --- a/styles/src/styleTree/feedback.ts +++ b/styles/src/styleTree/feedback.ts @@ -5,7 +5,6 @@ import { background, border, text } from "./components"; export default function feedback(colorScheme: ColorScheme) { let layer = colorScheme.highest; - // Currently feedback only needs style for the submit feedback button return { submit_button: { ...text(layer, "mono", "on"), @@ -32,6 +31,7 @@ export default function feedback(colorScheme: ColorScheme) { border: border(layer, "on", "hovered"), }, }, - button_margin: 8 + button_margin: 8, + info_text: text(layer, "sans", "default", { size: "xs" }), }; } From c3a88857f9a560ea0d9d0b6a3dd3d4f83b3b3317 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 9 Feb 2023 15:30:10 +0100 Subject: [PATCH 111/180] Always respond to language server, even when its requests are malformed This was causing Pyright to get stuck waiting for a response when sending us the `workspace/configuration` request. For some reason, after upgrading to Pyright 1.1.293, `scopeUri` is being sent as an empty string sometimes, which causes serde to error when trying to deserialize that request. Co-Authored-By: Petros Amoiridis --- crates/lsp/src/lsp.rs | 84 +++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 30 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index a535cfd252..660528daf1 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -65,6 +65,7 @@ struct Request<'a, T> { #[derive(Serialize, Deserialize)] struct AnyResponse<'a> { + jsonrpc: &'a str, id: usize, #[serde(default)] error: Option, @@ -204,8 +205,9 @@ impl LanguageServer { } else { on_unhandled_notification(msg); } - } else if let Ok(AnyResponse { id, error, result }) = - serde_json::from_slice(&buffer) + } else if let Ok(AnyResponse { + id, error, result, .. + }) = serde_json::from_slice(&buffer) { if let Some(handler) = response_handlers .lock() @@ -461,35 +463,57 @@ impl LanguageServer { method, Box::new(move |id, params, cx| { if let Some(id) = id { - if let Some(params) = serde_json::from_str(params).log_err() { - let response = f(params, cx.clone()); - cx.foreground() - .spawn({ - let outbound_tx = outbound_tx.clone(); - async move { - let response = match response.await { - Ok(result) => Response { - jsonrpc: JSON_RPC_VERSION, - id, - result: Some(result), - error: None, - }, - Err(error) => Response { - jsonrpc: JSON_RPC_VERSION, - id, - result: None, - error: Some(Error { - message: error.to_string(), - }), - }, - }; - if let Some(response) = serde_json::to_vec(&response).log_err() - { - outbound_tx.try_send(response).ok(); + match serde_json::from_str(params) { + Ok(params) => { + let response = f(params, cx.clone()); + cx.foreground() + .spawn({ + let outbound_tx = outbound_tx.clone(); + async move { + let response = match response.await { + Ok(result) => Response { + jsonrpc: JSON_RPC_VERSION, + id, + result: Some(result), + error: None, + }, + Err(error) => Response { + jsonrpc: JSON_RPC_VERSION, + id, + result: None, + error: Some(Error { + message: error.to_string(), + }), + }, + }; + if let Some(response) = + serde_json::to_vec(&response).log_err() + { + outbound_tx.try_send(response).ok(); + } } - } - }) - .detach(); + }) + .detach(); + } + Err(error) => { + log::error!( + "error deserializing {} request: {:?}, message: {:?}", + method, + error, + params + ); + let response = AnyResponse { + jsonrpc: JSON_RPC_VERSION, + id, + result: None, + error: Some(Error { + message: error.to_string(), + }), + }; + if let Some(response) = serde_json::to_vec(&response).log_err() { + outbound_tx.try_send(response).ok(); + } + } } } }), From da5a6a8b4fcefbffa8b5fc612bc06cc1101dc11c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Feb 2023 07:57:01 -0700 Subject: [PATCH 112/180] Don't render tooltip keystroke label if there's no focused view Co-authored-by: Antonio Scandurra --- crates/gpui/src/elements/tooltip.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs index e79eefc5f4..562f12295c 100644 --- a/crates/gpui/src/elements/tooltip.rs +++ b/crates/gpui/src/elements/tooltip.rs @@ -61,7 +61,7 @@ impl Tooltip { ) -> Self { struct ElementState(Tag); struct MouseEventHandlerState(Tag); - let focused_view_id = cx.focused_view_id(cx.window_id).unwrap(); + let focused_view_id = cx.focused_view_id(cx.window_id); let state_handle = cx.default_element_state::, Rc>(id); let state = state_handle.read(cx).clone(); @@ -132,7 +132,7 @@ impl Tooltip { pub fn render_tooltip( window_id: usize, - focused_view_id: usize, + focused_view_id: Option, text: String, style: TooltipStyle, action: Option>, @@ -149,18 +149,18 @@ impl Tooltip { text.flex(1., false).aligned().boxed() } }) - .with_children(action.map(|action| { + .with_children(action.and_then(|action| { let keystroke_label = KeystrokeLabel::new( window_id, - focused_view_id, + focused_view_id?, action, style.keystroke.container, style.keystroke.text, ); if measure { - keystroke_label.boxed() + Some(keystroke_label.boxed()) } else { - keystroke_label.aligned().boxed() + Some(keystroke_label.aligned().boxed()) } })) .contained() From a789476c95173dfc6a2d4f741fe338ec70b59015 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Fri, 10 Feb 2023 21:11:05 +0200 Subject: [PATCH 113/180] Introduce reveal_in_finder function And use this function in a new Reveal in Finder option of the project panel context menu. Co-Authored-By: Mikayla Maki --- crates/project_panel/src/project_panel.rs | 9 +++++++++ crates/util/src/lib.rs | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index e59353aae4..a5ca4077f7 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -119,6 +119,7 @@ actions!( AddFile, Copy, CopyPath, + RevealInFinder, Cut, Paste, Delete, @@ -147,6 +148,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(ProjectPanel::cancel); cx.add_action(ProjectPanel::copy); cx.add_action(ProjectPanel::copy_path); + cx.add_action(ProjectPanel::reveal_in_finder); cx.add_action(ProjectPanel::cut); cx.add_action( |this: &mut ProjectPanel, action: &Paste, cx: &mut ViewContext| { @@ -305,6 +307,7 @@ impl ProjectPanel { } menu_entries.push(ContextMenuItem::item("New File", AddFile)); menu_entries.push(ContextMenuItem::item("New Folder", AddDirectory)); + menu_entries.push(ContextMenuItem::item("Reveal in Finder", RevealInFinder)); menu_entries.push(ContextMenuItem::Separator); menu_entries.push(ContextMenuItem::item("Copy", Copy)); menu_entries.push(ContextMenuItem::item("Copy Path", CopyPath)); @@ -787,6 +790,12 @@ impl ProjectPanel { } } + fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext) { + if let Some((_worktree, entry)) = self.selected_entry(cx) { + util::reveal_in_finder(&entry.path); + } + } + fn move_entry( &mut self, &MoveProjectEntry { diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index ea8fdee2a8..698d888e15 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -9,6 +9,7 @@ use rand::{seq::SliceRandom, Rng}; use std::{ cmp::Ordering, ops::AddAssign, + path::Path, pin::Pin, task::{Context, Poll}, }; @@ -53,6 +54,15 @@ pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { } } +pub fn reveal_in_finder>(path: P) { + let path_to_reveal = path.as_ref().to_string_lossy(); + std::process::Command::new("open") + .arg("-R") // To reveal in Finder instead of opening the file + .arg(path_to_reveal.as_ref()) + .spawn() + .log_err(); +} + pub fn post_inc + AddAssign + Copy>(value: &mut T) -> T { let prev = *value; *value += T::from(1); From 5d23aaacc89fec002ac8b1a0be109bbc2c47385f Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Fri, 10 Feb 2023 21:11:54 +0200 Subject: [PATCH 114/180] Introduce an open function And refactor some of the older code to simplify it Co-Authored-By: Mikayla Maki --- crates/terminal/src/terminal.rs | 34 +++------------------------ crates/util/src/lib.rs | 8 +++++++ crates/workspace/src/notifications.rs | 6 +---- 3 files changed, 12 insertions(+), 36 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index dd5c5fb3b0..a4f913ec77 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -32,17 +32,14 @@ use mappings::mouse::{ use procinfo::LocalProcessInfo; use settings::{AlternateScroll, Settings, Shell, TerminalBlink}; -use util::ResultExt; use std::{ cmp::min, collections::{HashMap, VecDeque}, fmt::Display, - io, ops::{Deref, Index, RangeInclusive, Sub}, - os::unix::{prelude::AsRawFd, process::CommandExt}, + os::unix::prelude::AsRawFd, path::PathBuf, - process::Command, sync::Arc, time::{Duration, Instant}, }; @@ -734,7 +731,7 @@ impl Terminal { if let Some((url, url_match)) = found_url { if *open { - open_uri(&url).log_err(); + util::open(&url); } else { self.update_hyperlink(prev_hyperlink, url, url_match); } @@ -1075,7 +1072,7 @@ impl Terminal { if self.selection_phase == SelectionPhase::Ended { let mouse_cell_index = content_index_for_mouse(position, &self.last_content); if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() { - open_uri(link.uri()).log_err(); + util::open(link.uri()); } else { self.events .push_back(InternalEvent::FindHyperlink(position, true)); @@ -1234,31 +1231,6 @@ fn content_index_for_mouse<'a>(pos: Vector2F, content: &'a TerminalContent) -> u line * content.size.columns() + col } -fn open_uri(uri: &str) -> Result<(), std::io::Error> { - let mut command = Command::new("open"); - command.arg(uri); - - unsafe { - command - .pre_exec(|| { - match libc::fork() { - -1 => return Err(io::Error::last_os_error()), - 0 => (), - _ => libc::_exit(0), - } - - if libc::setsid() == -1 { - return Err(io::Error::last_os_error()); - } - - Ok(()) - }) - .spawn()? - .wait() - .map(|_| ()) - } -} - #[cfg(test)] mod tests { use alacritty_terminal::{ diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index 698d888e15..5b12502389 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -54,6 +54,14 @@ pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { } } +pub fn open>(path: P) { + let path_to_open = path.as_ref().to_string_lossy(); + std::process::Command::new("open") + .arg(path_to_open.as_ref()) + .spawn() + .log_err(); +} + pub fn reveal_in_finder>(path: P) { let path_to_reveal = path.as_ref().to_string_lossy(); std::process::Command::new("open") diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 72dec114d9..76ee5b1c40 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -121,7 +121,6 @@ impl Workspace { } pub mod simple_message_notification { - use std::process::Command; use gpui::{ actions, @@ -150,10 +149,7 @@ pub mod simple_message_notification { |_workspace: &mut Workspace, open_action: &OsOpen, _cx: &mut ViewContext| { #[cfg(target_os = "macos")] { - let mut command = Command::new("open"); - command.arg(open_action.0.clone()); - - command.spawn().ok(); + util::open(&open_action.0); } }, ) From 889b15683dfdd8b5591e794f89fcfcb77dcea103 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 10 Feb 2023 11:52:31 -0800 Subject: [PATCH 115/180] Update the atelier cave license file --- styles/src/buildLicenses.ts | 3 +-- styles/src/themes/atelier-cave.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/styles/src/buildLicenses.ts b/styles/src/buildLicenses.ts index e83496d91d..5026faef4e 100644 --- a/styles/src/buildLicenses.ts +++ b/styles/src/buildLicenses.ts @@ -41,8 +41,7 @@ function getLicenseText(schemeMeta: Meta[], callback: (meta: Meta, license_text: const { statusCode } = res; if (statusCode < 200 || statusCode >= 300) { - throw new Error('Failed to fetch license file.\n' + - `Status Code: ${statusCode}`); + throw new Error(`Failed to fetch license for: ${meta.name}, Status Code: ${statusCode}`); } res.setEncoding('utf8'); diff --git a/styles/src/themes/atelier-cave.ts b/styles/src/themes/atelier-cave.ts index b7b06381d8..fbd7b87f01 100644 --- a/styles/src/themes/atelier-cave.ts +++ b/styles/src/themes/atelier-cave.ts @@ -57,7 +57,7 @@ export const meta: Meta = { license: { SPDX: "MIT", https_url: "https://raw.githubusercontent.com/atelierbram/syntax-highlighting/master/LICENSE", - license_checksum: "6c2353bb9dd0b7b211364d98184ab482e54f40f611eda0c02974c3a1f9e6193c" + license_checksum: "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5" }, - url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/" + url: "https://atelierbram.mit-license.org/license.txt" } \ No newline at end of file From 436ab6e454355e93a7dfcee767f91264e02a2eca Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 10 Feb 2023 11:58:25 -0800 Subject: [PATCH 116/180] Fix other atelier license --- styles/src/themes/atelier-cave.ts | 4 ++-- styles/src/themes/atelier-sulphurpool.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/styles/src/themes/atelier-cave.ts b/styles/src/themes/atelier-cave.ts index fbd7b87f01..0959cabace 100644 --- a/styles/src/themes/atelier-cave.ts +++ b/styles/src/themes/atelier-cave.ts @@ -56,8 +56,8 @@ export const meta: Meta = { author: "atelierbram", license: { SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/atelierbram/syntax-highlighting/master/LICENSE", + https_url: "https://atelierbram.mit-license.org/license.txt", license_checksum: "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5" }, - url: "https://atelierbram.mit-license.org/license.txt" + url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/" } \ No newline at end of file diff --git a/styles/src/themes/atelier-sulphurpool.ts b/styles/src/themes/atelier-sulphurpool.ts index 2e2f708442..fa51b1ec80 100644 --- a/styles/src/themes/atelier-sulphurpool.ts +++ b/styles/src/themes/atelier-sulphurpool.ts @@ -35,8 +35,8 @@ export const meta: Meta = { author: "atelierbram", license: { SPDX: "MIT", - https_url: "https://raw.githubusercontent.com/atelierbram/syntax-highlighting/master/LICENSE", - license_checksum: "6c2353bb9dd0b7b211364d98184ab482e54f40f611eda0c02974c3a1f9e6193c" + https_url: "https://atelierbram.mit-license.org/license.txt", + license_checksum: "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5" }, - url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune/" + url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool/" } \ No newline at end of file From b31813fad3ab3101c25392d2189784feb339bd7a Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 10 Feb 2023 22:50:05 -0800 Subject: [PATCH 117/180] Split concepts out into self contained files in feedback editor --- crates/feedback/src/deploy_feedback_button.rs | 44 +++++ crates/feedback/src/feedback.rs | 6 +- crates/feedback/src/feedback_editor.rs | 171 +----------------- crates/feedback/src/feedback_info_text.rs | 60 ++++++ crates/feedback/src/submit_feedback_button.rs | 76 ++++++++ crates/zed/src/zed.rs | 7 +- 6 files changed, 197 insertions(+), 167 deletions(-) create mode 100644 crates/feedback/src/deploy_feedback_button.rs create mode 100644 crates/feedback/src/feedback_info_text.rs create mode 100644 crates/feedback/src/submit_feedback_button.rs diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs new file mode 100644 index 0000000000..e2d663f075 --- /dev/null +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -0,0 +1,44 @@ +use gpui::{ + elements::{MouseEventHandler, ParentElement, Stack, Text}, + CursorStyle, Element, ElementBox, Entity, MouseButton, RenderContext, View, ViewContext, +}; +use settings::Settings; +use workspace::{item::ItemHandle, StatusItemView}; + +use crate::feedback_editor::GiveFeedback; + +pub struct DeployFeedbackButton; + +impl Entity for DeployFeedbackButton { + type Event = (); +} + +impl View for DeployFeedbackButton { + fn ui_name() -> &'static str { + "DeployFeedbackButton" + } + + fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { + Stack::new() + .with_child( + MouseEventHandler::::new(0, cx, |state, cx| { + let theme = &cx.global::().theme; + let theme = &theme.workspace.status_bar.feedback; + + Text::new( + "Give Feedback".to_string(), + theme.style_for(state, true).clone(), + ) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(GiveFeedback)) + .boxed(), + ) + .boxed() + } +} + +impl StatusItemView for DeployFeedbackButton { + fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext) {} +} diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 85c55fd2d4..f95f24f557 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -1,6 +1,10 @@ +pub mod deploy_feedback_button; +pub mod feedback_editor; +pub mod feedback_info_text; +pub mod submit_feedback_button; + use std::sync::Arc; -pub mod feedback_editor; mod system_specs; use gpui::{actions, impl_actions, ClipboardItem, MutableAppContext, PromptLevel, ViewContext}; use serde::Deserialize; diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 7681b160e9..fd61827317 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -10,10 +10,10 @@ use editor::{Anchor, Editor}; use futures::AsyncReadExt; use gpui::{ actions, - elements::{ChildView, Flex, Label, MouseEventHandler, ParentElement, Stack, Text}, - serde_json, AnyViewHandle, AppContext, CursorStyle, Element, ElementBox, Entity, ModelHandle, - MouseButton, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + elements::{ChildView, Flex, Label, ParentElement}, + serde_json, AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, + MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, + WeakViewHandle, }; use isahc::Request; use language::Buffer; @@ -21,14 +21,13 @@ use postage::prelude::Stream; use project::Project; use serde::Serialize; -use settings::Settings; use workspace::{ item::{Item, ItemHandle}, searchable::{SearchableItem, SearchableItemHandle}, - AppState, StatusItemView, ToolbarItemLocation, ToolbarItemView, Workspace, + AppState, Workspace, }; -use crate::system_specs::SystemSpecs; +use crate::{submit_feedback_button::SubmitFeedbackButton, system_specs::SystemSpecs}; const FEEDBACK_CHAR_LIMIT: RangeInclusive = 10..=5000; const FEEDBACK_SUBMISSION_ERROR_TEXT: &str = @@ -54,42 +53,6 @@ pub fn init(system_specs: SystemSpecs, app_state: Arc, cx: &mut Mutabl ); } -pub struct DeployFeedbackButton; - -impl Entity for DeployFeedbackButton { - type Event = (); -} - -impl View for DeployFeedbackButton { - fn ui_name() -> &'static str { - "DeployFeedbackButton" - } - - fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { - Stack::new() - .with_child( - MouseEventHandler::::new(0, cx, |state, cx| { - let theme = &cx.global::().theme; - let theme = &theme.workspace.status_bar.feedback; - - Text::new( - "Give Feedback".to_string(), - theme.style_for(state, true).clone(), - ) - .boxed() - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(GiveFeedback)) - .boxed(), - ) - .boxed() - } -} - -impl StatusItemView for DeployFeedbackButton { - fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext) {} -} - #[derive(Serialize)] struct FeedbackRequestBody<'a> { feedback_text: &'a str, @@ -100,7 +63,7 @@ struct FeedbackRequestBody<'a> { } #[derive(Clone)] -struct FeedbackEditor { +pub(crate) struct FeedbackEditor { system_specs: SystemSpecs, editor: ViewHandle, project: ModelHandle, @@ -442,123 +405,3 @@ impl SearchableItem for FeedbackEditor { .update(cx, |editor, cx| editor.active_match_index(matches, cx)) } } - -pub struct SubmitFeedbackButton { - active_item: Option>, -} - -impl SubmitFeedbackButton { - pub fn new() -> Self { - Self { - active_item: Default::default(), - } - } -} - -impl Entity for SubmitFeedbackButton { - type Event = (); -} - -impl View for SubmitFeedbackButton { - fn ui_name() -> &'static str { - "SubmitFeedbackButton" - } - - fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - let theme = cx.global::().theme.clone(); - enum SubmitFeedbackButton {} - MouseEventHandler::::new(0, cx, |state, _| { - let style = theme.feedback.submit_button.style_for(state, false); - Label::new("Submit as Markdown".into(), style.text.clone()) - .contained() - .with_style(style.container) - .boxed() - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, cx| { - cx.dispatch_action(SubmitFeedback) - }) - .aligned() - .contained() - .with_margin_left(theme.feedback.button_margin) - .with_tooltip::( - 0, - "cmd-s".into(), - Some(Box::new(SubmitFeedback)), - theme.tooltip.clone(), - cx, - ) - .boxed() - } -} - -impl ToolbarItemView for SubmitFeedbackButton { - fn set_active_pane_item( - &mut self, - active_pane_item: Option<&dyn ItemHandle>, - cx: &mut ViewContext, - ) -> workspace::ToolbarItemLocation { - cx.notify(); - if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::()) - { - self.active_item = Some(feedback_editor); - ToolbarItemLocation::PrimaryRight { flex: None } - } else { - self.active_item = None; - ToolbarItemLocation::Hidden - } - } -} - -pub struct FeedbackInfoText { - active_item: Option>, -} - -impl FeedbackInfoText { - pub fn new() -> Self { - Self { - active_item: Default::default(), - } - } -} - -impl Entity for FeedbackInfoText { - type Event = (); -} - -impl View for FeedbackInfoText { - fn ui_name() -> &'static str { - "FeedbackInfoText" - } - - fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - let theme = cx.global::().theme.clone(); - let text = "We read whatever you submit here. For issues and discussions, visit the community repo on GitHub."; - Label::new(text.to_string(), theme.feedback.info_text.text.clone()) - .contained() - .aligned() - .left() - .clipped() - .boxed() - } -} - -impl ToolbarItemView for FeedbackInfoText { - fn set_active_pane_item( - &mut self, - active_pane_item: Option<&dyn ItemHandle>, - cx: &mut ViewContext, - ) -> workspace::ToolbarItemLocation { - cx.notify(); - if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::()) - { - self.active_item = Some(feedback_editor); - ToolbarItemLocation::PrimaryLeft { - flex: Some((1., false)), - } - } else { - self.active_item = None; - ToolbarItemLocation::Hidden - } - } -} diff --git a/crates/feedback/src/feedback_info_text.rs b/crates/feedback/src/feedback_info_text.rs new file mode 100644 index 0000000000..bfe67ec4ae --- /dev/null +++ b/crates/feedback/src/feedback_info_text.rs @@ -0,0 +1,60 @@ +use gpui::{ + elements::Label, Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle, +}; +use settings::Settings; +use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; + +use crate::feedback_editor::FeedbackEditor; + +pub struct FeedbackInfoText { + active_item: Option>, +} + +impl FeedbackInfoText { + pub fn new() -> Self { + Self { + active_item: Default::default(), + } + } +} + +impl Entity for FeedbackInfoText { + type Event = (); +} + +impl View for FeedbackInfoText { + fn ui_name() -> &'static str { + "FeedbackInfoText" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + let theme = cx.global::().theme.clone(); + let text = "We read whatever you submit here. For issues and discussions, visit the community repo on GitHub."; + Label::new(text.to_string(), theme.feedback.info_text.text.clone()) + .contained() + .aligned() + .left() + .clipped() + .boxed() + } +} + +impl ToolbarItemView for FeedbackInfoText { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut ViewContext, + ) -> workspace::ToolbarItemLocation { + cx.notify(); + if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::()) + { + self.active_item = Some(feedback_editor); + ToolbarItemLocation::PrimaryLeft { + flex: Some((1., false)), + } + } else { + self.active_item = None; + ToolbarItemLocation::Hidden + } + } +} diff --git a/crates/feedback/src/submit_feedback_button.rs b/crates/feedback/src/submit_feedback_button.rs new file mode 100644 index 0000000000..470a53905e --- /dev/null +++ b/crates/feedback/src/submit_feedback_button.rs @@ -0,0 +1,76 @@ +use gpui::{ + elements::{Label, MouseEventHandler}, + CursorStyle, Element, ElementBox, Entity, MouseButton, RenderContext, View, ViewContext, + ViewHandle, +}; +use settings::Settings; +use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; + +use crate::feedback_editor::{FeedbackEditor, SubmitFeedback}; + +pub struct SubmitFeedbackButton { + pub(crate) active_item: Option>, +} + +impl SubmitFeedbackButton { + pub fn new() -> Self { + Self { + active_item: Default::default(), + } + } +} + +impl Entity for SubmitFeedbackButton { + type Event = (); +} + +impl View for SubmitFeedbackButton { + fn ui_name() -> &'static str { + "SubmitFeedbackButton" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + let theme = cx.global::().theme.clone(); + enum SubmitFeedbackButton {} + MouseEventHandler::::new(0, cx, |state, _| { + let style = theme.feedback.submit_button.style_for(state, false); + Label::new("Submit as Markdown".into(), style.text.clone()) + .contained() + .with_style(style.container) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(SubmitFeedback) + }) + .aligned() + .contained() + .with_margin_left(theme.feedback.button_margin) + .with_tooltip::( + 0, + "cmd-s".into(), + Some(Box::new(SubmitFeedback)), + theme.tooltip.clone(), + cx, + ) + .boxed() + } +} + +impl ToolbarItemView for SubmitFeedbackButton { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut ViewContext, + ) -> workspace::ToolbarItemLocation { + cx.notify(); + if let Some(feedback_editor) = active_pane_item.and_then(|i| i.downcast::()) + { + self.active_item = Some(feedback_editor); + ToolbarItemLocation::PrimaryRight { flex: None } + } else { + self.active_item = None; + ToolbarItemLocation::Hidden + } + } +} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 6c92d4c0b8..9195c36940 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -11,7 +11,9 @@ use collections::VecDeque; pub use editor; use editor::{Editor, MultiBuffer}; -use feedback::feedback_editor::{FeedbackInfoText, SubmitFeedbackButton}; +use feedback::{ + feedback_info_text::FeedbackInfoText, submit_feedback_button::SubmitFeedbackButton, +}; use futures::StreamExt; use gpui::{ actions, @@ -349,7 +351,8 @@ pub fn initialize_workspace( let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); - let feedback_button = cx.add_view(|_| feedback::feedback_editor::DeployFeedbackButton {}); + let feedback_button = + cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton {}); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); From 91437906022975fd6bfd072ee9811168d2e7cacf Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Sat, 11 Feb 2023 11:12:46 +0200 Subject: [PATCH 118/180] Include code only on macOS Co-Authored-By: Mikayla Maki --- crates/util/src/lib.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index 5b12502389..c3376f2e78 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -56,19 +56,25 @@ pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { pub fn open>(path: P) { let path_to_open = path.as_ref().to_string_lossy(); - std::process::Command::new("open") - .arg(path_to_open.as_ref()) - .spawn() - .log_err(); + #[cfg(target_os = "macos")] + { + std::process::Command::new("open") + .arg(path_to_open.as_ref()) + .spawn() + .log_err(); + } } pub fn reveal_in_finder>(path: P) { let path_to_reveal = path.as_ref().to_string_lossy(); - std::process::Command::new("open") - .arg("-R") // To reveal in Finder instead of opening the file - .arg(path_to_reveal.as_ref()) - .spawn() - .log_err(); + #[cfg(target_os = "macos")] + { + std::process::Command::new("open") + .arg("-R") // To reveal in Finder instead of opening the file + .arg(path_to_reveal.as_ref()) + .spawn() + .log_err(); + } } pub fn post_inc + AddAssign + Copy>(value: &mut T) -> T { From d42d495cb0bb6c5f31df8ca0d2f9195d61d64f41 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Sat, 11 Feb 2023 21:53:10 -0500 Subject: [PATCH 119/180] Remove toggle right sidebar command --- assets/keymaps/default.json | 7 ------- crates/workspace/src/workspace.rs | 4 ---- crates/zed/src/menus.rs | 4 ---- 3 files changed, 15 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index ea8c341461..17168f679f 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -430,13 +430,6 @@ "cmd-enter": "project_search::SearchInNew" } }, - { - "context": "Workspace", - "bindings": { - "shift-escape": "dock::FocusDock", - "cmd-shift-b": "workspace::ToggleRightSidebar" - } - }, { "bindings": { "cmd-shift-k cmd-shift-right": "dock::AnchorDockRight", diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9a65f5df8a..9d209c6f1a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -96,7 +96,6 @@ actions!( ActivateNextPane, FollowNextCollaborator, ToggleLeftSidebar, - ToggleRightSidebar, NewTerminal, NewSearch, Feedback, @@ -230,9 +229,6 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftSidebar, cx| { workspace.toggle_sidebar(SidebarSide::Left, cx); }); - cx.add_action(|workspace: &mut Workspace, _: &ToggleRightSidebar, cx| { - workspace.toggle_sidebar(SidebarSide::Right, cx); - }); cx.add_action(Workspace::activate_pane_at_index); cx.add_action(Workspace::split_pane_with_item); diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 82b72611c2..09525f14e5 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -219,10 +219,6 @@ pub fn menus() -> Vec> { name: "Toggle Left Sidebar", action: Box::new(workspace::ToggleLeftSidebar), }, - MenuItem::Action { - name: "Toggle Right Sidebar", - action: Box::new(workspace::ToggleRightSidebar), - }, MenuItem::Submenu(Menu { name: "Editor Layout", items: vec![ From 67032646006a9b3e76e91f5b2fe9fcdde74094e3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Feb 2023 19:57:15 +0100 Subject: [PATCH 120/180] Limit `BufferSnapshot::chunks` to the outline item range Co-Authored-By: Max Brunsfeld --- crates/language/src/buffer.rs | 34 +++++++++++++++++++++---------- crates/language/src/syntax_map.rs | 27 +++++++++++++++++++++++- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 13b3a86822..15aa12c782 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2247,7 +2247,6 @@ impl BufferSnapshot { .map(|g| g.outline_config.as_ref().unwrap()) .collect::>(); - let mut chunks = self.chunks(0..self.len(), true); let mut stack = Vec::>::new(); let mut items = Vec::new(); while let Some(mat) = matches.peek() { @@ -2266,9 +2265,7 @@ impl BufferSnapshot { continue; } - let mut text = String::new(); - let mut name_ranges = Vec::new(); - let mut highlight_ranges = Vec::new(); + let mut buffer_ranges = Vec::new(); for capture in mat.captures { let node_is_name; if capture.index == config.name_capture_ix { @@ -2286,12 +2283,27 @@ impl BufferSnapshot { range.start + self.line_len(start.row as u32) as usize - start.column; } + buffer_ranges.push((range, node_is_name)); + } + + if buffer_ranges.is_empty() { + continue; + } + + let mut text = String::new(); + let mut highlight_ranges = Vec::new(); + let mut name_ranges = Vec::new(); + let mut chunks = self.chunks( + buffer_ranges.first().unwrap().0.start..buffer_ranges.last().unwrap().0.end, + true, + ); + for (buffer_range, is_name) in buffer_ranges { if !text.is_empty() { text.push(' '); } - if node_is_name { + if is_name { let mut start = text.len(); - let end = start + range.len(); + let end = start + buffer_range.len(); // When multiple names are captured, then the matcheable text // includes the whitespace in between the names. @@ -2302,12 +2314,12 @@ impl BufferSnapshot { name_ranges.push(start..end); } - let mut offset = range.start; + let mut offset = buffer_range.start; chunks.seek(offset); for mut chunk in chunks.by_ref() { - if chunk.text.len() > range.end - offset { - chunk.text = &chunk.text[0..(range.end - offset)]; - offset = range.end; + if chunk.text.len() > buffer_range.end - offset { + chunk.text = &chunk.text[0..(buffer_range.end - offset)]; + offset = buffer_range.end; } else { offset += chunk.text.len(); } @@ -2321,7 +2333,7 @@ impl BufferSnapshot { highlight_ranges.push((start..end, style)); } text.push_str(chunk.text); - if offset >= range.end { + if offset >= buffer_range.end { break; } } diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 41966c7596..670f479f10 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -608,6 +608,31 @@ impl SyntaxSnapshot { self.layers = layers; self.interpolated_version = text.version.clone(); self.parsed_version = text.version.clone(); + #[cfg(debug_assertions)] + self.check_invariants(text); + } + + #[cfg(debug_assertions)] + fn check_invariants(&self, text: &BufferSnapshot) { + let mut max_depth = 0; + let mut prev_range: Option> = None; + for layer in self.layers.iter() { + if layer.depth == max_depth { + if let Some(prev_range) = prev_range { + match layer.range.start.cmp(&prev_range.start, text) { + Ordering::Less => panic!("layers out of order"), + Ordering::Equal => { + assert!(layer.range.end.cmp(&prev_range.end, text).is_ge()) + } + Ordering::Greater => {} + } + } + } else if layer.depth < max_depth { + panic!("layers out of order") + } + max_depth = layer.depth; + prev_range = Some(layer.range.clone()); + } } pub fn single_tree_captures<'a>( @@ -1419,7 +1444,7 @@ impl sum_tree::Summary for SyntaxLayerSummary { self.max_depth = other.max_depth; self.range = other.range.clone(); } else { - if other.range.start.cmp(&self.range.start, buffer).is_lt() { + if self.range == (Anchor::MAX..Anchor::MAX) { self.range.start = other.range.start; } if other.range.end.cmp(&self.range.end, buffer).is_gt() { From d80dba1fe3e0e3e2de538fe864ce85f703ad2641 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 13 Feb 2023 12:49:57 -0800 Subject: [PATCH 121/180] Switch from vec to smallvec --- crates/diagnostics/src/diagnostics.rs | 3 ++- crates/editor/src/items.rs | 5 +++-- crates/feedback/src/feedback_editor.rs | 5 +++-- crates/search/src/project_search.rs | 9 ++++++--- crates/terminal_view/src/terminal_view.rs | 11 ++++++----- crates/theme_testbench/src/theme_testbench.rs | 5 +++-- crates/workspace/src/item.rs | 7 ++++--- crates/workspace/src/shared_screen.rs | 5 +++-- crates/workspace/src/workspace.rs | 2 ++ 9 files changed, 32 insertions(+), 20 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index d3078bce81..596abe9bb6 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -21,6 +21,7 @@ use language::{ use project::{DiagnosticSummary, Project, ProjectPath}; use serde_json::json; use settings::Settings; +use smallvec::SmallVec; use std::{ any::{Any, TypeId}, cmp::Ordering, @@ -579,7 +580,7 @@ impl Item for ProjectDiagnosticsEditor { .update(cx, |editor, cx| editor.git_diff_recalc(project, cx)) } - fn to_item_events(event: &Self::Event) -> Vec { + fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { Editor::to_item_events(event) } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 501306aa19..c52d00b7e3 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -19,6 +19,7 @@ use language::{ use project::{FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; +use smallvec::SmallVec; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -693,8 +694,8 @@ impl Item for Editor { Task::ready(Ok(())) } - fn to_item_events(event: &Self::Event) -> Vec { - let mut result = Vec::new(); + fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { + let mut result = SmallVec::new(); match event { Event::Closed => result.push(ItemEvent::CloseItem), Event::Saved | Event::TitleChanged => { diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index fd61827317..bcef9d0af5 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -24,6 +24,7 @@ use serde::Serialize; use workspace::{ item::{Item, ItemHandle}, searchable::{SearchableItem, SearchableItemHandle}, + smallvec::SmallVec, AppState, Workspace, }; @@ -258,8 +259,8 @@ impl Item for FeedbackEditor { self.editor.for_each_project_item(cx, f) } - fn to_item_events(_: &Self::Event) -> Vec { - Vec::new() + fn to_item_events(_: &Self::Event) -> SmallVec<[workspace::item::ItemEvent; 2]> { + SmallVec::new() } fn is_singleton(&self, _: &AppContext) -> bool { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 71a40685e9..99c73815c9 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -16,6 +16,7 @@ use gpui::{ use menu::Confirm; use project::{search::SearchQuery, Project}; use settings::Settings; +use smallvec::SmallVec; use std::{ any::{Any, TypeId}, mem, @@ -345,11 +346,13 @@ impl Item for ProjectSearchView { .update(cx, |editor, cx| editor.git_diff_recalc(project, cx)) } - fn to_item_events(event: &Self::Event) -> Vec { + fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { match event { - ViewEvent::UpdateTab => vec![ItemEvent::UpdateBreadcrumbs, ItemEvent::UpdateTab], + ViewEvent::UpdateTab => { + smallvec::smallvec![ItemEvent::UpdateBreadcrumbs, ItemEvent::UpdateTab] + } ViewEvent::EditorEvent(editor_event) => Editor::to_item_events(editor_event), - _ => Vec::new(), + _ => SmallVec::new(), } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 847dfc5ee5..cc3025d96e 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -21,6 +21,7 @@ use gpui::{ use project::{LocalWorktree, Project}; use serde::Deserialize; use settings::{Settings, TerminalBlink, WorkingDirectory}; +use smallvec::SmallVec; use smol::Timer; use terminal::{ alacritty_terminal::{ @@ -664,12 +665,12 @@ impl Item for TerminalView { Some(Box::new(handle.clone())) } - fn to_item_events(event: &Self::Event) -> Vec { + fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { match event { - Event::BreadcrumbsChanged => vec![ItemEvent::UpdateBreadcrumbs], - Event::TitleChanged | Event::Wakeup => vec![ItemEvent::UpdateTab], - Event::CloseTerminal => vec![ItemEvent::CloseItem], - _ => vec![], + Event::BreadcrumbsChanged => smallvec::smallvec![ItemEvent::UpdateBreadcrumbs], + Event::TitleChanged | Event::Wakeup => smallvec::smallvec![ItemEvent::UpdateTab], + Event::CloseTerminal => smallvec::smallvec![ItemEvent::CloseItem], + _ => smallvec::smallvec![], } } diff --git a/crates/theme_testbench/src/theme_testbench.rs b/crates/theme_testbench/src/theme_testbench.rs index 3cda5d3e51..84ec68e636 100644 --- a/crates/theme_testbench/src/theme_testbench.rs +++ b/crates/theme_testbench/src/theme_testbench.rs @@ -11,6 +11,7 @@ use gpui::{ }; use project::Project; use settings::Settings; +use smallvec::SmallVec; use theme::{ColorScheme, Layer, Style, StyleSet}; use workspace::{ item::{Item, ItemEvent}, @@ -350,8 +351,8 @@ impl Item for ThemeTestbench { gpui::Task::ready(Ok(())) } - fn to_item_events(_: &Self::Event) -> Vec { - Vec::new() + fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> { + SmallVec::new() } fn serialized_item_kind() -> Option<&'static str> { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index b1888bb243..0e28976151 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -88,7 +88,7 @@ pub trait Item: View { ) -> Task> { Task::ready(Ok(())) } - fn to_item_events(event: &Self::Event) -> Vec; + fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]>; fn should_close_item_on_event(_: &Self::Event) -> bool { false } @@ -723,6 +723,7 @@ pub(crate) mod test { RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use project::{Project, ProjectEntryId, ProjectPath, WorktreeId}; + use smallvec::SmallVec; use std::{any::Any, borrow::Cow, cell::Cell, path::Path}; pub struct TestProjectItem { @@ -985,8 +986,8 @@ pub(crate) mod test { Task::ready(Ok(())) } - fn to_item_events(_: &Self::Event) -> Vec { - vec![ItemEvent::UpdateTab, ItemEvent::Edit] + fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> { + [ItemEvent::UpdateTab, ItemEvent::Edit].into() } fn serialized_item_kind() -> Option<&'static str> { diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index b76535f6ed..b3e107c81b 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -13,6 +13,7 @@ use gpui::{ }; use project::Project; use settings::Settings; +use smallvec::SmallVec; use std::{ path::PathBuf, sync::{Arc, Weak}, @@ -177,9 +178,9 @@ impl Item for SharedScreen { Task::ready(Err(anyhow!("Item::reload called on SharedScreen"))) } - fn to_item_events(event: &Self::Event) -> Vec { + fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { match event { - Event::Close => vec![ItemEvent::CloseItem], + Event::Close => smallvec::smallvec!(ItemEvent::CloseItem), } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9d209c6f1a..7ca3cc7d22 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -14,6 +14,8 @@ pub mod sidebar; mod status_bar; mod toolbar; +pub use smallvec; + use anyhow::{anyhow, Result}; use call::ActiveCall; use client::{ From c1812ddc277cafe2403d3f84b5f4e5c0c2fc9d2a Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 8 Feb 2023 15:01:42 -0800 Subject: [PATCH 122/180] fix issue with single line editors in vim not properly unhooking vim mode bindings --- assets/keymaps/vim.json | 3 +- crates/editor/src/editor.rs | 15 ++++-- crates/vim/src/editor_events.rs | 58 +++++++++++------------ crates/vim/src/vim.rs | 84 ++++++++++++++++++--------------- 4 files changed, 88 insertions(+), 72 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 12873a3e4e..f52a1941a3 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -315,7 +315,8 @@ { "context": "Editor && VimWaiting", "bindings": { - "*": "gpui::KeyPressed" + "*": "gpui::KeyPressed", + "escape": "editor::Cancel" } } ] \ No newline at end of file diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f25eefcb7d..fbce19d607 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -400,7 +400,7 @@ pub enum SelectMode { All, } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum EditorMode { SingleLine, AutoHeight { max_lines: usize }, @@ -1732,11 +1732,13 @@ impl Editor { } pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext) { + let text: Arc = text.into(); + if !self.input_enabled { + cx.emit(Event::InputIgnored { text }); return; } - let text: Arc = text.into(); let selections = self.selections.all_adjusted(cx); let mut edits = Vec::new(); let mut new_selections = Vec::with_capacity(selections.len()); @@ -6187,6 +6189,9 @@ impl Deref for EditorSnapshot { #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { + InputIgnored { + text: Arc, + }, ExcerptsAdded { buffer: ModelHandle, predecessor: ExcerptId, @@ -6253,8 +6258,10 @@ impl View for Editor { } fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - let focused_event = EditorFocused(cx.handle()); - cx.emit_global(focused_event); + if cx.is_self_focused() { + let focused_event = EditorFocused(cx.handle()); + cx.emit_global(focused_event); + } if let Some(rename) = self.pending_rename.as_ref() { cx.focus(&rename.editor); } else { diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index c526e3b1dc..d452d4b790 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -1,62 +1,62 @@ -use editor::{EditorBlurred, EditorCreated, EditorFocused, EditorMode, EditorReleased}; +use editor::{EditorBlurred, EditorFocused, EditorMode, EditorReleased}; use gpui::MutableAppContext; use crate::{state::Mode, Vim}; pub fn init(cx: &mut MutableAppContext) { - cx.subscribe_global(editor_created).detach(); - cx.subscribe_global(editor_focused).detach(); - cx.subscribe_global(editor_blurred).detach(); - cx.subscribe_global(editor_released).detach(); + cx.subscribe_global(focused).detach(); + cx.subscribe_global(blurred).detach(); + cx.subscribe_global(released).detach(); } -fn editor_created(EditorCreated(editor): &EditorCreated, cx: &mut MutableAppContext) { - cx.update_default_global(|vim: &mut Vim, cx| { - vim.editors.insert(editor.id(), editor.downgrade()); - vim.sync_vim_settings(cx); - }) -} - -fn editor_focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppContext) { +fn focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppContext) { Vim::update(cx, |vim, cx| { + if let Some(previously_active_editor) = vim + .active_editor + .as_ref() + .and_then(|editor| editor.upgrade(cx)) + { + vim.unhook_vim_settings(previously_active_editor, cx); + } + vim.active_editor = Some(editor.downgrade()); - vim.selection_subscription = Some(cx.subscribe(editor, |editor, event, cx| { + dbg!("Active editor changed", editor.read(cx).mode()); + vim.editor_subscription = Some(cx.subscribe(editor, |editor, event, cx| { if editor.read(cx).leader_replica_id().is_none() { if let editor::Event::SelectionsChanged { local: true } = event { let newest_empty = editor.read(cx).selections.newest::(cx).is_empty(); - editor_local_selections_changed(newest_empty, cx); + local_selections_changed(newest_empty, cx); } } })); - if !vim.enabled { - return; + if vim.enabled { + let editor = editor.read(cx); + let editor_mode = editor.mode(); + let newest_selection_empty = editor.selections.newest::(cx).is_empty(); + + if editor_mode == EditorMode::Full && !newest_selection_empty { + vim.switch_mode(Mode::Visual { line: false }, true, cx); + } } - let editor = editor.read(cx); - let editor_mode = editor.mode(); - let newest_selection_empty = editor.selections.newest::(cx).is_empty(); - - if editor_mode == EditorMode::Full && !newest_selection_empty { - vim.switch_mode(Mode::Visual { line: false }, true, cx); - } + vim.sync_vim_settings(cx); }); } -fn editor_blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut MutableAppContext) { +fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut MutableAppContext) { Vim::update(cx, |vim, cx| { if let Some(previous_editor) = vim.active_editor.clone() { if previous_editor == editor.clone() { vim.active_editor = None; } } - vim.sync_vim_settings(cx); + vim.unhook_vim_settings(editor.clone(), cx); }) } -fn editor_released(EditorReleased(editor): &EditorReleased, cx: &mut MutableAppContext) { +fn released(EditorReleased(editor): &EditorReleased, cx: &mut MutableAppContext) { cx.update_default_global(|vim: &mut Vim, _| { - vim.editors.remove(&editor.id()); if let Some(previous_editor) = vim.active_editor.clone() { if previous_editor == editor.clone() { vim.active_editor = None; @@ -65,7 +65,7 @@ fn editor_released(EditorReleased(editor): &EditorReleased, cx: &mut MutableAppC }); } -fn editor_local_selections_changed(newest_empty: bool, cx: &mut MutableAppContext) { +fn local_selections_changed(newest_empty: bool, cx: &mut MutableAppContext) { Vim::update(cx, |vim, cx| { if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty { vim.switch_mode(Mode::Visual { line: false }, false, cx) diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 9f799ef37f..699ff01dcc 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -10,13 +10,12 @@ mod state; mod utils; mod visual; -use collections::HashMap; use command_palette::CommandPaletteFilter; use editor::{Bias, Cancel, Editor, EditorMode}; use gpui::{ impl_actions, keymap_matcher::{KeyPressed, Keystroke}, - MutableAppContext, Subscription, ViewContext, WeakViewHandle, + MutableAppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, }; use language::CursorShape; use motion::Motion; @@ -117,9 +116,8 @@ pub fn observe_keypresses(window_id: usize, cx: &mut MutableAppContext) { #[derive(Default)] pub struct Vim { - editors: HashMap>, active_editor: Option>, - selection_subscription: Option, + editor_subscription: Option, enabled: bool, state: VimState, @@ -160,24 +158,27 @@ impl Vim { } // Adjust selections - for editor in self.editors.values() { - if let Some(editor) = editor.upgrade(cx) { - editor.update(cx, |editor, cx| { - editor.change_selections(None, cx, |s| { - s.move_with(|map, selection| { - if self.state.empty_selections_only() { - let new_head = map.clip_point(selection.head(), Bias::Left); - selection.collapse_to(new_head, selection.goal) - } else { - selection.set_head( - map.clip_point(selection.head(), Bias::Left), - selection.goal, - ); - } - }); - }) + if let Some(editor) = self + .active_editor + .as_ref() + .and_then(|editor| editor.upgrade(cx)) + { + editor.update(cx, |editor, cx| { + dbg!(&mode, editor.mode()); + editor.change_selections(None, cx, |s| { + s.move_with(|map, selection| { + if self.state.empty_selections_only() { + let new_head = map.clip_point(selection.head(), Bias::Left); + selection.collapse_to(new_head, selection.goal) + } else { + selection.set_head( + map.clip_point(selection.head(), Bias::Left), + selection.goal, + ); + } + }); }) - } + }) } } @@ -264,26 +265,33 @@ impl Vim { } }); - for editor in self.editors.values() { - if let Some(editor) = editor.upgrade(cx) { + if let Some(editor) = self + .active_editor + .as_ref() + .and_then(|editor| editor.upgrade(cx)) + { + if self.enabled && editor.read(cx).mode() == EditorMode::Full { editor.update(cx, |editor, cx| { - if self.enabled && editor.mode() == EditorMode::Full { - editor.set_cursor_shape(cursor_shape, cx); - editor.set_clip_at_line_ends(state.clip_at_line_end(), cx); - editor.set_input_enabled(!state.vim_controlled()); - editor.selections.line_mode = - matches!(state.mode, Mode::Visual { line: true }); - let context_layer = state.keymap_context_layer(); - editor.set_keymap_context_layer::(context_layer); - } else { - editor.set_cursor_shape(CursorShape::Bar, cx); - editor.set_clip_at_line_ends(false, cx); - editor.set_input_enabled(true); - editor.selections.line_mode = false; - editor.remove_keymap_context_layer::(); - } + editor.set_cursor_shape(cursor_shape, cx); + editor.set_clip_at_line_ends(state.clip_at_line_end(), cx); + editor.set_input_enabled(!state.vim_controlled()); + editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true }); + let context_layer = state.keymap_context_layer(); + editor.set_keymap_context_layer::(context_layer); }); + } else { + self.unhook_vim_settings(editor, cx); } } } + + fn unhook_vim_settings(&self, editor: ViewHandle, cx: &mut MutableAppContext) { + editor.update(cx, |editor, cx| { + editor.set_cursor_shape(CursorShape::Bar, cx); + editor.set_clip_at_line_ends(false, cx); + editor.set_input_enabled(true); + editor.selections.line_mode = false; + editor.remove_keymap_context_layer::(); + }); + } } From 3d5333691666bbb7c8ff89a8dc7bb44106183f52 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 10 Feb 2023 14:41:22 -0800 Subject: [PATCH 123/180] More vim fixes and move some more things out of app.rs --- Cargo.lock | 1 - assets/keymaps/vim.json | 2 +- crates/editor/src/display_map.rs | 91 ++++- crates/editor/src/editor.rs | 35 +- crates/gpui/src/app.rs | 356 +------------------- crates/gpui/src/app/menu.rs | 52 +++ crates/gpui/src/app/ref_counts.rs | 217 ++++++++++++ crates/gpui/src/app/test_app_context.rs | 15 +- crates/gpui/src/app/window_input_handler.rs | 98 ++++++ crates/gpui/src/platform/test.rs | 4 +- crates/gpui/src/test.rs | 25 +- crates/sqlez/Cargo.toml | 5 +- crates/vim/src/editor_events.rs | 16 +- crates/vim/src/motion.rs | 96 +++--- crates/vim/src/normal.rs | 6 +- crates/vim/src/test/vim_test_context.rs | 2 +- crates/vim/src/vim.rs | 41 +-- crates/vim/src/visual.rs | 4 +- crates/zed/src/zed.rs | 2 +- 19 files changed, 595 insertions(+), 473 deletions(-) create mode 100644 crates/gpui/src/app/menu.rs create mode 100644 crates/gpui/src/app/ref_counts.rs create mode 100644 crates/gpui/src/app/window_input_handler.rs diff --git a/Cargo.lock b/Cargo.lock index 8eb8d6987a..82e9197631 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6104,7 +6104,6 @@ dependencies = [ "libsqlite3-sys", "parking_lot 0.11.2", "smol", - "sqlez_macros", "thread_local", "uuid 1.2.2", ] diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index f52a1941a3..e8a7d767f0 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -315,7 +315,7 @@ { "context": "Editor && VimWaiting", "bindings": { - "*": "gpui::KeyPressed", + // "*": "gpui::KeyPressed", "escape": "editor::Cancel" } } diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e32276df41..99a74fe7f2 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -337,7 +337,7 @@ impl DisplaySnapshot { .map(|h| h.text) } - // Returns text chunks starting at the end of the given display row in reverse until the start of the file + /// Returns text chunks starting at the end of the given display row in reverse until the start of the file pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator { (0..=display_row).into_iter().rev().flat_map(|row| { self.blocks_snapshot @@ -411,6 +411,67 @@ impl DisplaySnapshot { }) } + /// Returns an iterator of the start positions of the occurances of `target` in the `self` after `from` + /// Stops if `condition` returns false for any of the character position pairs observed. + pub fn find_while<'a>( + &'a self, + from: DisplayPoint, + target: &str, + condition: impl FnMut(char, DisplayPoint) -> bool + 'a, + ) -> impl Iterator + 'a { + Self::find_internal(self.chars_at(from), target.chars().collect(), condition) + } + + /// Returns an iterator of the end positions of the occurances of `target` in the `self` before `from` + /// Stops if `condition` returns false for any of the character position pairs observed. + pub fn reverse_find_while<'a>( + &'a self, + from: DisplayPoint, + target: &str, + condition: impl FnMut(char, DisplayPoint) -> bool + 'a, + ) -> impl Iterator + 'a { + Self::find_internal( + self.reverse_chars_at(from), + target.chars().rev().collect(), + condition, + ) + } + + fn find_internal<'a>( + iterator: impl Iterator + 'a, + target: Vec, + mut condition: impl FnMut(char, DisplayPoint) -> bool + 'a, + ) -> impl Iterator + 'a { + // List of partial matches with the index of the last seen character in target and the starting point of the match + let mut partial_matches: Vec<(usize, DisplayPoint)> = Vec::new(); + iterator + .take_while(move |(ch, point)| condition(*ch, *point)) + .filter_map(move |(ch, point)| { + if Some(&ch) == target.get(0) { + partial_matches.push((0, point)); + } + + let mut found = None; + // Keep partial matches that have the correct next character + partial_matches.retain_mut(|(match_position, match_start)| { + if target.get(*match_position) == Some(&ch) { + *match_position += 1; + if *match_position == target.len() { + found = Some(match_start.clone()); + // This match is completed. No need to keep tracking it + false + } else { + true + } + } else { + false + } + }); + + found + }) + } + pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 { let mut count = 0; let mut column = 0; @@ -627,7 +688,7 @@ pub mod tests { use smol::stream::StreamExt; use std::{env, sync::Arc}; use theme::SyntaxTheme; - use util::test::{marked_text_ranges, sample_text}; + use util::test::{marked_text_offsets, marked_text_ranges, sample_text}; use Bias::*; #[gpui::test(iterations = 100)] @@ -1418,6 +1479,32 @@ pub mod tests { ) } + #[test] + fn test_find_internal() { + assert("This is a ˇtest of find internal", "test"); + assert("Some text ˇaˇaˇaa with repeated characters", "aa"); + + fn assert(marked_text: &str, target: &str) { + let (text, expected_offsets) = marked_text_offsets(marked_text); + + let chars = text + .chars() + .enumerate() + .map(|(index, ch)| (ch, DisplayPoint::new(0, index as u32))); + let target = target.chars(); + + assert_eq!( + expected_offsets + .into_iter() + .map(|offset| offset as u32) + .collect::>(), + DisplaySnapshot::find_internal(chars, target.collect(), |_, _| true) + .map(|point| point.column()) + .collect::>() + ) + } + } + fn syntax_chunks<'a>( rows: Range, map: &ModelHandle, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fbce19d607..c649fed7ce 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6400,26 +6400,29 @@ impl View for Editor { text: &str, cx: &mut ViewContext, ) { + self.transact(cx, |this, cx| { + if this.input_enabled { + let new_selected_ranges = if let Some(range_utf16) = range_utf16 { + let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); + Some(this.selection_replacement_ranges(range_utf16, cx)) + } else { + this.marked_text_ranges(cx) + }; + + if let Some(new_selected_ranges) = new_selected_ranges { + this.change_selections(None, cx, |selections| { + selections.select_ranges(new_selected_ranges) + }); + } + } + + this.handle_input(text, cx); + }); + if !self.input_enabled { return; } - self.transact(cx, |this, cx| { - let new_selected_ranges = if let Some(range_utf16) = range_utf16 { - let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); - Some(this.selection_replacement_ranges(range_utf16, cx)) - } else { - this.marked_text_ranges(cx) - }; - - if let Some(new_selected_ranges) = new_selected_ranges { - this.change_selections(None, cx, |selections| { - selections.select_ranges(new_selected_ranges) - }); - } - this.handle_input(text, cx); - }); - if let Some(transaction) = self.ime_transaction { self.buffer.update(cx, |buffer, cx| { buffer.group_until_transaction(transaction, cx); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 6f7199aa33..c0e0b067c4 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1,7 +1,10 @@ pub mod action; mod callback_collection; +mod menu; +pub(crate) mod ref_counts; #[cfg(any(test, feature = "test-support"))] pub mod test_app_context; +mod window_input_handler; use std::{ any::{type_name, Any, TypeId}, @@ -19,34 +22,38 @@ use std::{ }; use anyhow::{anyhow, Context, Result}; -use lazy_static::lazy_static; use parking_lot::Mutex; use pathfinder_geometry::vector::Vector2F; use postage::oneshot; use smallvec::SmallVec; use smol::prelude::*; +use uuid::Uuid; pub use action::*; use callback_collection::CallbackCollection; use collections::{hash_map::Entry, HashMap, HashSet, VecDeque}; +pub use menu::*; use platform::Event; #[cfg(any(test, feature = "test-support"))] +use ref_counts::LeakDetector; +#[cfg(any(test, feature = "test-support"))] pub use test_app_context::{ContextHandle, TestAppContext}; -use uuid::Uuid; +use window_input_handler::WindowInputHandler; use crate::{ elements::ElementBox, executor::{self, Task}, - geometry::rect::RectF, keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult}, platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions}, presenter::Presenter, util::post_inc, - Appearance, AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, KeyUpEvent, + Appearance, AssetCache, AssetSource, ClipboardItem, FontCache, KeyUpEvent, ModifiersChangedEvent, MouseButton, MouseRegionId, PathPromptOptions, TextLayoutCache, WindowBounds, }; +use self::ref_counts::RefCounts; + pub trait Entity: 'static { type Event; @@ -174,31 +181,12 @@ pub trait UpdateView { T: View; } -pub struct Menu<'a> { - pub name: &'a str, - pub items: Vec>, -} - -pub enum MenuItem<'a> { - Separator, - Submenu(Menu<'a>), - Action { - name: &'a str, - action: Box, - }, -} - #[derive(Clone)] pub struct App(Rc>); #[derive(Clone)] pub struct AsyncAppContext(Rc>); -pub struct WindowInputHandler { - app: Rc>, - window_id: usize, -} - impl App { pub fn new(asset_source: impl AssetSource) -> Result { let platform = platform::current::platform(); @@ -220,33 +208,7 @@ impl App { cx.borrow_mut().quit(); } })); - foreground_platform.on_will_open_menu(Box::new({ - let cx = app.0.clone(); - move || { - let mut cx = cx.borrow_mut(); - cx.keystroke_matcher.clear_pending(); - } - })); - foreground_platform.on_validate_menu_command(Box::new({ - let cx = app.0.clone(); - move |action| { - let cx = cx.borrow_mut(); - !cx.keystroke_matcher.has_pending_keystrokes() && cx.is_action_available(action) - } - })); - foreground_platform.on_menu_command(Box::new({ - let cx = app.0.clone(); - move |action| { - let mut cx = cx.borrow_mut(); - if let Some(key_window_id) = cx.cx.platform.key_window_id() { - if let Some(view_id) = cx.focused_view_id(key_window_id) { - cx.handle_dispatch_action_from_effect(key_window_id, Some(view_id), action); - return; - } - } - cx.dispatch_global_action_any(action); - } - })); + setup_menu_handlers(foreground_platform.as_ref(), &app); app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0)); Ok(app) @@ -349,94 +311,6 @@ impl App { } } -impl WindowInputHandler { - fn read_focused_view(&self, f: F) -> Option - where - F: FnOnce(&dyn AnyView, &AppContext) -> T, - { - // Input-related application hooks are sometimes called by the OS during - // a call to a window-manipulation API, like prompting the user for file - // paths. In that case, the AppContext will already be borrowed, so any - // InputHandler methods need to fail gracefully. - // - // See https://github.com/zed-industries/community/issues/444 - let app = self.app.try_borrow().ok()?; - - let view_id = app.focused_view_id(self.window_id)?; - let view = app.cx.views.get(&(self.window_id, view_id))?; - let result = f(view.as_ref(), &app); - Some(result) - } - - fn update_focused_view(&mut self, f: F) -> Option - where - F: FnOnce(usize, usize, &mut dyn AnyView, &mut MutableAppContext) -> T, - { - let mut app = self.app.try_borrow_mut().ok()?; - app.update(|app| { - let view_id = app.focused_view_id(self.window_id)?; - let mut view = app.cx.views.remove(&(self.window_id, view_id))?; - let result = f(self.window_id, view_id, view.as_mut(), &mut *app); - app.cx.views.insert((self.window_id, view_id), view); - Some(result) - }) - } -} - -impl InputHandler for WindowInputHandler { - fn text_for_range(&self, range: Range) -> Option { - self.read_focused_view(|view, cx| view.text_for_range(range.clone(), cx)) - .flatten() - } - - fn selected_text_range(&self) -> Option> { - self.read_focused_view(|view, cx| view.selected_text_range(cx)) - .flatten() - } - - fn replace_text_in_range(&mut self, range: Option>, text: &str) { - self.update_focused_view(|window_id, view_id, view, cx| { - view.replace_text_in_range(range, text, cx, window_id, view_id); - }); - } - - fn marked_text_range(&self) -> Option> { - self.read_focused_view(|view, cx| view.marked_text_range(cx)) - .flatten() - } - - fn unmark_text(&mut self) { - self.update_focused_view(|window_id, view_id, view, cx| { - view.unmark_text(cx, window_id, view_id); - }); - } - - fn replace_and_mark_text_in_range( - &mut self, - range: Option>, - new_text: &str, - new_selected_range: Option>, - ) { - self.update_focused_view(|window_id, view_id, view, cx| { - view.replace_and_mark_text_in_range( - range, - new_text, - new_selected_range, - cx, - window_id, - view_id, - ); - }); - } - - fn rect_for_range(&self, range_utf16: Range) -> Option { - let app = self.app.borrow(); - let (presenter, _) = app.presenters_and_platform_windows.get(&self.window_id)?; - let presenter = presenter.borrow(); - presenter.rect_for_text_range(range_utf16, &app) - } -} - impl AsyncAppContext { pub fn spawn(&self, f: F) -> Task where @@ -984,11 +858,6 @@ impl MutableAppContext { result } - pub fn set_menus(&mut self, menus: Vec) { - self.foreground_platform - .set_menus(menus, &self.keystroke_matcher); - } - fn show_character_palette(&self, window_id: usize) { let (_, window) = &self.presenters_and_platform_windows[&window_id]; window.show_character_palette(); @@ -4025,7 +3894,7 @@ impl<'a, T: View> ViewContext<'a, T> { }) } - pub fn observe_keystroke(&mut self, mut callback: F) -> Subscription + pub fn observe_keystrokes(&mut self, mut callback: F) -> Subscription where F: 'static + FnMut( @@ -5280,205 +5149,6 @@ impl Subscription { } } -lazy_static! { - static ref LEAK_BACKTRACE: bool = - std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty()); -} - -#[cfg(any(test, feature = "test-support"))] -#[derive(Default)] -pub struct LeakDetector { - next_handle_id: usize, - #[allow(clippy::type_complexity)] - handle_backtraces: HashMap< - usize, - ( - Option<&'static str>, - HashMap>, - ), - >, -} - -#[cfg(any(test, feature = "test-support"))] -impl LeakDetector { - fn handle_created(&mut self, type_name: Option<&'static str>, entity_id: usize) -> usize { - let handle_id = post_inc(&mut self.next_handle_id); - let entry = self.handle_backtraces.entry(entity_id).or_default(); - let backtrace = if *LEAK_BACKTRACE { - Some(backtrace::Backtrace::new_unresolved()) - } else { - None - }; - if let Some(type_name) = type_name { - entry.0.get_or_insert(type_name); - } - entry.1.insert(handle_id, backtrace); - handle_id - } - - fn handle_dropped(&mut self, entity_id: usize, handle_id: usize) { - if let Some((_, backtraces)) = self.handle_backtraces.get_mut(&entity_id) { - assert!(backtraces.remove(&handle_id).is_some()); - if backtraces.is_empty() { - self.handle_backtraces.remove(&entity_id); - } - } - } - - pub fn assert_dropped(&mut self, entity_id: usize) { - if let Some((type_name, backtraces)) = self.handle_backtraces.get_mut(&entity_id) { - for trace in backtraces.values_mut().flatten() { - trace.resolve(); - eprintln!("{:?}", crate::util::CwdBacktrace(trace)); - } - - let hint = if *LEAK_BACKTRACE { - "" - } else { - " – set LEAK_BACKTRACE=1 for more information" - }; - - panic!( - "{} handles to {} {} still exist{}", - backtraces.len(), - type_name.unwrap_or("entity"), - entity_id, - hint - ); - } - } - - pub fn detect(&mut self) { - let mut found_leaks = false; - for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() { - eprintln!( - "leaked {} handles to {} {}", - backtraces.len(), - type_name.unwrap_or("entity"), - id - ); - for trace in backtraces.values_mut().flatten() { - trace.resolve(); - eprintln!("{:?}", crate::util::CwdBacktrace(trace)); - } - found_leaks = true; - } - - let hint = if *LEAK_BACKTRACE { - "" - } else { - " – set LEAK_BACKTRACE=1 for more information" - }; - assert!(!found_leaks, "detected leaked handles{}", hint); - } -} - -#[derive(Default)] -struct RefCounts { - entity_counts: HashMap, - element_state_counts: HashMap, - dropped_models: HashSet, - dropped_views: HashSet<(usize, usize)>, - dropped_element_states: HashSet, - - #[cfg(any(test, feature = "test-support"))] - leak_detector: Arc>, -} - -struct ElementStateRefCount { - ref_count: usize, - frame_id: usize, -} - -impl RefCounts { - fn inc_model(&mut self, model_id: usize) { - match self.entity_counts.entry(model_id) { - Entry::Occupied(mut entry) => { - *entry.get_mut() += 1; - } - Entry::Vacant(entry) => { - entry.insert(1); - self.dropped_models.remove(&model_id); - } - } - } - - fn inc_view(&mut self, window_id: usize, view_id: usize) { - match self.entity_counts.entry(view_id) { - Entry::Occupied(mut entry) => *entry.get_mut() += 1, - Entry::Vacant(entry) => { - entry.insert(1); - self.dropped_views.remove(&(window_id, view_id)); - } - } - } - - fn inc_element_state(&mut self, id: ElementStateId, frame_id: usize) { - match self.element_state_counts.entry(id) { - Entry::Occupied(mut entry) => { - let entry = entry.get_mut(); - if entry.frame_id == frame_id || entry.ref_count >= 2 { - panic!("used the same element state more than once in the same frame"); - } - entry.ref_count += 1; - entry.frame_id = frame_id; - } - Entry::Vacant(entry) => { - entry.insert(ElementStateRefCount { - ref_count: 1, - frame_id, - }); - self.dropped_element_states.remove(&id); - } - } - } - - fn dec_model(&mut self, model_id: usize) { - let count = self.entity_counts.get_mut(&model_id).unwrap(); - *count -= 1; - if *count == 0 { - self.entity_counts.remove(&model_id); - self.dropped_models.insert(model_id); - } - } - - fn dec_view(&mut self, window_id: usize, view_id: usize) { - let count = self.entity_counts.get_mut(&view_id).unwrap(); - *count -= 1; - if *count == 0 { - self.entity_counts.remove(&view_id); - self.dropped_views.insert((window_id, view_id)); - } - } - - fn dec_element_state(&mut self, id: ElementStateId) { - let entry = self.element_state_counts.get_mut(&id).unwrap(); - entry.ref_count -= 1; - if entry.ref_count == 0 { - self.element_state_counts.remove(&id); - self.dropped_element_states.insert(id); - } - } - - fn is_entity_alive(&self, entity_id: usize) -> bool { - self.entity_counts.contains_key(&entity_id) - } - - fn take_dropped( - &mut self, - ) -> ( - HashSet, - HashSet<(usize, usize)>, - HashSet, - ) { - ( - std::mem::take(&mut self.dropped_models), - std::mem::take(&mut self.dropped_views), - std::mem::take(&mut self.dropped_element_states), - ) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/gpui/src/app/menu.rs b/crates/gpui/src/app/menu.rs new file mode 100644 index 0000000000..2234bfa391 --- /dev/null +++ b/crates/gpui/src/app/menu.rs @@ -0,0 +1,52 @@ +use crate::{Action, App, ForegroundPlatform, MutableAppContext}; + +pub struct Menu<'a> { + pub name: &'a str, + pub items: Vec>, +} + +pub enum MenuItem<'a> { + Separator, + Submenu(Menu<'a>), + Action { + name: &'a str, + action: Box, + }, +} + +impl MutableAppContext { + pub fn set_menus(&mut self, menus: Vec) { + self.foreground_platform + .set_menus(menus, &self.keystroke_matcher); + } +} + +pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform, app: &App) { + foreground_platform.on_will_open_menu(Box::new({ + let cx = app.0.clone(); + move || { + let mut cx = cx.borrow_mut(); + cx.keystroke_matcher.clear_pending(); + } + })); + foreground_platform.on_validate_menu_command(Box::new({ + let cx = app.0.clone(); + move |action| { + let cx = cx.borrow_mut(); + !cx.keystroke_matcher.has_pending_keystrokes() && cx.is_action_available(action) + } + })); + foreground_platform.on_menu_command(Box::new({ + let cx = app.0.clone(); + move |action| { + let mut cx = cx.borrow_mut(); + if let Some(key_window_id) = cx.cx.platform.key_window_id() { + if let Some(view_id) = cx.focused_view_id(key_window_id) { + cx.handle_dispatch_action_from_effect(key_window_id, Some(view_id), action); + return; + } + } + cx.dispatch_global_action_any(action); + } + })); +} diff --git a/crates/gpui/src/app/ref_counts.rs b/crates/gpui/src/app/ref_counts.rs new file mode 100644 index 0000000000..a9ae6e7a6c --- /dev/null +++ b/crates/gpui/src/app/ref_counts.rs @@ -0,0 +1,217 @@ +use std::sync::Arc; + +use lazy_static::lazy_static; +use parking_lot::Mutex; + +use collections::{hash_map::Entry, HashMap, HashSet}; + +use crate::{util::post_inc, ElementStateId}; + +lazy_static! { + static ref LEAK_BACKTRACE: bool = + std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty()); +} + +struct ElementStateRefCount { + ref_count: usize, + frame_id: usize, +} + +#[derive(Default)] +pub struct RefCounts { + entity_counts: HashMap, + element_state_counts: HashMap, + dropped_models: HashSet, + dropped_views: HashSet<(usize, usize)>, + dropped_element_states: HashSet, + + #[cfg(any(test, feature = "test-support"))] + pub leak_detector: Arc>, +} + +impl RefCounts { + pub fn new( + #[cfg(any(test, feature = "test-support"))] leak_detector: Arc>, + ) -> Self { + Self { + #[cfg(any(test, feature = "test-support"))] + leak_detector, + ..Default::default() + } + } + + pub fn inc_model(&mut self, model_id: usize) { + match self.entity_counts.entry(model_id) { + Entry::Occupied(mut entry) => { + *entry.get_mut() += 1; + } + Entry::Vacant(entry) => { + entry.insert(1); + self.dropped_models.remove(&model_id); + } + } + } + + pub fn inc_view(&mut self, window_id: usize, view_id: usize) { + match self.entity_counts.entry(view_id) { + Entry::Occupied(mut entry) => *entry.get_mut() += 1, + Entry::Vacant(entry) => { + entry.insert(1); + self.dropped_views.remove(&(window_id, view_id)); + } + } + } + + pub fn inc_element_state(&mut self, id: ElementStateId, frame_id: usize) { + match self.element_state_counts.entry(id) { + Entry::Occupied(mut entry) => { + let entry = entry.get_mut(); + if entry.frame_id == frame_id || entry.ref_count >= 2 { + panic!("used the same element state more than once in the same frame"); + } + entry.ref_count += 1; + entry.frame_id = frame_id; + } + Entry::Vacant(entry) => { + entry.insert(ElementStateRefCount { + ref_count: 1, + frame_id, + }); + self.dropped_element_states.remove(&id); + } + } + } + + pub fn dec_model(&mut self, model_id: usize) { + let count = self.entity_counts.get_mut(&model_id).unwrap(); + *count -= 1; + if *count == 0 { + self.entity_counts.remove(&model_id); + self.dropped_models.insert(model_id); + } + } + + pub fn dec_view(&mut self, window_id: usize, view_id: usize) { + let count = self.entity_counts.get_mut(&view_id).unwrap(); + *count -= 1; + if *count == 0 { + self.entity_counts.remove(&view_id); + self.dropped_views.insert((window_id, view_id)); + } + } + + pub fn dec_element_state(&mut self, id: ElementStateId) { + let entry = self.element_state_counts.get_mut(&id).unwrap(); + entry.ref_count -= 1; + if entry.ref_count == 0 { + self.element_state_counts.remove(&id); + self.dropped_element_states.insert(id); + } + } + + pub fn is_entity_alive(&self, entity_id: usize) -> bool { + self.entity_counts.contains_key(&entity_id) + } + + pub fn take_dropped( + &mut self, + ) -> ( + HashSet, + HashSet<(usize, usize)>, + HashSet, + ) { + ( + std::mem::take(&mut self.dropped_models), + std::mem::take(&mut self.dropped_views), + std::mem::take(&mut self.dropped_element_states), + ) + } +} + +#[cfg(any(test, feature = "test-support"))] +#[derive(Default)] +pub struct LeakDetector { + next_handle_id: usize, + #[allow(clippy::type_complexity)] + handle_backtraces: HashMap< + usize, + ( + Option<&'static str>, + HashMap>, + ), + >, +} + +#[cfg(any(test, feature = "test-support"))] +impl LeakDetector { + pub fn handle_created(&mut self, type_name: Option<&'static str>, entity_id: usize) -> usize { + let handle_id = post_inc(&mut self.next_handle_id); + let entry = self.handle_backtraces.entry(entity_id).or_default(); + let backtrace = if *LEAK_BACKTRACE { + Some(backtrace::Backtrace::new_unresolved()) + } else { + None + }; + if let Some(type_name) = type_name { + entry.0.get_or_insert(type_name); + } + entry.1.insert(handle_id, backtrace); + handle_id + } + + pub fn handle_dropped(&mut self, entity_id: usize, handle_id: usize) { + if let Some((_, backtraces)) = self.handle_backtraces.get_mut(&entity_id) { + assert!(backtraces.remove(&handle_id).is_some()); + if backtraces.is_empty() { + self.handle_backtraces.remove(&entity_id); + } + } + } + + pub fn assert_dropped(&mut self, entity_id: usize) { + if let Some((type_name, backtraces)) = self.handle_backtraces.get_mut(&entity_id) { + for trace in backtraces.values_mut().flatten() { + trace.resolve(); + eprintln!("{:?}", crate::util::CwdBacktrace(trace)); + } + + let hint = if *LEAK_BACKTRACE { + "" + } else { + " – set LEAK_BACKTRACE=1 for more information" + }; + + panic!( + "{} handles to {} {} still exist{}", + backtraces.len(), + type_name.unwrap_or("entity"), + entity_id, + hint + ); + } + } + + pub fn detect(&mut self) { + let mut found_leaks = false; + for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() { + eprintln!( + "leaked {} handles to {} {}", + backtraces.len(), + type_name.unwrap_or("entity"), + id + ); + for trace in backtraces.values_mut().flatten() { + trace.resolve(); + eprintln!("{:?}", crate::util::CwdBacktrace(trace)); + } + found_leaks = true; + } + + let hint = if *LEAK_BACKTRACE { + "" + } else { + " – set LEAK_BACKTRACE=1 for more information" + }; + assert!(!found_leaks, "detected leaked handles{}", hint); + } +} diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 67455cd2a7..0805cdd865 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -19,13 +19,14 @@ use smol::stream::StreamExt; use crate::{ executor, geometry::vector::Vector2F, keymap_matcher::Keystroke, platform, Action, AnyViewHandle, AppContext, Appearance, Entity, Event, FontCache, InputHandler, KeyDownEvent, - LeakDetector, ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith, - ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle, - WeakHandle, WindowInputHandler, + ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith, ReadViewWith, + RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle, WeakHandle, }; use collections::BTreeMap; -use super::{AsyncAppContext, RefCounts}; +use super::{ + ref_counts::LeakDetector, window_input_handler::WindowInputHandler, AsyncAppContext, RefCounts, +}; pub struct TestAppContext { cx: Rc>, @@ -52,11 +53,7 @@ impl TestAppContext { platform, foreground_platform.clone(), font_cache, - RefCounts { - #[cfg(any(test, feature = "test-support"))] - leak_detector, - ..Default::default() - }, + RefCounts::new(leak_detector), (), ); cx.next_entity_id = first_entity_id; diff --git a/crates/gpui/src/app/window_input_handler.rs b/crates/gpui/src/app/window_input_handler.rs new file mode 100644 index 0000000000..855f0e3041 --- /dev/null +++ b/crates/gpui/src/app/window_input_handler.rs @@ -0,0 +1,98 @@ +use std::{cell::RefCell, ops::Range, rc::Rc}; + +use pathfinder_geometry::rect::RectF; + +use crate::{AnyView, AppContext, InputHandler, MutableAppContext}; + +pub struct WindowInputHandler { + pub app: Rc>, + pub window_id: usize, +} + +impl WindowInputHandler { + fn read_focused_view(&self, f: F) -> Option + where + F: FnOnce(&dyn AnyView, &AppContext) -> T, + { + // Input-related application hooks are sometimes called by the OS during + // a call to a window-manipulation API, like prompting the user for file + // paths. In that case, the AppContext will already be borrowed, so any + // InputHandler methods need to fail gracefully. + // + // See https://github.com/zed-industries/community/issues/444 + let app = self.app.try_borrow().ok()?; + + let view_id = app.focused_view_id(self.window_id)?; + let view = app.cx.views.get(&(self.window_id, view_id))?; + let result = f(view.as_ref(), &app); + Some(result) + } + + fn update_focused_view(&mut self, f: F) -> Option + where + F: FnOnce(usize, usize, &mut dyn AnyView, &mut MutableAppContext) -> T, + { + let mut app = self.app.try_borrow_mut().ok()?; + app.update(|app| { + let view_id = app.focused_view_id(self.window_id)?; + let mut view = app.cx.views.remove(&(self.window_id, view_id))?; + let result = f(self.window_id, view_id, view.as_mut(), &mut *app); + app.cx.views.insert((self.window_id, view_id), view); + Some(result) + }) + } +} + +impl InputHandler for WindowInputHandler { + fn text_for_range(&self, range: Range) -> Option { + self.read_focused_view(|view, cx| view.text_for_range(range.clone(), cx)) + .flatten() + } + + fn selected_text_range(&self) -> Option> { + self.read_focused_view(|view, cx| view.selected_text_range(cx)) + .flatten() + } + + fn replace_text_in_range(&mut self, range: Option>, text: &str) { + self.update_focused_view(|window_id, view_id, view, cx| { + view.replace_text_in_range(range, text, cx, window_id, view_id); + }); + } + + fn marked_text_range(&self) -> Option> { + self.read_focused_view(|view, cx| view.marked_text_range(cx)) + .flatten() + } + + fn unmark_text(&mut self) { + self.update_focused_view(|window_id, view_id, view, cx| { + view.unmark_text(cx, window_id, view_id); + }); + } + + fn replace_and_mark_text_in_range( + &mut self, + range: Option>, + new_text: &str, + new_selected_range: Option>, + ) { + self.update_focused_view(|window_id, view_id, view, cx| { + view.replace_and_mark_text_in_range( + range, + new_text, + new_selected_range, + cx, + window_id, + view_id, + ); + }); + } + + fn rect_for_range(&self, range_utf16: Range) -> Option { + let app = self.app.borrow(); + let (presenter, _) = app.presenters_and_platform_windows.get(&self.window_id)?; + let presenter = presenter.borrow(); + presenter.rect_for_text_range(range_utf16, &app) + } +} diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index aa73aebc90..173c8d8505 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -5,7 +5,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, keymap_matcher::KeymapMatcher, - Action, ClipboardItem, + Action, ClipboardItem, Menu, }; use anyhow::{anyhow, Result}; use collections::VecDeque; @@ -77,7 +77,7 @@ impl super::ForegroundPlatform for ForegroundPlatform { fn on_menu_command(&self, _: Box) {} fn on_validate_menu_command(&self, _: Box bool>) {} fn on_will_open_menu(&self, _: Box) {} - fn set_menus(&self, _: Vec, _: &KeymapMatcher) {} + fn set_menus(&self, _: Vec, _: &KeymapMatcher) {} fn prompt_for_paths( &self, diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index eb992b638a..d784d43ece 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -1,14 +1,3 @@ -use crate::{ - elements::Empty, - executor::{self, ExecutorEvent}, - platform, - util::CwdBacktrace, - Element, ElementBox, Entity, FontCache, Handle, LeakDetector, MutableAppContext, Platform, - RenderContext, Subscription, TestAppContext, View, -}; -use futures::StreamExt; -use parking_lot::Mutex; -use smol::channel; use std::{ fmt::Write, panic::{self, RefUnwindSafe}, @@ -19,6 +8,20 @@ use std::{ }, }; +use futures::StreamExt; +use parking_lot::Mutex; +use smol::channel; + +use crate::{ + app::ref_counts::LeakDetector, + elements::Empty, + executor::{self, ExecutorEvent}, + platform, + util::CwdBacktrace, + Element, ElementBox, Entity, FontCache, Handle, MutableAppContext, Platform, RenderContext, + Subscription, TestAppContext, View, +}; + #[cfg(test)] #[ctor::ctor] fn init_logger() { diff --git a/crates/sqlez/Cargo.toml b/crates/sqlez/Cargo.toml index f247f3e537..716ec76644 100644 --- a/crates/sqlez/Cargo.toml +++ b/crates/sqlez/Cargo.toml @@ -15,7 +15,4 @@ thread_local = "1.1.4" lazy_static = "1.4" parking_lot = "0.11.1" futures = "0.3" -uuid = { version = "1.1.2", features = ["v4"] } - -[dev-dependencies] -sqlez_macros = { path = "../sqlez_macros"} \ No newline at end of file +uuid = { version = "1.1.2", features = ["v4"] } \ No newline at end of file diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index d452d4b790..c58f66478f 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -1,4 +1,4 @@ -use editor::{EditorBlurred, EditorFocused, EditorMode, EditorReleased}; +use editor::{EditorBlurred, EditorFocused, EditorMode, EditorReleased, Event}; use gpui::MutableAppContext; use crate::{state::Mode, Vim}; @@ -20,14 +20,18 @@ fn focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppContext) { } vim.active_editor = Some(editor.downgrade()); - dbg!("Active editor changed", editor.read(cx).mode()); - vim.editor_subscription = Some(cx.subscribe(editor, |editor, event, cx| { - if editor.read(cx).leader_replica_id().is_none() { - if let editor::Event::SelectionsChanged { local: true } = event { - let newest_empty = editor.read(cx).selections.newest::(cx).is_empty(); + vim.editor_subscription = Some(cx.subscribe(editor, |editor, event, cx| match event { + Event::SelectionsChanged { local: true } => { + let editor = editor.read(cx); + if editor.leader_replica_id().is_none() { + let newest_empty = editor.selections.newest::(cx).is_empty(); local_selections_changed(newest_empty, cx); } } + Event::InputIgnored { text } => { + Vim::active_editor_input_ignored(text.clone(), cx); + } + _ => {} })); if vim.enabled { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 62b30730e8..8bc7c756e0 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use editor::{ char_kind, display_map::{DisplaySnapshot, ToDisplayPoint}, @@ -15,7 +17,7 @@ use crate::{ Vim, }; -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Motion { Left, Backspace, @@ -32,8 +34,8 @@ pub enum Motion { StartOfDocument, EndOfDocument, Matching, - FindForward { before: bool, character: char }, - FindBackward { after: bool, character: char }, + FindForward { before: bool, text: Arc }, + FindBackward { after: bool, text: Arc }, } #[derive(Clone, Deserialize, PartialEq)] @@ -134,7 +136,7 @@ pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) { // Motion handling is specified here: // https://github.com/vim/vim/blob/master/runtime/doc/motion.txt impl Motion { - pub fn linewise(self) -> bool { + pub fn linewise(&self) -> bool { use Motion::*; matches!( self, @@ -142,12 +144,12 @@ impl Motion { ) } - pub fn infallible(self) -> bool { + pub fn infallible(&self) -> bool { use Motion::*; matches!(self, StartOfDocument | CurrentLine | EndOfDocument) } - pub fn inclusive(self) -> bool { + pub fn inclusive(&self) -> bool { use Motion::*; match self { Down @@ -171,13 +173,14 @@ impl Motion { } pub fn move_point( - self, + &self, map: &DisplaySnapshot, point: DisplayPoint, goal: SelectionGoal, times: usize, ) -> Option<(DisplayPoint, SelectionGoal)> { use Motion::*; + let infallible = self.infallible(); let (new_point, goal) = match self { Left => (left(map, point, times), SelectionGoal::None), Backspace => (backspace(map, point, times), SelectionGoal::None), @@ -185,15 +188,15 @@ impl Motion { Up => up(map, point, goal, times), Right => (right(map, point, times), SelectionGoal::None), NextWordStart { ignore_punctuation } => ( - next_word_start(map, point, ignore_punctuation, times), + next_word_start(map, point, *ignore_punctuation, times), SelectionGoal::None, ), NextWordEnd { ignore_punctuation } => ( - next_word_end(map, point, ignore_punctuation, times), + next_word_end(map, point, *ignore_punctuation, times), SelectionGoal::None, ), PreviousWordStart { ignore_punctuation } => ( - previous_word_start(map, point, ignore_punctuation, times), + previous_word_start(map, point, *ignore_punctuation, times), SelectionGoal::None, ), FirstNonWhitespace => (first_non_whitespace(map, point), SelectionGoal::None), @@ -203,22 +206,22 @@ impl Motion { StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None), EndOfDocument => (end_of_document(map, point, times), SelectionGoal::None), Matching => (matching(map, point), SelectionGoal::None), - FindForward { before, character } => ( - find_forward(map, point, before, character, times), + FindForward { before, text } => ( + find_forward(map, point, *before, text.clone(), times), SelectionGoal::None, ), - FindBackward { after, character } => ( - find_backward(map, point, after, character, times), + FindBackward { after, text } => ( + find_backward(map, point, *after, text.clone(), times), SelectionGoal::None, ), }; - (new_point != point || self.infallible()).then_some((new_point, goal)) + (new_point != point || infallible).then_some((new_point, goal)) } // Expands a selection using self motion for an operator pub fn expand_selection( - self, + &self, map: &DisplaySnapshot, selection: &mut Selection, times: usize, @@ -254,7 +257,7 @@ impl Motion { // but "d}" will not include that line. let mut inclusive = self.inclusive(); if !inclusive - && self != Motion::Backspace + && self != &Motion::Backspace && selection.end.row() > selection.start.row() && selection.end.column() == 0 { @@ -466,45 +469,42 @@ fn find_forward( map: &DisplaySnapshot, from: DisplayPoint, before: bool, - target: char, - mut times: usize, + target: Arc, + times: usize, ) -> DisplayPoint { - let mut previous_point = from; - - for (ch, point) in map.chars_at(from) { - if ch == target && point != from { - times -= 1; - if times == 0 { - return if before { previous_point } else { point }; + map.find_while(from, target.as_ref(), |ch, _| ch != '\n') + .skip_while(|found_at| found_at == &from) + .nth(times - 1) + .map(|mut found| { + if before { + *found.column_mut() -= 1; + found = map.clip_point(found, Bias::Right); + found + } else { + found } - } else if ch == '\n' { - break; - } - previous_point = point; - } - - from + }) + .unwrap_or(from) } fn find_backward( map: &DisplaySnapshot, from: DisplayPoint, after: bool, - target: char, - mut times: usize, + target: Arc, + times: usize, ) -> DisplayPoint { - let mut previous_point = from; - for (ch, point) in map.reverse_chars_at(from) { - if ch == target && point != from { - times -= 1; - if times == 0 { - return if after { previous_point } else { point }; + map.reverse_find_while(from, target.as_ref(), |ch, _| ch != '\n') + .skip_while(|found_at| found_at == &from) + .nth(times - 1) + .map(|mut found| { + if after { + *found.column_mut() += 1; + found = map.clip_point(found, Bias::Left); + found + } else { + found } - } else if ch == '\n' { - break; - } - previous_point = point; - } - - from + }) + .unwrap_or(from) } diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index d6391353cf..742f2426c8 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -2,7 +2,7 @@ mod change; mod delete; mod yank; -use std::{borrow::Cow, cmp::Ordering}; +use std::{borrow::Cow, cmp::Ordering, sync::Arc}; use crate::{ motion::Motion, @@ -424,7 +424,7 @@ fn scroll(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext, cx: &mut MutableAppContext) { Vim::update(cx, |vim, cx| { vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { @@ -453,7 +453,7 @@ pub(crate) fn normal_replace(text: &str, cx: &mut MutableAppContext) { ( range.start.to_offset(&map, Bias::Left) ..range.end.to_offset(&map, Bias::Left), - text, + text.clone(), ) }) .collect::>(); diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 723dac0581..539ab0a8ff 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -53,7 +53,7 @@ impl<'a> VimTestContext<'a> { // Setup search toolbars and keypress hook workspace.update(cx, |workspace, cx| { - observe_keypresses(window_id, cx); + observe_keystrokes(window_id, cx); workspace.active_pane().update(cx, |pane, cx| { pane.toolbar().update(cx, |toolbar, cx| { let buffer_search_bar = cx.add_view(BufferSearchBar::new); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 699ff01dcc..9fe9969680 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -10,12 +10,12 @@ mod state; mod utils; mod visual; +use std::sync::Arc; + use command_palette::CommandPaletteFilter; use editor::{Bias, Cancel, Editor, EditorMode}; use gpui::{ - impl_actions, - keymap_matcher::{KeyPressed, Keystroke}, - MutableAppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, + impl_actions, MutableAppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, }; use language::CursorShape; use motion::Motion; @@ -57,11 +57,6 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(|_: &mut Workspace, n: &Number, cx: _| { Vim::update(cx, |vim, cx| vim.push_number(n, cx)); }); - cx.add_action( - |_: &mut Workspace, KeyPressed { keystroke }: &KeyPressed, cx| { - Vim::key_pressed(keystroke, cx); - }, - ); // Editor Actions cx.add_action(|_: &mut Editor, _: &Cancel, cx| { @@ -91,7 +86,7 @@ pub fn init(cx: &mut MutableAppContext) { .detach(); } -pub fn observe_keypresses(window_id: usize, cx: &mut MutableAppContext) { +pub fn observe_keystrokes(window_id: usize, cx: &mut MutableAppContext) { cx.observe_keystrokes(window_id, |_keystroke, _result, handled_by, cx| { if let Some(handled_by) = handled_by { // Keystroke is handled by the vim system, so continue forward @@ -103,11 +98,12 @@ pub fn observe_keypresses(window_id: usize, cx: &mut MutableAppContext) { } } - Vim::update(cx, |vim, cx| { - if vim.active_operator().is_some() { - // If the keystroke is not handled by vim, we should clear the operator + Vim::update(cx, |vim, cx| match vim.active_operator() { + Some(Operator::FindForward { .. } | Operator::FindBackward { .. }) => {} + Some(_) => { vim.clear_operator(cx); } + _ => {} }); true }) @@ -164,7 +160,6 @@ impl Vim { .and_then(|editor| editor.upgrade(cx)) { editor.update(cx, |editor, cx| { - dbg!(&mode, editor.mode()); editor.change_selections(None, cx, |s| { s.move_with(|map, selection| { if self.state.empty_selections_only() { @@ -221,24 +216,24 @@ impl Vim { self.state.operator_stack.last().copied() } - fn key_pressed(keystroke: &Keystroke, cx: &mut ViewContext) { + fn active_editor_input_ignored(text: Arc, cx: &mut MutableAppContext) { + if text.is_empty() { + return; + } + match Vim::read(cx).active_operator() { Some(Operator::FindForward { before }) => { - if let Some(character) = keystroke.key.chars().next() { - motion::motion(Motion::FindForward { before, character }, cx) - } + motion::motion(Motion::FindForward { before, text }, cx) } Some(Operator::FindBackward { after }) => { - if let Some(character) = keystroke.key.chars().next() { - motion::motion(Motion::FindBackward { after, character }, cx) - } + motion::motion(Motion::FindBackward { after, text }, cx) } Some(Operator::Replace) => match Vim::read(cx).state.mode { - Mode::Normal => normal_replace(&keystroke.key, cx), - Mode::Visual { line } => visual_replace(&keystroke.key, line, cx), + Mode::Normal => normal_replace(text, cx), + Mode::Visual { line } => visual_replace(text, line, cx), _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)), }, - _ => cx.propagate_action(), + _ => {} } } diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index ac8771f969..b890e4e41b 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -1,4 +1,4 @@ -use std::borrow::Cow; +use std::{borrow::Cow, sync::Arc}; use collections::HashMap; use editor::{ @@ -313,7 +313,7 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext }); } -pub(crate) fn visual_replace(text: &str, line: bool, cx: &mut MutableAppContext) { +pub(crate) fn visual_replace(text: Arc, line: bool, cx: &mut MutableAppContext) { Vim::update(cx, |vim, cx| { vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 9195c36940..bf9afe136e 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -363,7 +363,7 @@ pub fn initialize_workspace( auto_update::notify_of_any_new_update(cx.weak_handle(), cx); let window_id = cx.window_id(); - vim::observe_keypresses(window_id, cx); + vim::observe_keystrokes(window_id, cx); cx.on_window_should_close(|workspace, cx| { if let Some(task) = workspace.close(&Default::default(), cx) { From 459060764a3c66dcd1c816ba30c4e317ddeb9d64 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 10 Feb 2023 15:41:19 -0800 Subject: [PATCH 124/180] fix sqlez warning, introduce tab and enter bindings to vim for inputing tab and enter text when waiting for text --- assets/keymaps/vim.json | 3 ++- crates/sqlez/src/migrations.rs | 19 +++++-------------- crates/vim/src/vim.rs | 17 ++++++++++++++--- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index e8a7d767f0..824fb63c0f 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -315,7 +315,8 @@ { "context": "Editor && VimWaiting", "bindings": { - // "*": "gpui::KeyPressed", + "tab": "vim::Tab", + "enter": "vim::Enter", "escape": "editor::Cancel" } } diff --git a/crates/sqlez/src/migrations.rs b/crates/sqlez/src/migrations.rs index b3aaef95b1..b8e589e268 100644 --- a/crates/sqlez/src/migrations.rs +++ b/crates/sqlez/src/migrations.rs @@ -83,7 +83,6 @@ impl Connection { #[cfg(test)] mod test { use indoc::indoc; - use sqlez_macros::sql; use crate::connection::Connection; @@ -288,21 +287,18 @@ mod test { let connection = Connection::open_memory(Some("test_create_alter_drop")); connection - .migrate( - "first_migration", - &[sql!( CREATE TABLE table1(a TEXT) STRICT; )], - ) + .migrate("first_migration", &["CREATE TABLE table1(a TEXT) STRICT;"]) .unwrap(); connection - .exec(sql!( INSERT INTO table1(a) VALUES ("test text"); )) + .exec("INSERT INTO table1(a) VALUES (\"test text\");") .unwrap()() .unwrap(); connection .migrate( "second_migration", - &[sql!( + &[indoc! {" CREATE TABLE table2(b TEXT) STRICT; INSERT INTO table2 (b) @@ -311,16 +307,11 @@ mod test { DROP TABLE table1; ALTER TABLE table2 RENAME TO table1; - )], + "}], ) .unwrap(); - let res = &connection - .select::(sql!( - SELECT b FROM table1 - )) - .unwrap()() - .unwrap()[0]; + let res = &connection.select::("SELECT b FROM table1").unwrap()().unwrap()[0]; assert_eq!(res, "test text"); } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 9fe9969680..33f142c21e 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use command_palette::CommandPaletteFilter; use editor::{Bias, Cancel, Editor, EditorMode}; use gpui::{ - impl_actions, MutableAppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, + actions, impl_actions, MutableAppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, }; use language::CursorShape; use motion::Motion; @@ -35,6 +35,7 @@ pub struct PushOperator(pub Operator); #[derive(Clone, Deserialize, PartialEq)] struct Number(u8); +actions!(vim, [Tab, Enter]); impl_actions!(vim, [Number, SwitchMode, PushOperator]); pub fn init(cx: &mut MutableAppContext) { @@ -74,8 +75,16 @@ pub fn init(cx: &mut MutableAppContext) { } }); + cx.add_action(|_: &mut Workspace, _: &Tab, cx| { + Vim::active_editor_input_ignored(" ".into(), cx) + }); + + cx.add_action(|_: &mut Workspace, _: &Enter, cx| { + Vim::active_editor_input_ignored("\n".into(), cx) + }); + // Sync initial settings with the rest of the app - Vim::update(cx, |state, cx| state.sync_vim_settings(cx)); + Vim::update(cx, |vim, cx| vim.sync_vim_settings(cx)); // Any time settings change, update vim mode to match cx.observe_global::(|cx| { @@ -99,7 +108,9 @@ pub fn observe_keystrokes(window_id: usize, cx: &mut MutableAppContext) { } Vim::update(cx, |vim, cx| match vim.active_operator() { - Some(Operator::FindForward { .. } | Operator::FindBackward { .. }) => {} + Some( + Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace, + ) => {} Some(_) => { vim.clear_operator(cx); } From 327932ba3b39f832d529cd274fd81e376d7c6810 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 10 Feb 2023 16:09:15 -0800 Subject: [PATCH 125/180] Remove catch all keymap and KeyPressed action --- crates/command_palette/src/command_palette.rs | 2 +- crates/gpui/src/keymap_matcher.rs | 19 ++-------- crates/gpui/src/keymap_matcher/binding.rs | 36 ++++++------------- crates/gpui/src/platform/mac/platform.rs | 2 +- 4 files changed, 14 insertions(+), 45 deletions(-) diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index d4625cbce0..5b5d8f1162 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -65,7 +65,7 @@ impl CommandPalette { action, keystrokes: bindings .iter() - .filter_map(|binding| binding.keystrokes()) + .map(|binding| binding.keystrokes()) .last() .map_or(Vec::new(), |keystrokes| keystrokes.to_vec()), }) diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index c7de035232..edcc458658 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -6,24 +6,15 @@ mod keystroke; use std::{any::TypeId, fmt::Debug}; use collections::HashMap; -use serde::Deserialize; use smallvec::SmallVec; -use crate::{impl_actions, Action}; +use crate::Action; pub use binding::{Binding, BindingMatchResult}; pub use keymap::Keymap; pub use keymap_context::{KeymapContext, KeymapContextPredicate}; pub use keystroke::Keystroke; -#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)] -pub struct KeyPressed { - #[serde(default)] - pub keystroke: Keystroke, -} - -impl_actions!(gpui, [KeyPressed]); - pub struct KeymapMatcher { pub contexts: Vec, pending_views: HashMap, @@ -102,13 +93,7 @@ impl KeymapMatcher { for binding in self.keymap.bindings().iter().rev() { match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..]) { - BindingMatchResult::Complete(mut action) => { - // Swap in keystroke for special KeyPressed action - if action.name() == "KeyPressed" && action.namespace() == "gpui" { - action = Box::new(KeyPressed { - keystroke: keystroke.clone(), - }); - } + BindingMatchResult::Complete(action) => { matched_bindings.push((view_id, action)) } BindingMatchResult::Partial => { diff --git a/crates/gpui/src/keymap_matcher/binding.rs b/crates/gpui/src/keymap_matcher/binding.rs index 8146437884..c1cfd14e82 100644 --- a/crates/gpui/src/keymap_matcher/binding.rs +++ b/crates/gpui/src/keymap_matcher/binding.rs @@ -7,7 +7,7 @@ use super::{KeymapContext, KeymapContextPredicate, Keystroke}; pub struct Binding { action: Box, - keystrokes: Option>, + keystrokes: SmallVec<[Keystroke; 2]>, context_predicate: Option, } @@ -23,16 +23,10 @@ impl Binding { None }; - let keystrokes = if keystrokes == "*" { - None // Catch all context - } else { - Some( - keystrokes - .split_whitespace() - .map(Keystroke::parse) - .collect::>()?, - ) - }; + let keystrokes = keystrokes + .split_whitespace() + .map(Keystroke::parse) + .collect::>()?; Ok(Self { keystrokes, @@ -53,20 +47,10 @@ impl Binding { pending_keystrokes: &Vec, contexts: &[KeymapContext], ) -> BindingMatchResult { - if self - .keystrokes - .as_ref() - .map(|keystrokes| keystrokes.starts_with(&pending_keystrokes)) - .unwrap_or(true) - && self.match_context(contexts) + if self.keystrokes.as_ref().starts_with(&pending_keystrokes) && self.match_context(contexts) { // If the binding is completed, push it onto the matches list - if self - .keystrokes - .as_ref() - .map(|keystrokes| keystrokes.len() == pending_keystrokes.len()) - .unwrap_or(true) - { + if self.keystrokes.as_ref().len() == pending_keystrokes.len() { BindingMatchResult::Complete(self.action.boxed_clone()) } else { BindingMatchResult::Partial @@ -82,14 +66,14 @@ impl Binding { contexts: &[KeymapContext], ) -> Option> { if self.action.eq(action) && self.match_context(contexts) { - self.keystrokes.clone() + Some(self.keystrokes.clone()) } else { None } } - pub fn keystrokes(&self) -> Option<&[Keystroke]> { - self.keystrokes.as_deref() + pub fn keystrokes(&self) -> &[Keystroke] { + self.keystrokes.as_slice() } pub fn action(&self) -> &dyn Action { diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 5d13227585..3d57a7fe2a 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -184,7 +184,7 @@ impl MacForegroundPlatform { .map(|binding| binding.keystrokes()); let item; - if let Some(keystrokes) = keystrokes.flatten() { + if let Some(keystrokes) = keystrokes { if keystrokes.len() == 1 { let keystroke = &keystrokes[0]; let mut mask = NSEventModifierFlags::empty(); From 2c9199fd3283c4b839c928567824fa30ca64f2d3 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Mon, 13 Feb 2023 14:12:43 -0800 Subject: [PATCH 126/180] fix build error --- crates/gpui/src/app.rs | 2 +- crates/pando/Cargo.toml | 21 +++++++++++++++++++++ crates/pando/src/file_format.rs | 0 crates/pando/src/pando.rs | 15 +++++++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 crates/pando/Cargo.toml create mode 100644 crates/pando/src/file_format.rs create mode 100644 crates/pando/src/pando.rs diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index c0e0b067c4..60adadb96c 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1219,7 +1219,7 @@ impl MutableAppContext { .bindings_for_action_type(action.as_any().type_id()) .find_map(|b| { if b.match_context(&contexts) { - b.keystrokes().map(|s| s.into()) + Some(b.keystrokes().into()) } else { None } diff --git a/crates/pando/Cargo.toml b/crates/pando/Cargo.toml new file mode 100644 index 0000000000..8521c4fd81 --- /dev/null +++ b/crates/pando/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pando" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/pando.rs" + +[features] +test-support = [] + +[dependencies] +anyhow = "1.0.38" +client = { path = "../client" } +gpui = { path = "../gpui" } +settings = { path = "../settings" } +theme = { path = "../theme" } +workspace = { path = "../workspace" } +sqlez = { path = "../sqlez" } +sqlez_macros = { path = "../sqlez_macros" } \ No newline at end of file diff --git a/crates/pando/src/file_format.rs b/crates/pando/src/file_format.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/pando/src/pando.rs b/crates/pando/src/pando.rs new file mode 100644 index 0000000000..e75f843720 --- /dev/null +++ b/crates/pando/src/pando.rs @@ -0,0 +1,15 @@ +//! ## Goals +//! - Opinionated Subset of Obsidian. Only the things that cant be done other ways in zed +//! - Checked in .zp file is an sqlite db containing graph metadata +//! - All nodes are file urls +//! - Markdown links auto add soft linked nodes to the db +//! - Links create positioning data regardless of if theres a file +//! - Lock links to make structure that doesn't rotate or spread +//! - Drag from file finder to pando item to add it in +//! - For linked files, zoom out to see closest linking pando file + +//! ## Plan +//! - [ ] Make item backed by .zp sqlite file with camera position by user account +//! - [ ] Render grid of dots and allow scrolling around the grid +//! - [ ] Add scale property to layer canvas and manipulate it with pinch zooming +//! - [ ] Allow dropping files onto .zp pane. Their relative path is recorded into the file along with From ea663f3017db797d233a765467439fa2978f2ccd Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 13 Feb 2023 23:46:44 -0800 Subject: [PATCH 127/180] Bump tree-sitter for tree-balancing bugfix --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8eb8d6987a..84fdc9dda9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7003,7 +7003,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.20.9" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=36b5b6c89e55ad1a502f8b3234bb3e12ec83a5da#36b5b6c89e55ad1a502f8b3234bb3e12ec83a5da" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=125503ff3b613b08233fc1e06292be9ddd9dd448#125503ff3b613b08233fc1e06292be9ddd9dd448" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index 39e4cd6367..d4a2843338 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } rand = { version = "0.8" } [patch.crates-io] -tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "36b5b6c89e55ad1a502f8b3234bb3e12ec83a5da" } +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "125503ff3b613b08233fc1e06292be9ddd9dd448" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 From 087d51634d3072854c150371a892ef56c544b563 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 14 Feb 2023 10:46:29 +0100 Subject: [PATCH 128/180] Fix test that wasn't properly verifying disconnection from livekit --- crates/collab/src/tests/integration_tests.rs | 107 +++++++++++++++++-- 1 file changed, 100 insertions(+), 7 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 9a7e7295fe..55f888d11d 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -166,9 +166,67 @@ async fn test_basic_calls( } ); + // Call user C again from user A. + active_call_a + .update(cx_a, |call, cx| { + call.invite(client_c.user_id().unwrap(), None, cx) + }) + .await + .unwrap(); + + deterministic.run_until_parked(); + assert_eq!( + room_participants(&room_a, cx_a), + RoomParticipants { + remote: vec!["user_b".to_string()], + pending: vec!["user_c".to_string()] + } + ); + assert_eq!( + room_participants(&room_b, cx_b), + RoomParticipants { + remote: vec!["user_a".to_string()], + pending: vec!["user_c".to_string()] + } + ); + + // User C accepts the call. + let call_c = incoming_call_c.next().await.unwrap().unwrap(); + assert_eq!(call_c.calling_user.github_login, "user_a"); + active_call_c + .update(cx_c, |call, cx| call.accept_incoming(cx)) + .await + .unwrap(); + assert!(incoming_call_c.next().await.unwrap().is_none()); + let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone()); + + deterministic.run_until_parked(); + assert_eq!( + room_participants(&room_a, cx_a), + RoomParticipants { + remote: vec!["user_b".to_string(), "user_c".to_string()], + pending: Default::default() + } + ); + assert_eq!( + room_participants(&room_b, cx_b), + RoomParticipants { + remote: vec!["user_a".to_string(), "user_c".to_string()], + pending: Default::default() + } + ); + assert_eq!( + room_participants(&room_c, cx_c), + RoomParticipants { + remote: vec!["user_a".to_string(), "user_b".to_string()], + pending: Default::default() + } + ); + // User A shares their screen let display = MacOSDisplay::new(); let events_b = active_call_events(cx_b); + let events_c = active_call_events(cx_c); active_call_a .update(cx_a, |call, cx| { call.room().unwrap().update(cx, |room, cx| { @@ -181,9 +239,10 @@ async fn test_basic_calls( deterministic.run_until_parked(); + // User B observes the remote screen sharing track. assert_eq!(events_b.borrow().len(), 1); - let event = events_b.borrow().first().unwrap().clone(); - if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event { + let event_b = events_b.borrow().first().unwrap().clone(); + if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_b { assert_eq!(participant_id, client_a.peer_id().unwrap()); room_b.read_with(cx_b, |room, _| { assert_eq!( @@ -197,6 +256,23 @@ async fn test_basic_calls( panic!("unexpected event") } + // User C observes the remote screen sharing track. + assert_eq!(events_c.borrow().len(), 1); + let event_c = events_c.borrow().first().unwrap().clone(); + if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_c { + assert_eq!(participant_id, client_a.peer_id().unwrap()); + room_c.read_with(cx_c, |room, _| { + assert_eq!( + room.remote_participants()[&client_a.user_id().unwrap()] + .tracks + .len(), + 1 + ); + }); + } else { + panic!("unexpected event") + } + // User A leaves the room. active_call_a.update(cx_a, |call, cx| { call.hang_up(cx).unwrap(); @@ -213,18 +289,28 @@ async fn test_basic_calls( assert_eq!( room_participants(&room_b, cx_b), RoomParticipants { - remote: Default::default(), + remote: vec!["user_c".to_string()], + pending: Default::default() + } + ); + assert_eq!( + room_participants(&room_c, cx_c), + RoomParticipants { + remote: vec!["user_b".to_string()], pending: Default::default() } ); // User B gets disconnected from the LiveKit server, which causes them - // to automatically leave the room. + // to automatically leave the room. User C leaves the room as well because + // nobody else is in there. server .test_live_kit_server - .disconnect_client(client_b.peer_id().unwrap().to_string()) + .disconnect_client(client_b.user_id().unwrap().to_string()) .await; - active_call_b.update(cx_b, |call, _| assert!(call.room().is_none())); + deterministic.run_until_parked(); + active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none())); + active_call_c.read_with(cx_c, |call, _| assert!(call.room().is_none())); assert_eq!( room_participants(&room_a, cx_a), RoomParticipants { @@ -239,6 +325,13 @@ async fn test_basic_calls( pending: Default::default() } ); + assert_eq!( + room_participants(&room_c, cx_c), + RoomParticipants { + remote: Default::default(), + pending: Default::default() + } + ); } #[gpui::test(iterations = 10)] @@ -2572,7 +2665,7 @@ async fn test_fs_operations( .await .unwrap(); deterministic.run_until_parked(); - + worktree_a.read_with(cx_a, |worktree, _| { assert_eq!( worktree From 7be868e3726c268c0d7cfc01dabd2687e0951dbc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 14 Feb 2023 12:03:30 +0100 Subject: [PATCH 129/180] Avoid creating more than one room when inviting multiple people at once Previously, when initiating a call by calling multiple people, only the first person would get the call while all the others would briefly show a "pending" status but never get the call. This would happen because `ActiveCall` was trying to a create a different room for each person called, because the original room creation hadn't finished and so a `ModelHandle` wasn't being store in the active call. With this commit, only one room can be created at any given time and further invites have to wait until that room creation is done. --- crates/call/src/call.rs | 92 ++++++++----- crates/collab/src/tests/integration_tests.rs | 128 +++++++++++++++++++ 2 files changed, 190 insertions(+), 30 deletions(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 596a0ec853..64584e6140 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use anyhow::{anyhow, Result}; use client::{proto, Client, TypedEnvelope, User, UserStore}; use collections::HashSet; +use futures::{future::Shared, FutureExt}; use postage::watch; use gpui::{ @@ -33,6 +34,7 @@ pub struct IncomingCall { /// Singleton global maintaining the user's participation in a room across workspaces. pub struct ActiveCall { room: Option<(ModelHandle, Vec)>, + pending_room_creation: Option, Arc>>>>, location: Option>, pending_invites: HashSet, incoming_call: ( @@ -56,6 +58,7 @@ impl ActiveCall { ) -> Self { Self { room: None, + pending_room_creation: None, location: None, pending_invites: Default::default(), incoming_call: watch::channel(), @@ -124,45 +127,74 @@ impl ActiveCall { initial_project: Option>, cx: &mut ModelContext, ) -> Task> { - let client = self.client.clone(); - let user_store = self.user_store.clone(); if !self.pending_invites.insert(called_user_id) { return Task::ready(Err(anyhow!("user was already invited"))); } - cx.notify(); - cx.spawn(|this, mut cx| async move { - let invite = async { - if let Some(room) = this.read_with(&cx, |this, _| this.room().cloned()) { - let initial_project_id = if let Some(initial_project) = initial_project { - Some( - room.update(&mut cx, |room, cx| { - room.share_project(initial_project, cx) - }) + + let room = if let Some(room) = self.room().cloned() { + Some(Task::ready(Ok(room)).shared()) + } else { + self.pending_room_creation.clone() + }; + + let invite = if let Some(room) = room { + cx.spawn_weak(|_, mut cx| async move { + let room = room.await.map_err(|err| anyhow!("{:?}", err))?; + + let initial_project_id = if let Some(initial_project) = initial_project { + Some( + room.update(&mut cx, |room, cx| room.share_project(initial_project, cx)) .await?, - ) - } else { - None - }; - - room.update(&mut cx, |room, cx| { - room.call(called_user_id, initial_project_id, cx) - }) - .await?; + ) } else { - let room = cx - .update(|cx| { - Room::create(called_user_id, initial_project, client, user_store, cx) - }) - .await?; - - this.update(&mut cx, |this, cx| this.set_room(Some(room), cx)) - .await?; + None }; - Ok(()) - }; + room.update(&mut cx, |room, cx| { + room.call(called_user_id, initial_project_id, cx) + }) + .await?; + anyhow::Ok(()) + }) + } else { + let client = self.client.clone(); + let user_store = self.user_store.clone(); + let room = cx + .spawn(|this, mut cx| async move { + let create_room = async { + let room = cx + .update(|cx| { + Room::create( + called_user_id, + initial_project, + client, + user_store, + cx, + ) + }) + .await?; + + this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx)) + .await?; + + anyhow::Ok(room) + }; + + let room = create_room.await; + this.update(&mut cx, |this, _| this.pending_room_creation = None); + room.map_err(Arc::new) + }) + .shared(); + self.pending_room_creation = Some(room.clone()); + cx.foreground().spawn(async move { + room.await.map_err(|err| anyhow!("{:?}", err))?; + anyhow::Ok(()) + }) + }; + + cx.spawn(|this, mut cx| async move { let result = invite.await; this.update(&mut cx, |this, cx| { this.pending_invites.remove(&called_user_id); diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 55f888d11d..ff9872f31f 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -334,6 +334,134 @@ async fn test_basic_calls( ); } +#[gpui::test(iterations = 10)] +async fn test_calling_multiple_users_simultaneously( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, + cx_c: &mut TestAppContext, + cx_d: &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; + let client_c = server.create_client(cx_c, "user_c").await; + let client_d = server.create_client(cx_d, "user_d").await; + server + .make_contacts(&mut [ + (&client_a, cx_a), + (&client_b, cx_b), + (&client_c, cx_c), + (&client_d, cx_d), + ]) + .await; + + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + let active_call_c = cx_c.read(ActiveCall::global); + let active_call_d = cx_d.read(ActiveCall::global); + + // Simultaneously call user B and user C from client A. + let b_invite = active_call_a.update(cx_a, |call, cx| { + call.invite(client_b.user_id().unwrap(), None, cx) + }); + let c_invite = active_call_a.update(cx_a, |call, cx| { + call.invite(client_c.user_id().unwrap(), None, cx) + }); + b_invite.await.unwrap(); + c_invite.await.unwrap(); + + let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone()); + deterministic.run_until_parked(); + assert_eq!( + room_participants(&room_a, cx_a), + RoomParticipants { + remote: Default::default(), + pending: vec!["user_b".to_string(), "user_c".to_string()] + } + ); + + // Call client D from client A. + active_call_a + .update(cx_a, |call, cx| { + call.invite(client_d.user_id().unwrap(), None, cx) + }) + .await + .unwrap(); + deterministic.run_until_parked(); + assert_eq!( + room_participants(&room_a, cx_a), + RoomParticipants { + remote: Default::default(), + pending: vec![ + "user_b".to_string(), + "user_c".to_string(), + "user_d".to_string() + ] + } + ); + + // Accept the call on all clients simultaneously. + let accept_b = active_call_b.update(cx_b, |call, cx| call.accept_incoming(cx)); + let accept_c = active_call_c.update(cx_c, |call, cx| call.accept_incoming(cx)); + let accept_d = active_call_d.update(cx_d, |call, cx| call.accept_incoming(cx)); + accept_b.await.unwrap(); + accept_c.await.unwrap(); + accept_d.await.unwrap(); + + deterministic.run_until_parked(); + + let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone()); + let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone()); + let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone()); + assert_eq!( + room_participants(&room_a, cx_a), + RoomParticipants { + remote: vec![ + "user_b".to_string(), + "user_c".to_string(), + "user_d".to_string(), + ], + pending: Default::default() + } + ); + assert_eq!( + room_participants(&room_b, cx_b), + RoomParticipants { + remote: vec![ + "user_a".to_string(), + "user_c".to_string(), + "user_d".to_string(), + ], + pending: Default::default() + } + ); + assert_eq!( + room_participants(&room_c, cx_c), + RoomParticipants { + remote: vec![ + "user_a".to_string(), + "user_b".to_string(), + "user_d".to_string(), + ], + pending: Default::default() + } + ); + assert_eq!( + room_participants(&room_d, cx_d), + RoomParticipants { + remote: vec![ + "user_a".to_string(), + "user_b".to_string(), + "user_c".to_string(), + ], + pending: Default::default() + } + ); +} + #[gpui::test(iterations = 10)] async fn test_room_uniqueness( deterministic: Arc, From 015b8db1c307fcfe735fa77455ce5affaac1645f Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Tue, 14 Feb 2023 15:14:15 +0200 Subject: [PATCH 130/180] Introduce reveal_path in Platform And implement it for MacPlatform, and instead of using an external process to run `open`, use the NSWorkspace selectFile instance method. --- crates/gpui/src/platform.rs | 1 + crates/gpui/src/platform/mac/platform.rs | 13 +++++++++++++ crates/gpui/src/platform/test.rs | 2 ++ crates/project_panel/src/project_panel.rs | 5 +++-- crates/util/src/lib.rs | 12 ------------ 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 57e8f89539..4753450110 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -65,6 +65,7 @@ pub trait Platform: Send + Sync { fn write_to_clipboard(&self, item: ClipboardItem); fn read_from_clipboard(&self) -> Option; fn open_url(&self, url: &str); + fn reveal_path(&self, path: &Path); fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>; fn read_credentials(&self, url: &str) -> Result)>>; diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 5d13227585..ec15af3bd8 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -599,6 +599,19 @@ impl platform::Platform for MacPlatform { } } + fn reveal_path(&self, path: &Path) { + unsafe { + let full_path = ns_string(path.to_str().unwrap_or("")); + let root_full_path = ns_string(""); + let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; + msg_send![ + workspace, + selectFile: full_path + inFileViewerRootedAtPath: root_full_path + ] + } + } + fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { let url = CFString::from(url); let username = CFString::from(username); diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index aa73aebc90..d0cabd8bc1 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -173,6 +173,8 @@ impl super::Platform for Platform { fn open_url(&self, _: &str) {} + fn reveal_path(&self, _: &Path) {} + fn write_credentials(&self, _: &str, _: &str, _: &[u8]) -> Result<()> { Ok(()) } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a5ca4077f7..f2330dfd4f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -791,8 +791,9 @@ impl ProjectPanel { } fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext) { - if let Some((_worktree, entry)) = self.selected_entry(cx) { - util::reveal_in_finder(&entry.path); + if let Some((worktree, entry)) = self.selected_entry(cx) { + cx.platform() + .reveal_path(&worktree.abs_path().join(&entry.path)); } } diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index c3376f2e78..65af53f8c5 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -65,18 +65,6 @@ pub fn open>(path: P) { } } -pub fn reveal_in_finder>(path: P) { - let path_to_reveal = path.as_ref().to_string_lossy(); - #[cfg(target_os = "macos")] - { - std::process::Command::new("open") - .arg("-R") // To reveal in Finder instead of opening the file - .arg(path_to_reveal.as_ref()) - .spawn() - .log_err(); - } -} - pub fn post_inc + AddAssign + Copy>(value: &mut T) -> T { let prev = *value; *value += T::from(1); From 888fcb5b1ba3f5b30ee0e533a72c7a9874059403 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 14 Feb 2023 14:36:18 +0100 Subject: [PATCH 131/180] Surround with bracket only when opening brace is 1 character long --- crates/editor/src/editor.rs | 6 +-- crates/editor/src/editor_tests.rs | 81 ++++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c649fed7ce..8ab02db130 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1816,9 +1816,9 @@ impl Editor { } } } - // If an opening bracket is typed while text is selected, then - // surround that text with the bracket pair. - else if is_bracket_pair_start { + // If an opening bracket is 1 character long and is typed while + // text is selected, then surround that text with the bracket pair. + else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 { edits.push((selection.start..selection.start, text.clone())); edits.push(( selection.end..selection.end, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index ff59c5dc14..e1590a22ba 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3452,12 +3452,20 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) { cx.update(|cx| cx.set_global(Settings::test(cx))); let language = Arc::new(Language::new( LanguageConfig { - brackets: vec![BracketPair { - start: "{".to_string(), - end: "}".to_string(), - close: true, - newline: true, - }], + brackets: vec![ + BracketPair { + start: "{".to_string(), + end: "}".to_string(), + close: true, + newline: true, + }, + BracketPair { + start: "/* ".to_string(), + end: "*/".to_string(), + close: true, + ..Default::default() + }, + ], ..Default::default() }, Some(tree_sitter_rust::language()), @@ -3526,6 +3534,67 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) { DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1) ] ); + + // Ensure inserting the first character of a multi-byte bracket pair + // doesn't surround the selections with the bracket. + view.handle_input("/", cx); + assert_eq!( + view.text(cx), + " + / + / + / + " + .unindent() + ); + assert_eq!( + view.selections.display_ranges(cx), + [ + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), + DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), + DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1) + ] + ); + + view.undo(&Undo, cx); + assert_eq!( + view.text(cx), + " + a + b + c + " + .unindent() + ); + assert_eq!( + view.selections.display_ranges(cx), + [ + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1), + DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1) + ] + ); + + // Ensure inserting the last character of a multi-byte bracket pair + // doesn't surround the selections with the bracket. + view.handle_input("*", cx); + assert_eq!( + view.text(cx), + " + * + * + * + " + .unindent() + ); + assert_eq!( + view.selections.display_ranges(cx), + [ + DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1), + DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1), + DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1) + ] + ); }); } From 4a2b7e48209ab61ae7be187cfdc765eaaf5dc11d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 14 Feb 2023 15:16:06 +0100 Subject: [PATCH 132/180] Use `alt-z` to toggle soft wrap in active editor When there isn't a default soft-wrapping for the active editor, we will soft wrap at the editor width. This is consistent with Visual Studio Code. --- assets/keymaps/default.json | 1 + crates/editor/src/editor.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 17168f679f..1d324fe582 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -164,6 +164,7 @@ "bindings": { "enter": "editor::Newline", "cmd-enter": "editor::NewlineBelow", + "alt-z": "editor::ToggleSoftWrap", "cmd-f": [ "buffer_search::Deploy", { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8ab02db130..db357f2b83 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -236,6 +236,7 @@ actions!( RestartLanguageServer, Hover, Format, + ToggleSoftWrap ] ); @@ -346,6 +347,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Editor::toggle_code_actions); cx.add_action(Editor::open_excerpts); cx.add_action(Editor::jump); + cx.add_action(Editor::toggle_soft_wrap); cx.add_async_action(Editor::format); cx.add_action(Editor::restart_language_server); cx.add_action(Editor::show_character_palette); @@ -5812,6 +5814,19 @@ impl Editor { .update(cx, |map, cx| map.set_wrap_width(width, cx)) } + pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext) { + if self.soft_wrap_mode_override.is_some() { + self.soft_wrap_mode_override.take(); + } else { + let soft_wrap = match self.soft_wrap_mode(cx) { + SoftWrap::None => settings::SoftWrap::EditorWidth, + SoftWrap::EditorWidth | SoftWrap::Column(_) => settings::SoftWrap::None, + }; + self.soft_wrap_mode_override = Some(soft_wrap); + } + cx.notify(); + } + pub fn highlight_rows(&mut self, rows: Option>) { self.highlighted_rows = rows; } From 1012cea4aff235ba465cc94f54753b0f7d1f57bd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 14 Feb 2023 15:22:00 +0100 Subject: [PATCH 133/180] Soft wrap at editor width if it's narrower than preferred line length --- crates/editor/src/element.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index bce63ca0cf..9d8922fab5 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1534,15 +1534,14 @@ impl Element for EditorElement { let snapshot = self.update_view(cx.app, |view, cx| { view.set_visible_line_count(size.y() / line_height); + let editor_width = text_width - gutter_margin - overscroll.x() - em_width; let wrap_width = match view.soft_wrap_mode(cx) { - SoftWrap::None => Some((MAX_LINE_LEN / 2) as f32 * em_advance), - SoftWrap::EditorWidth => { - Some(text_width - gutter_margin - overscroll.x() - em_width) - } - SoftWrap::Column(column) => Some(column as f32 * em_advance), + SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance, + SoftWrap::EditorWidth => editor_width, + SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance), }; - if view.set_wrap_width(wrap_width, cx) { + if view.set_wrap_width(Some(wrap_width), cx) { view.snapshot(cx) } else { snapshot From 3a7ac9c0ff64181758c4bcce46db363da945c7e9 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 14 Feb 2023 12:39:29 -0500 Subject: [PATCH 134/180] Focus pane when clicking on tab bar background --- crates/workspace/src/pane.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index cc1d8f0ad2..ccd2dd38e1 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1420,23 +1420,40 @@ impl View for Pane { Stack::new() .with_child( MouseEventHandler::::new(0, cx, |_, cx| { + let active_item_index = self.active_item_index; + if let Some(active_item) = self.active_item() { Flex::column() .with_child({ + let theme = cx.global::().theme.clone(); + + let mut stack = Stack::new(); + + enum TabBarEventHandler {} + stack.add_child( + MouseEventHandler::::new(0, cx, |_, _| { + Flex::row() + .contained() + .with_style(theme.workspace.tab_bar.container) + .boxed() + }) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(ActivateItem(active_item_index)); + }) + .boxed(), + ); + let mut tab_row = Flex::row() .with_child(self.render_tabs(cx).flex(1., true).named("tabs")); - // Render pane buttons - let theme = cx.global::().theme.clone(); if self.is_active { tab_row.add_child(self.render_tab_bar_buttons(&theme, cx)) } - tab_row + stack.add_child(tab_row.boxed()); + stack .constrained() .with_height(theme.workspace.tab_bar.height) - .contained() - .with_style(theme.workspace.tab_bar.container) .flex(1., false) .named("tab bar") }) From 8e9d95fefc52b680bfda9795c881a8ec013d871c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 14 Feb 2023 09:54:31 -0800 Subject: [PATCH 135/180] Fix error where terminal search matches wouldn't be updated when clearing --- crates/terminal/src/terminal.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index dd5c5fb3b0..65edf1b692 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -643,6 +643,8 @@ impl Terminal { if (new_cursor.line.0 as usize) < term.screen_lines() - 1 { term.grid_mut().reset_region((new_cursor.line + 1)..); } + + cx.emit(Event::Wakeup); } InternalEvent::Scroll(scroll) => { term.scroll_display(*scroll); From c2de0f6b5ebabc2c587bbbf12c65a75dc2fe08ff Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 14 Feb 2023 18:05:42 -0800 Subject: [PATCH 136/180] Add auto update setting --- assets/settings/default.json | 2 ++ crates/auto_update/src/auto_update.rs | 19 ++++++++++++++++++- crates/settings/src/settings.rs | 6 ++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index bab998f72f..a4b95603e8 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -90,6 +90,8 @@ // Send anonymized usage data like what languages you're using Zed with. "metrics": true }, + // Automatically update Zed + "auto_update": true, // Git gutter behavior configuration. "git": { // Control whether the git gutter is shown. May take 2 values: diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 19f4652005..4272d7b1af 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -9,6 +9,7 @@ use gpui::{ MutableAppContext, Task, WeakViewHandle, }; use serde::Deserialize; +use settings::Settings; use smol::{fs::File, io::AsyncReadExt, process::Command}; use std::{ffi::OsString, sync::Arc, time::Duration}; use update_notification::UpdateNotification; @@ -53,7 +54,23 @@ pub fn init(http_client: Arc, server_url: String, cx: &mut Mutab let server_url = server_url; let auto_updater = cx.add_model(|cx| { let updater = AutoUpdater::new(version, http_client, server_url.clone()); - updater.start_polling(cx).detach(); + + let mut update_subscription = cx + .global::() + .auto_update + .then(|| updater.start_polling(cx)); + + cx.observe_global::(move |updater, cx| { + if cx.global::().auto_update { + if update_subscription.is_none() { + *(&mut update_subscription) = Some(updater.start_polling(cx)) + } + } else { + (&mut update_subscription).take(); + } + }) + .detach(); + updater }); cx.set_global(Some(auto_updater)); diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 9c4b1f8044..21939b26b0 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -53,6 +53,7 @@ pub struct Settings { pub theme: Arc, pub telemetry_defaults: TelemetrySettings, pub telemetry_overrides: TelemetrySettings, + pub auto_update: bool, } #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -312,6 +313,8 @@ pub struct SettingsFileContent { pub theme: Option, #[serde(default)] pub telemetry: TelemetrySettings, + #[serde(default)] + pub auto_update: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -375,6 +378,7 @@ impl Settings { theme: themes.get(&defaults.theme.unwrap()).unwrap(), telemetry_defaults: defaults.telemetry, telemetry_overrides: Default::default(), + auto_update: defaults.auto_update.unwrap(), } } @@ -427,6 +431,7 @@ impl Settings { self.language_overrides = data.languages; self.telemetry_overrides = data.telemetry; self.lsp = data.lsp; + merge(&mut self.auto_update, data.auto_update); } pub fn with_language_defaults( @@ -573,6 +578,7 @@ impl Settings { metrics: Some(true), }, telemetry_overrides: Default::default(), + auto_update: true, } } From 8db131a3a1bb33ccdf7ab22d437bf8673a9ba400 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 15 Feb 2023 13:12:45 +0100 Subject: [PATCH 137/180] Score matches case-sensitively when query contains an uppercase char --- crates/editor/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index db357f2b83..6df0456d2f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -812,7 +812,7 @@ impl CompletionsMenu { fuzzy::match_strings( &self.match_candidates, query, - false, + query.chars().any(|c| c.is_uppercase()), 100, &Default::default(), executor, From 2482a1a9cee549b33b7c60c885eb99a22f9e830c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 15 Feb 2023 14:48:36 +0100 Subject: [PATCH 138/180] Add timeout to HTTP requests during `npm info` and `npm fetch` --- crates/zed/src/languages/installation.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/zed/src/languages/installation.rs b/crates/zed/src/languages/installation.rs index c5aff17e56..df28177f0b 100644 --- a/crates/zed/src/languages/installation.rs +++ b/crates/zed/src/languages/installation.rs @@ -39,6 +39,7 @@ pub async fn npm_package_latest_version(name: &str) -> Result { let output = smol::process::Command::new("npm") .args(["-fetch-retry-mintimeout", "2000"]) .args(["-fetch-retry-maxtimeout", "5000"]) + .args(["-fetch-timeout", "5000"]) .args(["info", name, "--json"]) .output() .await @@ -64,6 +65,7 @@ pub async fn npm_install_packages( let output = smol::process::Command::new("npm") .args(["-fetch-retry-mintimeout", "2000"]) .args(["-fetch-retry-maxtimeout", "5000"]) + .args(["-fetch-timeout", "5000"]) .arg("install") .arg("--prefix") .arg(directory) From 7a667f390bbc9a1fb15b255eebf0e4764f4f749c Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 15 Feb 2023 15:58:57 +0200 Subject: [PATCH 139/180] Use open_url from the platform module And remove the open function from the `util` crate. --- crates/terminal/src/terminal.rs | 4 ++-- crates/util/src/lib.rs | 12 ------------ crates/workspace/src/notifications.rs | 7 ++----- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index a4f913ec77..e92d437297 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -731,7 +731,7 @@ impl Terminal { if let Some((url, url_match)) = found_url { if *open { - util::open(&url); + cx.platform().open_url(url.as_str()); } else { self.update_hyperlink(prev_hyperlink, url, url_match); } @@ -1072,7 +1072,7 @@ impl Terminal { if self.selection_phase == SelectionPhase::Ended { let mouse_cell_index = content_index_for_mouse(position, &self.last_content); if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() { - util::open(link.uri()); + cx.platform().open_url(link.uri()); } else { self.events .push_back(InternalEvent::FindHyperlink(position, true)); diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index 65af53f8c5..ea8fdee2a8 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -9,7 +9,6 @@ use rand::{seq::SliceRandom, Rng}; use std::{ cmp::Ordering, ops::AddAssign, - path::Path, pin::Pin, task::{Context, Poll}, }; @@ -54,17 +53,6 @@ pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { } } -pub fn open>(path: P) { - let path_to_open = path.as_ref().to_string_lossy(); - #[cfg(target_os = "macos")] - { - std::process::Command::new("open") - .arg(path_to_open.as_ref()) - .spawn() - .log_err(); - } -} - pub fn post_inc + AddAssign + Copy>(value: &mut T) -> T { let prev = *value; *value += T::from(1); diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 76ee5b1c40..141a345382 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -146,11 +146,8 @@ pub mod simple_message_notification { pub fn init(cx: &mut MutableAppContext) { cx.add_action(MessageNotification::dismiss); cx.add_action( - |_workspace: &mut Workspace, open_action: &OsOpen, _cx: &mut ViewContext| { - #[cfg(target_os = "macos")] - { - util::open(&open_action.0); - } + |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext| { + cx.platform().open_url(open_action.0.as_str()); }, ) } From 5df50e2fc97a92f875c7174a760da5914be03bd5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 15 Feb 2023 14:52:13 +0100 Subject: [PATCH 140/180] Add timeouts to HTTP client --- crates/client/src/http.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/client/src/http.rs b/crates/client/src/http.rs index 5139bb8d03..0757cebf3a 100644 --- a/crates/client/src/http.rs +++ b/crates/client/src/http.rs @@ -9,7 +9,7 @@ pub use isahc::{ Error, }; use smol::future::FutureExt; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; pub use url::Url; pub type Request = isahc::Request; @@ -41,7 +41,13 @@ pub trait HttpClient: Send + Sync { } pub fn client() -> Arc { - Arc::new(isahc::HttpClient::builder().build().unwrap()) + Arc::new( + isahc::HttpClient::builder() + .connect_timeout(Duration::from_secs(5)) + .low_speed_timeout(100, Duration::from_secs(5)) + .build() + .unwrap(), + ) } impl HttpClient for isahc::HttpClient { From 5fbc9736e58bc6a293352e6158f3e3c75d1c040e Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 15 Feb 2023 14:00:49 -0500 Subject: [PATCH 141/180] Add option to advance cursor downward when toggling comment Co-Authored-By: Julia <30666851+ForLoveOfCats@users.noreply.github.com> --- assets/keymaps/default.json | 7 +- crates/editor/src/editor.rs | 40 +++++++- crates/editor/src/editor_tests.rs | 149 ++++++++++++++++++++++++++++-- crates/zed/src/menus.rs | 2 +- 4 files changed, 185 insertions(+), 13 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 1d324fe582..58a58e07b1 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -228,7 +228,12 @@ "replace_newest": true } ], - "cmd-/": "editor::ToggleComments", + "cmd-/": [ + "editor::ToggleComments", + { + "advance_downwards": false + } + ], "alt-up": "editor::SelectLargerSyntaxNode", "alt-down": "editor::SelectSmallerSyntaxNode", "cmd-u": "editor::UndoSelection", diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6df0456d2f..f32f87a42d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -154,6 +154,12 @@ pub struct ConfirmCodeAction { pub item_ix: Option, } +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct ToggleComments { + #[serde(default)] + pub advance_downwards: bool, +} + actions!( editor, [ @@ -216,7 +222,6 @@ actions!( AddSelectionBelow, Tab, TabPrev, - ToggleComments, ShowCharacterPalette, SelectLargerSyntaxNode, SelectSmallerSyntaxNode, @@ -251,6 +256,7 @@ impl_actions!( MovePageDown, ConfirmCompletion, ConfirmCodeAction, + ToggleComments, ] ); @@ -3804,7 +3810,7 @@ impl Editor { } } - if matches!(self.mode, EditorMode::SingleLine) { + if self.mode == EditorMode::SingleLine { cx.propagate_action(); return; } @@ -4466,7 +4472,7 @@ impl Editor { } } - pub fn toggle_comments(&mut self, _: &ToggleComments, cx: &mut ViewContext) { + pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext) { self.transact(cx, |this, cx| { let mut selections = this.selections.all::(cx); let mut edits = Vec::new(); @@ -4685,6 +4691,34 @@ impl Editor { drop(snapshot); this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + + let selections = this.selections.all::(cx); + let selections_on_single_row = selections.windows(2).all(|selections| { + selections[0].start.row == selections[1].start.row + && selections[0].end.row == selections[1].end.row + && selections[0].start.row == selections[0].end.row + }); + let selections_selecting = selections + .iter() + .any(|selection| selection.start != selection.end); + let advance_downwards = action.advance_downwards + && selections_on_single_row + && !selections_selecting + && this.mode != EditorMode::SingleLine; + + if advance_downwards { + let snapshot = this.buffer.read(cx).snapshot(cx); + + this.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_cursors_with(|display_snapshot, display_point, _| { + let mut point = display_point.to_point(display_snapshot); + point.row += 1; + point = snapshot.clip_point(point, Bias::Left); + let display_point = point.to_display_point(display_snapshot); + (display_point, SelectionGoal::Column(display_point.column())) + }) + }); + } }); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index e1590a22ba..02216fba5c 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -4451,7 +4451,7 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6), ]) }); - editor.toggle_comments(&ToggleComments, cx); + editor.toggle_comments(&ToggleComments::default(), cx); assert_eq!( editor.text(cx), " @@ -4469,7 +4469,7 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { editor.change_selections(None, cx, |s| { s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)]) }); - editor.toggle_comments(&ToggleComments, cx); + editor.toggle_comments(&ToggleComments::default(), cx); assert_eq!( editor.text(cx), " @@ -4486,7 +4486,7 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { editor.change_selections(None, cx, |s| { s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)]) }); - editor.toggle_comments(&ToggleComments, cx); + editor.toggle_comments(&ToggleComments::default(), cx); assert_eq!( editor.text(cx), " @@ -4501,6 +4501,139 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { }); } +#[gpui::test] +async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) { + let mut cx = EditorTestContext::new(cx); + cx.update(|cx| cx.set_global(Settings::test(cx))); + + let language = Arc::new(Language::new( + LanguageConfig { + line_comment: Some("// ".into()), + ..Default::default() + }, + Some(tree_sitter_rust::language()), + )); + + let registry = Arc::new(LanguageRegistry::test()); + registry.add(language.clone()); + + cx.update_buffer(|buffer, cx| { + buffer.set_language_registry(registry); + buffer.set_language(Some(language), cx); + }); + + let toggle_comments = &ToggleComments { + advance_downwards: true, + }; + + // Single cursor on one line -> advance + // Cursor moves horizontally 3 characters as well on non-blank line + cx.set_state(indoc!( + "fn a() { + ˇdog(); + cat(); + }" + )); + cx.update_editor(|editor, cx| { + editor.toggle_comments(toggle_comments, cx); + }); + cx.assert_editor_state(indoc!( + "fn a() { + // dog(); + catˇ(); + }" + )); + + // Single selection on one line -> don't advance + cx.set_state(indoc!( + "fn a() { + «dog()ˇ»; + cat(); + }" + )); + cx.update_editor(|editor, cx| { + editor.toggle_comments(toggle_comments, cx); + }); + cx.assert_editor_state(indoc!( + "fn a() { + // «dog()ˇ»; + cat(); + }" + )); + + // Multiple cursors on one line -> advance + cx.set_state(indoc!( + "fn a() { + ˇdˇog(); + cat(); + }" + )); + cx.update_editor(|editor, cx| { + editor.toggle_comments(toggle_comments, cx); + }); + cx.assert_editor_state(indoc!( + "fn a() { + // dog(); + catˇ(ˇ); + }" + )); + + // Multiple cursors on one line, with selection -> don't advance + cx.set_state(indoc!( + "fn a() { + ˇdˇog«()ˇ»; + cat(); + }" + )); + cx.update_editor(|editor, cx| { + editor.toggle_comments(toggle_comments, cx); + }); + cx.assert_editor_state(indoc!( + "fn a() { + // ˇdˇog«()ˇ»; + cat(); + }" + )); + + // Single cursor on one line -> advance + // Cursor moves to column 0 on blank line + cx.set_state(indoc!( + "fn a() { + ˇdog(); + + cat(); + }" + )); + cx.update_editor(|editor, cx| { + editor.toggle_comments(toggle_comments, cx); + }); + cx.assert_editor_state(indoc!( + "fn a() { + // dog(); + ˇ + cat(); + }" + )); + + // Single cursor on one line -> advance + // Cursor starts and ends at column 0 + cx.set_state(indoc!( + "fn a() { + ˇ dog(); + cat(); + }" + )); + cx.update_editor(|editor, cx| { + editor.toggle_comments(toggle_comments, cx); + }); + cx.assert_editor_state(indoc!( + "fn a() { + // dog(); + ˇ cat(); + }" + )); +} + #[gpui::test] async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { let mut cx = EditorTestContext::new(cx); @@ -4551,7 +4684,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { "# .unindent(), ); - cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx)); + cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx)); cx.assert_editor_state( &r#" @@ -4560,7 +4693,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { "# .unindent(), ); - cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx)); + cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx)); cx.assert_editor_state( &r#"

A

ˇ @@ -4582,7 +4715,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { .unindent(), ); - cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx)); + cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx)); cx.assert_editor_state( &r#" diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 09525f14e5..52ca7d2324 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -146,7 +146,7 @@ pub fn menus() -> Vec> { MenuItem::Separator, MenuItem::Action { name: "Toggle Line Comment", - action: Box::new(editor::ToggleComments), + action: Box::new(editor::ToggleComments::default()), }, MenuItem::Action { name: "Emoji & Symbols", From bcf7a322847631607ff2821e5e6ee118dbfac5a7 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 15 Feb 2023 14:10:23 -0500 Subject: [PATCH 142/180] Update pull_request_template.md --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1302b197bd..6826566e71 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,6 @@ ## Description of feature or change -## Link to related issues from zed or insiders +## Link to related issues from zed or community ## Before Merging From afb375f9097a913eb5b4a633ff35f594daa8a3fb Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 15 Feb 2023 14:57:51 -0500 Subject: [PATCH 143/180] v0.75.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82e9197631..12b4c6fe83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8344,7 +8344,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.74.0" +version = "0.75.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 8d92880c75..ed40aa7391 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.74.0" +version = "0.75.0" publish = false [lib] From e56dfd9177d5bc7ac84b6eee26fca8b1b985f6bb Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 15 Feb 2023 15:55:55 -0500 Subject: [PATCH 144/180] Tell OS about window title --- crates/gpui/src/platform/mac/window.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index e67aa25e13..50e0de216c 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -19,7 +19,7 @@ use cocoa::{ appkit::{ CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior, - NSWindowStyleMask, + NSWindowStyleMask, NSWindowTitleVisibility, }, base::{id, nil}, foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger}, @@ -536,6 +536,7 @@ impl Window { .map_or(true, |titlebar| titlebar.appears_transparent) { native_window.setTitlebarAppearsTransparent_(YES); + native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden); } native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable); @@ -734,7 +735,8 @@ impl platform::Window for Window { let app = NSApplication::sharedApplication(nil); let window = self.0.borrow().native_window; let title = ns_string(title); - msg_send![app, changeWindowsItem:window title:title filename:false] + let _: () = msg_send![app, changeWindowsItem:window title:title filename:false]; + let _: () = msg_send![window, setTitle: title]; } } From df0715e7c9df37309bb8ab8d9b80e674279b1f3e Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 15 Feb 2023 15:56:16 -0500 Subject: [PATCH 145/180] Indicate in native window title if project is shared or remote --- crates/workspace/src/workspace.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7ca3cc7d22..8d81ae7f2e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -491,19 +491,24 @@ impl Workspace { cx.subscribe(&project, move |this, _, event, cx| { match event { project::Event::RemoteIdChanged(remote_id) => { + this.update_window_title(cx); this.project_remote_id_changed(*remote_id, cx); } + project::Event::CollaboratorLeft(peer_id) => { this.collaborator_left(*peer_id, cx); } + project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => { this.update_window_title(cx); this.serialize_workspace(cx); } + project::Event::DisconnectedFromHost => { this.update_window_edited(cx); cx.blur(); } + _ => {} } cx.notify() @@ -1841,8 +1846,9 @@ impl Workspace { } fn update_window_title(&mut self, cx: &mut ViewContext) { - let mut title = String::new(); let project = self.project().read(cx); + let mut title = String::new(); + if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) { let filename = path .path @@ -1856,20 +1862,30 @@ impl Workspace { .root_name(), )) }); + if let Some(filename) = filename { title.push_str(filename.as_ref()); title.push_str(" — "); } } + for (i, name) in project.worktree_root_names(cx).enumerate() { if i > 0 { title.push_str(", "); } title.push_str(name); } + if title.is_empty() { title = "empty project".to_string(); } + + if project.is_remote() { + title.push_str(" ↙"); + } else if project.is_shared() { + title.push_str(" ↗"); + } + cx.set_window_title(&title); } From 7037842bef0434dd24ae02c7414e7d0335f6ec70 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 15 Feb 2023 13:55:25 -0800 Subject: [PATCH 146/180] Put back shift-escape binding for FocusDock action --- assets/keymaps/default.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 1d324fe582..efa98299bc 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -431,6 +431,12 @@ "cmd-enter": "project_search::SearchInNew" } }, + { + "context": "Workspace", + "bindings": { + "shift-escape": "dock::FocusDock" + } + }, { "bindings": { "cmd-shift-k cmd-shift-right": "dock::AnchorDockRight", From 0ba051a7545d1e7486659a315e41b818a4e75acc Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 15 Feb 2023 14:04:16 -0800 Subject: [PATCH 147/180] use more predictable rules for selecting which bracket to jump to and where --- Cargo.lock | 2 + crates/editor/Cargo.toml | 2 + crates/editor/src/editor.rs | 65 +++-- crates/editor/src/editor_tests.rs | 48 ++++ .../editor/src/highlight_matching_bracket.rs | 2 +- crates/editor/src/multi_buffer.rs | 151 +++++----- crates/editor/src/selections_collection.rs | 25 ++ .../src/test/editor_lsp_test_context.rs | 30 +- crates/editor/src/test/editor_test_context.rs | 5 +- crates/gpui/src/app/ref_counts.rs | 11 +- crates/gpui/src/app/test_app_context.rs | 5 + crates/language/Cargo.toml | 1 + crates/language/src/buffer.rs | 51 ++-- crates/language/src/buffer_tests.rs | 271 ++++++++++++------ crates/vim/src/motion.rs | 5 +- 15 files changed, 457 insertions(+), 217 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82e9197631..c748774237 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1956,6 +1956,7 @@ dependencies = [ "tree-sitter-html", "tree-sitter-javascript", "tree-sitter-rust", + "tree-sitter-typescript", "unindent", "util", "workspace", @@ -3249,6 +3250,7 @@ dependencies = [ "fuzzy", "git", "gpui", + "indoc", "lazy_static", "log", "lsp", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 26dd371041..7f1bec1d85 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -58,6 +58,7 @@ smol = "1.2" tree-sitter-rust = { version = "*", optional = true } tree-sitter-html = { version = "*", optional = true } tree-sitter-javascript = { version = "*", optional = true } +tree-sitter-typescript = { version = "*", optional = true } [dev-dependencies] text = { path = "../text", features = ["test-support"] } @@ -75,4 +76,5 @@ unindent = "0.1.7" tree-sitter = "0.20" tree-sitter-rust = "0.20" tree-sitter-html = "0.19" +tree-sitter-typescript = "0.20.1" tree-sitter-javascript = "0.20" diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c649fed7ce..eafcfd694a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4752,27 +4752,52 @@ impl Editor { _: &MoveToEnclosingBracket, cx: &mut ViewContext, ) { - let buffer = self.buffer.read(cx).snapshot(cx); - let mut selections = self.selections.all::(cx); - for selection in &mut selections { - if let Some((open_range, close_range)) = - buffer.enclosing_bracket_ranges(selection.start..selection.end) - { - let close_range = close_range.to_inclusive(); - let destination = if close_range.contains(&selection.start) - && close_range.contains(&selection.end) - { - open_range.end - } else { - *close_range.start() - }; - selection.start = destination; - selection.end = destination; - } - } - self.change_selections(Some(Autoscroll::fit()), cx, |s| { - s.select(selections); + s.move_offsets_with(|snapshot, selection| { + let Some(enclosing_bracket_ranges) = snapshot.enclosing_bracket_ranges(selection.start..selection.end) else { return; }; + + let mut best_length = usize::MAX; + let mut best_inside = false; + let mut best_in_bracket_range = false; + let mut best_destination = None; + for (open, close) in enclosing_bracket_ranges { + let close = close.to_inclusive(); + let length = close.end() - open.start; + let inside = selection.start >= open.end && selection.end <= *close.start(); + let in_bracket_range = open.to_inclusive().contains(&selection.head()) || close.contains(&selection.head()); + + // If best is next to a bracket and current isn't, skip + if !in_bracket_range && best_in_bracket_range { + continue; + } + + // Prefer smaller lengths unless best is inside and current isn't + if length > best_length && (best_inside || !inside) { + continue; + } + + best_length = length; + best_inside = inside; + best_in_bracket_range = in_bracket_range; + best_destination = Some(if close.contains(&selection.start) && close.contains(&selection.end) { + if inside { + open.end + } else { + open.start + } + } else { + if inside { + *close.start() + } else { + *close.end() + } + }); + } + + if let Some(destination) = best_destination { + selection.collapse_to(destination, SelectionGoal::None); + } + }) }); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index ff59c5dc14..5b023bfe82 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5459,6 +5459,54 @@ fn test_split_words() { assert_eq!(split("helloworld"), &["helloworld"]); } +#[gpui::test] +async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) { + let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await; + let mut assert = |before, after| { + let _state_context = cx.set_state(before); + cx.update_editor(|editor, cx| { + editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx) + }); + cx.assert_editor_state(after); + }; + + // Outside bracket jumps to outside of matching bracket + assert("console.logˇ(var);", "console.log(var)ˇ;"); + assert("console.log(var)ˇ;", "console.logˇ(var);"); + + // Inside bracket jumps to inside of matching bracket + assert("console.log(ˇvar);", "console.log(varˇ);"); + assert("console.log(varˇ);", "console.log(ˇvar);"); + + // When outside a bracket and inside, favor jumping to the inside bracket + assert( + "console.log('foo', [1, 2, 3]ˇ);", + "console.log(ˇ'foo', [1, 2, 3]);", + ); + assert( + "console.log(ˇ'foo', [1, 2, 3]);", + "console.log('foo', [1, 2, 3]ˇ);", + ); + + // Bias forward if two options are equally likely + assert( + "let result = curried_fun()ˇ();", + "let result = curried_fun()()ˇ;", + ); + + // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller + assert( + indoc! {" + function test() { + console.log('test')ˇ + }"}, + indoc! {" + function test() { + console.logˇ('test') + }"}, + ); +} + fn empty_range(row: usize, column: usize) -> Range { let point = DisplayPoint::new(row as u32, column as u32); point..point diff --git a/crates/editor/src/highlight_matching_bracket.rs b/crates/editor/src/highlight_matching_bracket.rs index 043b21db21..0d868d460c 100644 --- a/crates/editor/src/highlight_matching_bracket.rs +++ b/crates/editor/src/highlight_matching_bracket.rs @@ -17,7 +17,7 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon let snapshot = editor.snapshot(cx); if let Some((opening_range, closing_range)) = snapshot .buffer_snapshot - .enclosing_bracket_ranges(head..head) + .innermost_enclosing_bracket_ranges(head..head) { editor.highlight_background::( vec![ diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 7079d197f9..7b85799b31 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2621,56 +2621,71 @@ impl MultiBufferSnapshot { self.parse_count } - pub fn enclosing_bracket_ranges( + pub fn innermost_enclosing_bracket_ranges( &self, range: Range, ) -> Option<(Range, Range)> { let range = range.start.to_offset(self)..range.end.to_offset(self); - let mut cursor = self.excerpts.cursor::(); - cursor.seek(&range.start, Bias::Right, &()); - let start_excerpt = cursor.item(); + // Get the ranges of the innermost pair of brackets. + let mut result: Option<(Range, Range)> = None; - cursor.seek(&range.end, Bias::Right, &()); - let end_excerpt = cursor.item(); + let Some(enclosing_bracket_ranges) = self.enclosing_bracket_ranges(range.clone()) else { return None; }; - start_excerpt - .zip(end_excerpt) - .and_then(|(start_excerpt, end_excerpt)| { - if start_excerpt.id != end_excerpt.id { - return None; + for (open, close) in enclosing_bracket_ranges { + let len = close.end - open.start; + + if let Some((existing_open, existing_close)) = &result { + let existing_len = existing_close.end - existing_open.start; + if len > existing_len { + continue; } + } - let excerpt_buffer_start = start_excerpt - .range - .context - .start - .to_offset(&start_excerpt.buffer); - let excerpt_buffer_end = excerpt_buffer_start + start_excerpt.text_summary.len; + result = Some((open, close)); + } + + result + } + + /// Returns enclosingn bracket ranges containing the given range or returns None if the range is not contained in a single excerpt + pub fn enclosing_bracket_ranges<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> Option, Range)> + 'a> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + self.excerpt_containing(range.clone()) + .map(|(excerpt, excerpt_offset)| { + let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len; let start_in_buffer = - excerpt_buffer_start + range.start.saturating_sub(*cursor.start()); - let end_in_buffer = - excerpt_buffer_start + range.end.saturating_sub(*cursor.start()); - let (mut start_bracket_range, mut end_bracket_range) = start_excerpt - .buffer - .enclosing_bracket_ranges(start_in_buffer..end_in_buffer)?; + excerpt_buffer_start + range.start.saturating_sub(excerpt_offset); + let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset); - if start_bracket_range.start >= excerpt_buffer_start - && end_bracket_range.end <= excerpt_buffer_end - { - start_bracket_range.start = - cursor.start() + (start_bracket_range.start - excerpt_buffer_start); - start_bracket_range.end = - cursor.start() + (start_bracket_range.end - excerpt_buffer_start); - end_bracket_range.start = - cursor.start() + (end_bracket_range.start - excerpt_buffer_start); - end_bracket_range.end = - cursor.start() + (end_bracket_range.end - excerpt_buffer_start); - Some((start_bracket_range, end_bracket_range)) - } else { - None - } + excerpt + .buffer + .enclosing_bracket_ranges(start_in_buffer..end_in_buffer) + .filter_map(move |(start_bracket_range, end_bracket_range)| { + if start_bracket_range.start < excerpt_buffer_start + || end_bracket_range.end > excerpt_buffer_end + { + return None; + } + + let mut start_bracket_range = start_bracket_range.clone(); + start_bracket_range.start = + excerpt_offset + (start_bracket_range.start - excerpt_buffer_start); + start_bracket_range.end = + excerpt_offset + (start_bracket_range.end - excerpt_buffer_start); + + let mut end_bracket_range = end_bracket_range.clone(); + end_bracket_range.start = + excerpt_offset + (end_bracket_range.start - excerpt_buffer_start); + end_bracket_range.end = + excerpt_offset + (end_bracket_range.end - excerpt_buffer_start); + Some((start_bracket_range, end_bracket_range)) + }) }) } @@ -2812,40 +2827,23 @@ impl MultiBufferSnapshot { pub fn range_for_syntax_ancestor(&self, range: Range) -> Option> { let range = range.start.to_offset(self)..range.end.to_offset(self); - let mut cursor = self.excerpts.cursor::(); - cursor.seek(&range.start, Bias::Right, &()); - let start_excerpt = cursor.item(); - - cursor.seek(&range.end, Bias::Right, &()); - let end_excerpt = cursor.item(); - - start_excerpt - .zip(end_excerpt) - .and_then(|(start_excerpt, end_excerpt)| { - if start_excerpt.id != end_excerpt.id { - return None; - } - - let excerpt_buffer_start = start_excerpt - .range - .context - .start - .to_offset(&start_excerpt.buffer); - let excerpt_buffer_end = excerpt_buffer_start + start_excerpt.text_summary.len; + self.excerpt_containing(range.clone()) + .and_then(|(excerpt, excerpt_offset)| { + let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len; let start_in_buffer = - excerpt_buffer_start + range.start.saturating_sub(*cursor.start()); - let end_in_buffer = - excerpt_buffer_start + range.end.saturating_sub(*cursor.start()); - let mut ancestor_buffer_range = start_excerpt + excerpt_buffer_start + range.start.saturating_sub(excerpt_offset); + let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset); + let mut ancestor_buffer_range = excerpt .buffer .range_for_syntax_ancestor(start_in_buffer..end_in_buffer)?; ancestor_buffer_range.start = cmp::max(ancestor_buffer_range.start, excerpt_buffer_start); ancestor_buffer_range.end = cmp::min(ancestor_buffer_range.end, excerpt_buffer_end); - let start = cursor.start() + (ancestor_buffer_range.start - excerpt_buffer_start); - let end = cursor.start() + (ancestor_buffer_range.end - excerpt_buffer_start); + let start = excerpt_offset + (ancestor_buffer_range.start - excerpt_buffer_start); + let end = excerpt_offset + (ancestor_buffer_range.end - excerpt_buffer_start); Some(start..end) }) } @@ -2929,6 +2927,31 @@ impl MultiBufferSnapshot { None } + /// Returns the excerpt containing range and its offset start within the multibuffer or none if `range` spans multiple excerpts + fn excerpt_containing<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> Option<(&'a Excerpt, usize)> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&range.start, Bias::Right, &()); + let start_excerpt = cursor.item(); + + cursor.seek(&range.end, Bias::Right, &()); + let end_excerpt = cursor.item(); + + start_excerpt + .zip(end_excerpt) + .and_then(|(start_excerpt, end_excerpt)| { + if start_excerpt.id != end_excerpt.id { + return None; + } + + Some((start_excerpt, *cursor.start())) + }) + } + pub fn remote_selections_in_range<'a>( &'a self, range: &'a Range, diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index f1c19bca8a..f3ce89adc5 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -659,6 +659,31 @@ impl<'a> MutableSelectionsCollection<'a> { } } + pub fn move_offsets_with( + &mut self, + mut move_selection: impl FnMut(&MultiBufferSnapshot, &mut Selection), + ) { + let mut changed = false; + let snapshot = self.buffer().clone(); + let selections = self + .all::(self.cx) + .into_iter() + .map(|selection| { + let mut moved_selection = selection.clone(); + move_selection(&snapshot, &mut moved_selection); + if selection != moved_selection { + changed = true; + } + moved_selection + }) + .collect(); + drop(snapshot); + + if changed { + self.select(selections) + } + } + pub fn move_heads_with( &mut self, mut update_head: impl FnMut( diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index b65b09cf17..7f92190489 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, ops::{Deref, DerefMut, Range}, sync::Arc, }; @@ -7,7 +8,8 @@ use anyhow::Result; use futures::Future; use gpui::{json, ViewContext, ViewHandle}; -use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig}; +use indoc::indoc; +use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries}; use lsp::{notification, request}; use project::Project; use smol::stream::StreamExt; @@ -125,6 +127,32 @@ impl<'a> EditorLspTestContext<'a> { Self::new(language, capabilities, cx).await } + pub async fn new_typescript( + capabilities: lsp::ServerCapabilities, + cx: &'a mut gpui::TestAppContext, + ) -> EditorLspTestContext<'a> { + let language = Language::new( + LanguageConfig { + name: "Typescript".into(), + path_suffixes: vec!["ts".to_string()], + ..Default::default() + }, + Some(tree_sitter_typescript::language_typescript()), + ) + .with_queries(LanguageQueries { + brackets: Some(Cow::from(indoc! {r#" + ("(" @open ")" @close) + ("[" @open "]" @close) + ("{" @open "}" @close) + ("<" @open ">" @close) + ("\"" @open "\"" @close)"#})), + ..Default::default() + }) + .expect("Could not parse brackets"); + + Self::new(language, capabilities, cx).await + } + // Constructs lsp range using a marked string with '[', ']' range delimiters pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range { let ranges = self.ranges(marked_text); diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index d8dbfee171..8f8647e88d 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -162,10 +162,13 @@ impl<'a> EditorTestContext<'a> { /// embedded range markers that represent the ranges and directions of /// each selection. /// + /// Returns a context handle so that assertion failures can print what + /// editor state was needed to cause the failure. + /// /// See the `util::test::marked_text_ranges` function for more information. pub fn set_state(&mut self, marked_text: &str) -> ContextHandle { let _state_context = self.add_assertion_context(format!( - "Editor State: \"{}\"", + "Initial Editor State: \"{}\"", marked_text.escape_debug().to_string() )); let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true); diff --git a/crates/gpui/src/app/ref_counts.rs b/crates/gpui/src/app/ref_counts.rs index a9ae6e7a6c..f0c1699f16 100644 --- a/crates/gpui/src/app/ref_counts.rs +++ b/crates/gpui/src/app/ref_counts.rs @@ -1,11 +1,15 @@ +#[cfg(any(test, feature = "test-support"))] use std::sync::Arc; use lazy_static::lazy_static; +#[cfg(any(test, feature = "test-support"))] use parking_lot::Mutex; use collections::{hash_map::Entry, HashMap, HashSet}; -use crate::{util::post_inc, ElementStateId}; +#[cfg(any(test, feature = "test-support"))] +use crate::util::post_inc; +use crate::ElementStateId; lazy_static! { static ref LEAK_BACKTRACE: bool = @@ -30,9 +34,8 @@ pub struct RefCounts { } impl RefCounts { - pub fn new( - #[cfg(any(test, feature = "test-support"))] leak_detector: Arc>, - ) -> Self { + #[cfg(any(test, feature = "test-support"))] + pub fn new(leak_detector: Arc>) -> Self { Self { #[cfg(any(test, feature = "test-support"))] leak_detector, diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 0805cdd865..1366e7e6ed 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -621,6 +621,8 @@ impl ViewHandle { } } +/// Tracks string context to be printed when assertions fail. +/// Often this is done by storing a context string in the manager and returning the handle. #[derive(Clone)] pub struct AssertionContextManager { id: Arc, @@ -651,6 +653,9 @@ impl AssertionContextManager { } } +/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails. +/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails, +/// the state that was set initially for the failure can be printed in the error message pub struct ContextHandle { id: usize, manager: AssertionContextManager, diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 8f1d3d39ed..3e6561e471 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -66,6 +66,7 @@ settings = { path = "../settings", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } ctor = "0.1" env_logger = "0.9" +indoc = "1.0.4" rand = "0.8.3" tree-sitter-embedded-template = "*" tree-sitter-html = "*" diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 13b3a86822..953e059518 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2346,12 +2346,13 @@ impl BufferSnapshot { Some(items) } - pub fn enclosing_bracket_ranges( - &self, + pub fn enclosing_bracket_ranges<'a, T: ToOffset>( + &'a self, range: Range, - ) -> Option<(Range, Range)> { + ) -> impl Iterator, Range)> + 'a { // Find bracket pairs that *inclusively* contain the given range. let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut matches = self.syntax.matches( range.start.saturating_sub(1)..self.len().min(range.end + 1), &self.text, @@ -2363,39 +2364,31 @@ impl BufferSnapshot { .map(|grammar| grammar.brackets_config.as_ref().unwrap()) .collect::>(); - // Get the ranges of the innermost pair of brackets. - let mut result: Option<(Range, Range)> = None; - while let Some(mat) = matches.peek() { - let mut open = None; - let mut close = None; - let config = &configs[mat.grammar_index]; - for capture in mat.captures { - if capture.index == config.open_capture_ix { - open = Some(capture.node.byte_range()); - } else if capture.index == config.close_capture_ix { - close = Some(capture.node.byte_range()); + iter::from_fn(move || { + while let Some(mat) = matches.peek() { + let mut open = None; + let mut close = None; + let config = &configs[mat.grammar_index]; + for capture in mat.captures { + if capture.index == config.open_capture_ix { + open = Some(capture.node.byte_range()); + } else if capture.index == config.close_capture_ix { + close = Some(capture.node.byte_range()); + } } - } - matches.advance(); + matches.advance(); - let Some((open, close)) = open.zip(close) else { continue }; - if open.start > range.start || close.end < range.end { - continue; - } - let len = close.end - open.start; + let Some((open, close)) = open.zip(close) else { continue }; - if let Some((existing_open, existing_close)) = &result { - let existing_len = existing_close.end - existing_open.start; - if len > existing_len { + if open.start > range.start || close.end < range.end { continue; } + + return Some((open, close)); } - - result = Some((open, close)); - } - - result + None + }) } #[allow(clippy::type_complexity)] diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 59fd6a534b..4d2c9670c6 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -3,6 +3,7 @@ use clock::ReplicaId; use collections::BTreeMap; use fs::LineEnding; use gpui::{ModelHandle, MutableAppContext}; +use indoc::indoc; use proto::deserialize_operation; use rand::prelude::*; use settings::Settings; @@ -15,7 +16,7 @@ use std::{ }; use text::network::Network; use unindent::Unindent as _; -use util::{post_inc, test::marked_text_ranges, RandomCharIter}; +use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter}; #[cfg(test)] #[ctor::ctor] @@ -576,53 +577,117 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) { - cx.set_global(Settings::test(cx)); - let buffer = cx.add_model(|cx| { - let text = " - mod x { - mod y { + let mut assert = |selection_text, range_markers| { + assert_enclosing_bracket_pairs(selection_text, range_markers, rust_lang(), cx) + }; + assert( + indoc! {" + mod x { + moˇd y { + } } - " - .unindent(); - Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx) - }); - let buffer = buffer.read(cx); - assert_eq!( - buffer.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)), - Some(( - Point::new(0, 6)..Point::new(0, 7), - Point::new(4, 0)..Point::new(4, 1) - )) - ); - assert_eq!( - buffer.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)), - Some(( - Point::new(1, 10)..Point::new(1, 11), - Point::new(3, 4)..Point::new(3, 5) - )) - ); - assert_eq!( - buffer.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)), - Some(( - Point::new(1, 10)..Point::new(1, 11), - Point::new(3, 4)..Point::new(3, 5) - )) + let foo = 1;"}, + vec![indoc! {" + mod x «{» + mod y { + + } + «}» + let foo = 1;"}], ); - assert_eq!( - buffer.enclosing_bracket_point_ranges(Point::new(4, 1)..Point::new(4, 1)), - Some(( - Point::new(0, 6)..Point::new(0, 7), - Point::new(4, 0)..Point::new(4, 1) - )) + assert( + indoc! {" + mod x { + mod y ˇ{ + + } + } + let foo = 1;"}, + vec![ + indoc! {" + mod x «{» + mod y { + + } + «}» + let foo = 1;"}, + indoc! {" + mod x { + mod y «{» + + «}» + } + let foo = 1;"}, + ], + ); + + assert( + indoc! {" + mod x { + mod y { + + }ˇ + } + let foo = 1;"}, + vec![ + indoc! {" + mod x «{» + mod y { + + } + «}» + let foo = 1;"}, + indoc! {" + mod x { + mod y «{» + + «}» + } + let foo = 1;"}, + ], + ); + + assert( + indoc! {" + mod x { + mod y { + + } + ˇ} + let foo = 1;"}, + vec![indoc! {" + mod x «{» + mod y { + + } + «}» + let foo = 1;"}], + ); + + assert( + indoc! {" + mod x { + mod y { + + } + } + let fˇoo = 1;"}, + vec![], ); // Regression test: avoid crash when querying at the end of the buffer. - assert_eq!( - buffer.enclosing_bracket_point_ranges(Point::new(4, 1)..Point::new(5, 0)), - None + assert( + indoc! {" + mod x { + mod y { + + } + } + let foo = 1;ˇ"}, + vec![], ); } @@ -630,52 +695,33 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) { fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children( cx: &mut MutableAppContext, ) { - let javascript_language = Arc::new( - Language::new( - LanguageConfig { - name: "JavaScript".into(), - ..Default::default() - }, - Some(tree_sitter_javascript::language()), - ) - .with_brackets_query( - r#" - ("{" @open "}" @close) - ("(" @open ")" @close) - "#, - ) - .unwrap(), - ); + let mut assert = |selection_text, bracket_pair_texts| { + assert_enclosing_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx) + }; - cx.set_global(Settings::test(cx)); - let buffer = cx.add_model(|cx| { - let text = " - for (const a in b) { - // a comment that's longer than the for-loop header - } - " - .unindent(); - Buffer::new(0, text, cx).with_language(javascript_language, cx) - }); - - let buffer = buffer.read(cx); - assert_eq!( - buffer.enclosing_bracket_point_ranges(Point::new(0, 18)..Point::new(0, 18)), - Some(( - Point::new(0, 4)..Point::new(0, 5), - Point::new(0, 17)..Point::new(0, 18) - )) + assert( + indoc! {" + for (const a in b)ˇ { + // a comment that's longer than the for-loop header + }"}, + vec![indoc! {" + for «(»const a in b«)» { + // a comment that's longer than the for-loop header + }"}], ); // Regression test: even though the parent node of the parentheses (the for loop) does // intersect the given range, the parentheses themselves do not contain the range, so // they should not be returned. Only the curly braces contain the range. - assert_eq!( - buffer.enclosing_bracket_point_ranges(Point::new(0, 20)..Point::new(0, 20)), - Some(( - Point::new(0, 19)..Point::new(0, 20), - Point::new(2, 0)..Point::new(2, 1) - )) + assert( + indoc! {" + for (const a in b) {ˇ + // a comment that's longer than the for-loop header + }"}, + vec![indoc! {" + for (const a in b) «{» + // a comment that's longer than the for-loop header + «}»"}], ); } @@ -1892,21 +1938,6 @@ fn test_contiguous_ranges() { ); } -impl Buffer { - pub fn enclosing_bracket_point_ranges( - &self, - range: Range, - ) -> Option<(Range, Range)> { - self.snapshot() - .enclosing_bracket_ranges(range) - .map(|(start, end)| { - let point_start = start.start.to_point(self)..start.end.to_point(self); - let point_end = end.start.to_point(self)..end.end.to_point(self); - (point_start, point_end) - }) - } -} - fn ruby_lang() -> Language { Language::new( LanguageConfig { @@ -1990,6 +2021,23 @@ fn json_lang() -> Language { ) } +fn javascript_lang() -> Language { + Language::new( + LanguageConfig { + name: "JavaScript".into(), + ..Default::default() + }, + Some(tree_sitter_javascript::language()), + ) + .with_brackets_query( + r#" + ("{" @open "}" @close) + ("(" @open ")" @close) + "#, + ) + .unwrap() +} + fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> String { buffer.read_with(cx, |buffer, _| { let snapshot = buffer.snapshot(); @@ -1997,3 +2045,36 @@ fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> Str layers[0].node.to_sexp() }) } + +// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers` +fn assert_enclosing_bracket_pairs( + selection_text: &'static str, + bracket_pair_texts: Vec<&'static str>, + language: Language, + cx: &mut MutableAppContext, +) { + cx.set_global(Settings::test(cx)); + let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false); + let buffer = cx.add_model(|cx| { + Buffer::new(0, expected_text.clone(), cx).with_language(Arc::new(language), cx) + }); + let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot()); + + let selection_range = selection_ranges[0].clone(); + + let bracket_pairs = bracket_pair_texts + .into_iter() + .map(|pair_text| { + let (bracket_text, ranges) = marked_text_ranges(pair_text, false); + assert_eq!(bracket_text, expected_text); + (ranges[0].clone(), ranges[1].clone()) + }) + .collect::>(); + + assert_set_eq!( + buffer + .enclosing_bracket_ranges(selection_range) + .collect::>(), + bracket_pairs + ); +} diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 8bc7c756e0..7c821d6fec 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -452,8 +452,9 @@ fn end_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) -> D fn matching(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { let offset = point.to_offset(map, Bias::Left); - if let Some((open_range, close_range)) = - map.buffer_snapshot.enclosing_bracket_ranges(offset..offset) + if let Some((open_range, close_range)) = map + .buffer_snapshot + .innermost_enclosing_bracket_ranges(offset..offset) { if open_range.contains(&offset) { close_range.start.to_display_point(map) From 30caeeaeb597470fb714f5caccdffbe11eae3a14 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 15 Feb 2023 14:11:00 -0800 Subject: [PATCH 148/180] fix comment typo --- crates/editor/src/multi_buffer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 7b85799b31..908e5c827d 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2648,7 +2648,8 @@ impl MultiBufferSnapshot { result } - /// Returns enclosingn bracket ranges containing the given range or returns None if the range is not contained in a single excerpt + /// Returns enclosinng bracket ranges containing the given range or returns None if the range is + /// not contained in a single excerpt pub fn enclosing_bracket_ranges<'a, T: ToOffset>( &'a self, range: Range, From 33306846a6d94bad5e6f143473c1f363b9c026ac Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 15 Feb 2023 14:28:50 -0800 Subject: [PATCH 149/180] add tree-sitter-typescript to editor crate test support features --- crates/editor/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 7f1bec1d85..fd704becd2 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -17,7 +17,8 @@ test-support = [ "project/test-support", "util/test-support", "workspace/test-support", - "tree-sitter-rust" + "tree-sitter-rust", + "tree-sitter-typescript" ] [dependencies] From bef2013c7fb94c71bee86dbe2aedb00a6b8153b5 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 15 Feb 2023 14:28:14 -0800 Subject: [PATCH 150/180] wip --- .../src/test/editor_lsp_test_context.rs | 4 +- crates/vim/src/normal.rs | 9 +++++ crates/vim/src/test/vim_test_context.rs | 40 ++++++++++++++----- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 7f92190489..89d59d853b 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -62,7 +62,7 @@ impl<'a> EditorLspTestContext<'a> { params .fs .as_fake() - .insert_tree("/root", json!({ "dir": { file_name: "" }})) + .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }})) .await; let (window_id, workspace) = cx.add_window(|cx| { @@ -107,7 +107,7 @@ impl<'a> EditorLspTestContext<'a> { }, lsp, workspace, - buffer_lsp_url: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(), + buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(), } } diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 742f2426c8..f4d79ba89b 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -1009,4 +1009,13 @@ mod test { .await; } } + + #[gpui::test] + async fn test_percent(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + for count in 1..=2 { + // let test_case = indoc! {" + // "} + } + } } diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 539ab0a8ff..4ac01c3f34 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -1,6 +1,8 @@ use std::ops::{Deref, DerefMut}; -use editor::test::editor_test_context::EditorTestContext; +use editor::test::{ + editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext, +}; use gpui::{json::json, AppContext, ContextHandle, ViewHandle}; use project::Project; use search::{BufferSearchBar, ProjectSearchBar}; @@ -11,7 +13,7 @@ use crate::{state::Operator, *}; use super::VimBindingTestContext; pub struct VimTestContext<'a> { - cx: EditorTestContext<'a>, + cx: EditorLspTestContext<'a>, workspace: ViewHandle, } @@ -26,19 +28,28 @@ impl<'a> VimTestContext<'a> { settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap(); }); - let params = cx.update(AppState::test); - let project = Project::test(params.fs.clone(), [], cx).await; - cx.update(|cx| { cx.update_global(|settings: &mut Settings, _| { settings.vim_mode = enabled; }); }); + let params = cx.update(AppState::test); + + let file_name = "test.rs"; + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + ..Default::default() + })) + .await; + + let project = Project::test(params.fs.clone(), [], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); + params .fs .as_fake() - .insert_tree("/root", json!({ "dir": { "test.txt": "" } })) + .insert_tree("/root", json!({ "dir": { "test.rs": "" } })) .await; let (window_id, workspace) = cx.add_window(|cx| { @@ -87,11 +98,18 @@ impl<'a> VimTestContext<'a> { }); editor.update(cx, |_, cx| cx.focus_self()); + let lsp = fake_servers.next().await.unwrap(); + Self { - cx: EditorTestContext { - cx, - window_id, - editor, + cx: EditorLspTestContext { + cx: EditorTestContext { + cx, + window_id, + editor, + }, + lsp, + workspace, + buffer_lsp_url: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(), }, workspace, } @@ -101,7 +119,7 @@ impl<'a> VimTestContext<'a> { where F: FnOnce(&Workspace, &AppContext) -> T, { - self.workspace.read_with(self.cx.cx, read) + self.workspace.read_with(self.cx.cx.cx, read) } pub fn enable_vim(&mut self) { From 50ccf16de1d43fc1e9a54a939e0199d98af797af Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 15 Feb 2023 17:41:29 -0500 Subject: [PATCH 151/180] Cargo check before test to catch warnings/errors Co-Authored-By: Nathan Sobo --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3268cc13c..5da8c8945e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,9 @@ jobs: clean: false submodules: 'recursive' + - name: Run check + run: cargo check --workspace + - name: Run tests run: cargo test --workspace --no-fail-fast From baee6d03425ee8bfe3f78b1b8e0b2b65ad697b3d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 16 Feb 2023 15:53:37 +0100 Subject: [PATCH 152/180] Simulate disk-based diagnostics finishing 1s after saving buffer Previously, we would simulate disk-based diagnostics finishing after saving a buffer. However, the language server may produce diagnostics right after emitting the event, causing the diagnostics status bar item to not reflect the latest state of the buffer. With this change, we will instead simulate disk-based diagnostics finishing after 1s after saving the buffer (only for language servers that don't have the concept of disk-based diagnostics, such as TypeScript). This ensures we always reflect the latest state and doesn't cause the UI to flicker as a result of the LSP sending us diagnostics after every input. --- crates/project/src/project.rs | 79 ++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8f6b867b12..ec6a99edec 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -59,7 +59,7 @@ use std::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, - time::Instant, + time::{Duration, Instant}, }; use terminal::{Terminal, TerminalBuilder}; use util::{debug_panic, defer, post_inc, ResultExt, TryFutureExt as _}; @@ -185,6 +185,7 @@ pub enum LanguageServerState { language: Arc, adapter: Arc, server: Arc, + simulate_disk_based_diagnostics_completion: Option>, }, } @@ -1716,19 +1717,39 @@ impl Project { .log_err(); } - // After saving a buffer, simulate disk-based diagnostics being finished for languages - // that don't support a disk-based progress token. - let (lsp_adapter, language_server) = - self.language_server_for_buffer(buffer.read(cx), cx)?; - if lsp_adapter.disk_based_diagnostics_progress_token.is_none() { - let server_id = language_server.server_id(); - self.disk_based_diagnostics_finished(server_id, cx); - self.broadcast_language_server_update( - server_id, - proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( - proto::LspDiskBasedDiagnosticsUpdated {}, - ), - ); + let language_server_id = self.language_server_id_for_buffer(buffer.read(cx), cx)?; + if let Some(LanguageServerState::Running { + adapter, + simulate_disk_based_diagnostics_completion, + .. + }) = self.language_servers.get_mut(&language_server_id) + { + // After saving a buffer using a language server that doesn't provide + // a disk-based progress token, kick off a timer that will reset every + // time the buffer is saved. If the timer eventually fires, simulate + // disk-based diagnostics being finished so that other pieces of UI + // (e.g., project diagnostics view, diagnostic status bar) can update. + // We don't emit an event right away because the language server might take + // some time to publish diagnostics. + if adapter.disk_based_diagnostics_progress_token.is_none() { + const DISK_BASED_DIAGNOSTICS_DEBOUNCE: Duration = Duration::from_secs(1); + + let task = cx.spawn_weak(|this, mut cx| async move { + cx.background().timer(DISK_BASED_DIAGNOSTICS_DEBOUNCE).await; + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx | { + this.disk_based_diagnostics_finished(language_server_id, cx); + this.broadcast_language_server_update( + language_server_id, + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( + proto::LspDiskBasedDiagnosticsUpdated {}, + ), + ); + }); + } + }); + *simulate_disk_based_diagnostics_completion = Some(task); + } } } _ => {} @@ -1749,6 +1770,7 @@ impl Project { adapter, language, server, + .. }) = self.language_servers.get(id) { return Some((adapter, language, server)); @@ -2035,6 +2057,7 @@ impl Project { adapter: adapter.clone(), language, server: language_server.clone(), + simulate_disk_based_diagnostics_completion: None, }, ); this.language_server_statuses.insert( @@ -3105,6 +3128,7 @@ impl Project { adapter, language, server, + .. }) = self.language_servers.get(server_id) { let adapter = adapter.clone(); @@ -6178,22 +6202,27 @@ impl Project { buffer: &Buffer, cx: &AppContext, ) -> Option<(&Arc, &Arc)> { + let server_id = self.language_server_id_for_buffer(buffer, cx)?; + let server = self.language_servers.get(&server_id)?; + if let LanguageServerState::Running { + adapter, server, .. + } = server + { + Some((adapter, server)) + } else { + None + } + } + + fn language_server_id_for_buffer(&self, buffer: &Buffer, cx: &AppContext) -> Option { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { let name = language.lsp_adapter()?.name.clone(); let worktree_id = file.worktree_id(cx); let key = (worktree_id, name); - - if let Some(server_id) = self.language_server_ids.get(&key) { - if let Some(LanguageServerState::Running { - adapter, server, .. - }) = self.language_servers.get(server_id) - { - return Some((adapter, server)); - } - } + self.language_server_ids.get(&key).copied() + } else { + None } - - None } } From 2d393583230c2d7365d1d50c7e5c5687c4186766 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 16 Feb 2023 12:11:57 -0800 Subject: [PATCH 153/180] rust: Highlight functions called with a turbofish --- crates/zed/src/languages/rust/highlights.scm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/zed/src/languages/rust/highlights.scm b/crates/zed/src/languages/rust/highlights.scm index b52a7a8aff..7240173a89 100644 --- a/crates/zed/src/languages/rust/highlights.scm +++ b/crates/zed/src/languages/rust/highlights.scm @@ -12,6 +12,15 @@ field: (field_identifier) @function.method) ]) +(generic_function + function: [ + (identifier) @function + (scoped_identifier + name: (identifier) @function) + (field_expression + field: (field_identifier) @function.method) + ]) + (function_item name: (identifier) @function.definition) (function_signature_item name: (identifier) @function.definition) From eac33d732e1db3894d2f5a8e26346dcb25b63b18 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Thu, 16 Feb 2023 12:23:45 -0800 Subject: [PATCH 154/180] wip --- crates/editor/src/multi_buffer.rs | 61 ++++++++------ .../src/test/editor_lsp_test_context.rs | 23 +++++- crates/language/src/buffer.rs | 6 +- crates/vim/src/normal.rs | 10 ++- crates/vim/src/test/vim_test_context.rs | 80 ++----------------- crates/vim/src/visual.rs | 2 +- 6 files changed, 73 insertions(+), 109 deletions(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 908e5c827d..7cc0031c4f 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -385,9 +385,13 @@ impl MultiBuffer { _ => Default::default(), }; - #[allow(clippy::type_complexity)] - let mut buffer_edits: HashMap, Arc, bool, u32)>> = - Default::default(); + struct BufferEdit { + range: Range, + new_text: Arc, + is_insertion: bool, + original_indent_column: u32, + } + let mut buffer_edits: HashMap> = Default::default(); let mut cursor = snapshot.excerpts.cursor::(); for (ix, (range, new_text)) in edits.enumerate() { let new_text: Arc = new_text.into(); @@ -422,12 +426,12 @@ impl MultiBuffer { buffer_edits .entry(start_excerpt.buffer_id) .or_insert(Vec::new()) - .push(( - buffer_start..buffer_end, + .push(BufferEdit { + range: buffer_start..buffer_end, new_text, - true, + is_insertion: true, original_indent_column, - )); + }); } else { let start_excerpt_range = buffer_start ..start_excerpt @@ -444,21 +448,21 @@ impl MultiBuffer { buffer_edits .entry(start_excerpt.buffer_id) .or_insert(Vec::new()) - .push(( - start_excerpt_range, - new_text.clone(), - true, + .push(BufferEdit { + range: start_excerpt_range, + new_text: new_text.clone(), + is_insertion: true, original_indent_column, - )); + }); buffer_edits .entry(end_excerpt.buffer_id) .or_insert(Vec::new()) - .push(( - end_excerpt_range, - new_text.clone(), - false, + .push(BufferEdit { + range: end_excerpt_range, + new_text: new_text.clone(), + is_insertion: false, original_indent_column, - )); + }); cursor.seek(&range.start, Bias::Right, &()); cursor.next(&()); @@ -469,19 +473,19 @@ impl MultiBuffer { buffer_edits .entry(excerpt.buffer_id) .or_insert(Vec::new()) - .push(( - excerpt.range.context.to_offset(&excerpt.buffer), - new_text.clone(), - false, + .push(BufferEdit { + range: excerpt.range.context.to_offset(&excerpt.buffer), + new_text: new_text.clone(), + is_insertion: false, original_indent_column, - )); + }); cursor.next(&()); } } } for (buffer_id, mut edits) in buffer_edits { - edits.sort_unstable_by_key(|(range, _, _, _)| range.start); + edits.sort_unstable_by_key(|edit| edit.range.start); self.buffers.borrow()[&buffer_id] .buffer .update(cx, |buffer, cx| { @@ -490,14 +494,19 @@ impl MultiBuffer { let mut original_indent_columns = Vec::new(); let mut deletions = Vec::new(); let empty_str: Arc = "".into(); - while let Some(( + while let Some(BufferEdit { mut range, new_text, mut is_insertion, original_indent_column, - )) = edits.next() + }) = edits.next() { - while let Some((next_range, _, next_is_insertion, _)) = edits.peek() { + while let Some(BufferEdit { + range: next_range, + is_insertion: next_is_insertion, + .. + }) = edits.peek() + { if range.end >= next_range.start { range.end = cmp::max(next_range.end, range.end); is_insertion |= *next_is_insertion; diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 89d59d853b..938de169a7 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -122,7 +122,26 @@ impl<'a> EditorLspTestContext<'a> { ..Default::default() }, Some(tree_sitter_rust::language()), - ); + ) + .with_queries(LanguageQueries { + indents: Some(Cow::from(indoc! {r#" + [ + ((where_clause) _ @end) + (field_expression) + (call_expression) + (assignment_expression) + (let_declaration) + (let_chain) + (await_expression) + ] @indent + + (_ "[" "]" @end) @indent + (_ "<" ">" @end) @indent + (_ "{" "}" @end) @indent + (_ "(" ")" @end) @indent"#})), + ..Default::default() + }) + .expect("Could not parse queries"); Self::new(language, capabilities, cx).await } @@ -148,7 +167,7 @@ impl<'a> EditorLspTestContext<'a> { ("\"" @open "\"" @close)"#})), ..Default::default() }) - .expect("Could not parse brackets"); + .expect("Could not parse queries"); Self::new(language, capabilities, cx).await } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 953e059518..2119f86bbd 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1389,12 +1389,12 @@ impl Buffer { .enumerate() .zip(&edit_operation.as_edit().unwrap().new_text) .map(|((ix, (range, _)), new_text)| { - let new_text_len = new_text.len(); + let new_text_length = new_text.len(); let old_start = range.start.to_point(&before_edit); let new_start = (delta + range.start as isize) as usize; - delta += new_text_len as isize - (range.end as isize - range.start as isize); + delta += new_text_length as isize - (range.end as isize - range.start as isize); - let mut range_of_insertion_to_indent = 0..new_text_len; + let mut range_of_insertion_to_indent = 0..new_text_length; let mut first_line_is_new = false; let mut original_indent_column = None; diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index f4d79ba89b..2dd2d489a5 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -857,13 +857,15 @@ mod test { // Our indentation is smarter than vims. So we don't match here cx.assert_manual( indoc! {" - fn test() - println!(ˇ);"}, + fn test() { + println!(ˇ); + }"}, Mode::Normal, indoc! {" - fn test() + fn test() { ˇ - println!();"}, + println!(); + }"}, Mode::Insert, ); cx.assert_manual( diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 4ac01c3f34..f5614b4b47 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -3,10 +3,8 @@ use std::ops::{Deref, DerefMut}; use editor::test::{ editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext, }; -use gpui::{json::json, AppContext, ContextHandle, ViewHandle}; -use project::Project; +use gpui::{AppContext, ContextHandle}; use search::{BufferSearchBar, ProjectSearchBar}; -use workspace::{pane, AppState, WorkspaceHandle}; use crate::{state::Operator, *}; @@ -14,56 +12,29 @@ use super::VimBindingTestContext; pub struct VimTestContext<'a> { cx: EditorLspTestContext<'a>, - workspace: ViewHandle, } impl<'a> VimTestContext<'a> { pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> { cx.update(|cx| { - editor::init(cx); - pane::init(cx); search::init(cx); crate::init(cx); settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap(); }); + let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await; + cx.update(|cx| { cx.update_global(|settings: &mut Settings, _| { settings.vim_mode = enabled; }); }); - let params = cx.update(AppState::test); - - let file_name = "test.rs"; - let mut fake_servers = language - .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - ..Default::default() - })) - .await; - - let project = Project::test(params.fs.clone(), [], cx).await; - project.update(cx, |project, _| project.languages().add(Arc::new(language))); - - params - .fs - .as_fake() - .insert_tree("/root", json!({ "dir": { "test.rs": "" } })) - .await; - - let (window_id, workspace) = cx.add_window(|cx| { - Workspace::new( - Default::default(), - 0, - project.clone(), - |_, _| unimplemented!(), - cx, - ) - }); + let window_id = cx.window_id; // Setup search toolbars and keypress hook - workspace.update(cx, |workspace, cx| { + cx.update_workspace(|workspace, cx| { observe_keystrokes(window_id, cx); workspace.active_pane().update(cx, |pane, cx| { pane.toolbar().update(cx, |toolbar, cx| { @@ -75,51 +46,14 @@ impl<'a> VimTestContext<'a> { }); }); - project - .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root", true, cx) - }) - .await - .unwrap(); - cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx)) - .await; - - let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone()); - let item = workspace - .update(cx, |workspace, cx| { - workspace.open_path(file, None, true, cx) - }) - .await - .expect("Could not open test file"); - - let editor = cx.update(|cx| { - item.act_as::(cx) - .expect("Opened test file wasn't an editor") - }); - editor.update(cx, |_, cx| cx.focus_self()); - - let lsp = fake_servers.next().await.unwrap(); - - Self { - cx: EditorLspTestContext { - cx: EditorTestContext { - cx, - window_id, - editor, - }, - lsp, - workspace, - buffer_lsp_url: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(), - }, - workspace, - } + Self { cx } } pub fn workspace(&mut self, read: F) -> T where F: FnOnce(&Workspace, &AppContext) -> T, { - self.workspace.read_with(self.cx.cx.cx, read) + self.cx.workspace.read_with(self.cx.cx.cx, read) } pub fn enable_vim(&mut self) { diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index b890e4e41b..2180fbdabb 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -650,7 +650,7 @@ mod test { The quick brown the ˇfox jumps over - dog"}, + dog"}, Mode::Normal, ); } From b03eebeb6c71a5ff6de3a6a6fd8f955586289584 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Thu, 16 Feb 2023 12:24:35 -0800 Subject: [PATCH 155/180] update tree-sitter-typescript to add support for new satisfies operator --- Cargo.lock | 15 ++++++++++++--- crates/editor/Cargo.toml | 4 ++-- crates/zed/Cargo.toml | 3 ++- .../zed/src/languages/typescript/highlights.scm | 1 + 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75c084d3ef..daa613bfcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1956,7 +1956,7 @@ dependencies = [ "tree-sitter-html", "tree-sitter-javascript", "tree-sitter-rust", - "tree-sitter-typescript", + "tree-sitter-typescript 0.20.2", "unindent", "util", "workspace", @@ -3277,7 +3277,7 @@ dependencies = [ "tree-sitter-python", "tree-sitter-ruby", "tree-sitter-rust", - "tree-sitter-typescript", + "tree-sitter-typescript 0.20.1", "unicase", "unindent", "util", @@ -7192,6 +7192,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-typescript" +version = "0.20.2" +source = "git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259#5d20856f34315b068c41edaee2ac8a100081d259" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-yaml" version = "0.0.1" @@ -8439,7 +8448,7 @@ dependencies = [ "tree-sitter-rust", "tree-sitter-scheme", "tree-sitter-toml", - "tree-sitter-typescript", + "tree-sitter-typescript 0.20.2", "tree-sitter-yaml", "unindent", "url", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index fd704becd2..6cb7ef32ec 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -59,7 +59,7 @@ smol = "1.2" tree-sitter-rust = { version = "*", optional = true } tree-sitter-html = { version = "*", optional = true } tree-sitter-javascript = { version = "*", optional = true } -tree-sitter-typescript = { version = "*", optional = true } +tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259", optional = true } [dev-dependencies] text = { path = "../text", features = ["test-support"] } @@ -77,5 +77,5 @@ unindent = "0.1.7" tree-sitter = "0.20" tree-sitter-rust = "0.20" tree-sitter-html = "0.19" -tree-sitter-typescript = "0.20.1" +tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" } tree-sitter-javascript = "0.20" diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index ed40aa7391..046866cc0c 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -105,7 +105,8 @@ tree-sitter-rust = "0.20.3" tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } tree-sitter-python = "0.20.2" tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" } -tree-sitter-typescript = "0.20.1" +tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" } + tree-sitter-ruby = "0.20.0" tree-sitter-html = "0.19.0" tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"} diff --git a/crates/zed/src/languages/typescript/highlights.scm b/crates/zed/src/languages/typescript/highlights.scm index bd1986b6b3..43df33d158 100644 --- a/crates/zed/src/languages/typescript/highlights.scm +++ b/crates/zed/src/languages/typescript/highlights.scm @@ -175,6 +175,7 @@ "new" "of" "return" + "satisfies" "set" "static" "switch" From 28eb69e74eb578d1a228c27647f75b1d20a47352 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 16 Feb 2023 12:35:35 -0800 Subject: [PATCH 156/180] Bump tree-sitter for optimization of querying in range --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84fdc9dda9..3f4f46aedf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7003,7 +7003,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.20.9" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=125503ff3b613b08233fc1e06292be9ddd9dd448#125503ff3b613b08233fc1e06292be9ddd9dd448" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14#c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index d4a2843338..c74a76ccce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } rand = { version = "0.8" } [patch.crates-io] -tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "125503ff3b613b08233fc1e06292be9ddd9dd448" } +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 From cf83ecccbb8f1fe28ea533b103efdb3091dd8578 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 16 Feb 2023 10:36:46 -0800 Subject: [PATCH 157/180] Added workspace restart command --- Cargo.lock | 1 + crates/cli/Cargo.toml | 1 + crates/cli/src/main.rs | 13 ++++++++- crates/workspace/src/workspace.rs | 1 + crates/zed/src/main.rs | 8 ++++++ crates/zed/src/zed.rs | 44 ++++++++++++++++++++++++------- 6 files changed, 58 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75c084d3ef..abf9a12f0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1097,6 +1097,7 @@ dependencies = [ "ipc-channel", "plist", "serde", + "sysinfo", ] [[package]] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index f2bab22ea7..98c5ba60ab 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -18,6 +18,7 @@ clap = { version = "3.1", features = ["derive"] } dirs = "3.0" ipc-channel = "0.16" serde = { version = "1.0", features = ["derive", "rc"] } +sysinfo = "0.27" [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.9" diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index a31e59587f..8ef1b1fb2b 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -14,8 +14,10 @@ use std::{ fs::{self, OpenOptions}, io, path::{Path, PathBuf}, - ptr, + ptr, thread, + time::Duration, }; +use sysinfo::{Pid, System, SystemExt}; #[derive(Parser)] #[clap(name = "zed", global_setting(clap::AppSettings::NoAutoVersion))] @@ -32,6 +34,8 @@ struct Args { /// Custom Zed.app path #[clap(short, long)] bundle_path: Option, + #[clap(short, long)] + restart_from: Option, } #[derive(Debug, Deserialize)] @@ -60,6 +64,13 @@ fn main() -> Result<()> { return Ok(()); } + if let Some(parent_pid) = args.restart_from { + let mut system = System::new(); + while system.refresh_process(parent_pid) { + thread::sleep(Duration::from_millis(100)); + } + } + for path in args.paths.iter() { if !path.exists() { touch(path.as_path())?; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8d81ae7f2e..bf4fb11522 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -101,6 +101,7 @@ actions!( NewTerminal, NewSearch, Feedback, + Restart ] ); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 77fd516b86..5679bce53a 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -567,6 +567,14 @@ async fn handle_cli_connection( if let Some(request) = requests.next().await { match request { CliRequest::Open { paths, wait } => { + let paths = if paths.is_empty() { + workspace::last_opened_workspace_paths() + .await + .map(|location| location.paths().to_vec()) + .unwrap_or(paths) + } else { + paths + }; let (workspace, items) = cx .update(|cx| workspace::open_paths(&paths, &app_state, cx)) .await; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index bf9afe136e..d6e8a1a4be 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -23,7 +23,8 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind, + AssetSource, AsyncAppContext, Platform, PromptLevel, Task, TitlebarOptions, ViewContext, + WindowKind, }; use language::Rope; use lazy_static::lazy_static; @@ -34,11 +35,11 @@ use search::{BufferSearchBar, ProjectSearchBar}; use serde::Deserialize; use serde_json::to_string_pretty; use settings::{keymap_file_json_schema, settings_file_json_schema, Settings}; -use std::{borrow::Cow, env, path::Path, str, sync::Arc}; +use std::{borrow::Cow, env, path::Path, process::Command, str, sync::Arc}; use util::{channel::ReleaseChannel, paths, ResultExt, StaffMode}; use uuid::Uuid; pub use workspace; -use workspace::{sidebar::SidebarSide, AppState, Workspace}; +use workspace::{sidebar::SidebarSide, AppState, Restart, Workspace}; #[derive(Deserialize, Clone, PartialEq)] pub struct OpenBrowser { @@ -129,7 +130,10 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { } }, ); - cx.add_global_action(quit); + cx.add_global_action(|_: &Quit, cx| { + quit(cx).detach_and_log_err(cx); + }); + cx.add_global_action(restart); cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { cx.update_global::(|settings, cx| { @@ -403,7 +407,29 @@ pub fn build_window_options( } } -fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { +fn restart(_: &Restart, cx: &mut gpui::MutableAppContext) { + let cli_process = dbg!(cx.platform().path_for_auxiliary_executable("cli")) + .log_err() + .and_then(|path| { + Command::new(path) + .args(["--restart-from", &format!("{}", dbg!(std::process::id()))]) + .spawn() + .log_err() + }); + + cx.spawn(|mut cx| async move { + let did_quit = dbg!(cx.update(quit).await?); + if !did_quit { + if let Some(mut cli_process) = cli_process { + cli_process.kill().log_err(); + } + } + Ok::<(), anyhow::Error>(()) + }) + .detach_and_log_err(cx); +} + +fn quit(cx: &mut gpui::MutableAppContext) -> Task> { let mut workspaces = cx .window_ids() .filter_map(|window_id| cx.root_view::(window_id)) @@ -426,7 +452,7 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { .next() .await; if answer != Some(0) { - return Ok(()); + return Ok(false); } } @@ -438,13 +464,13 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { }) .await? { - return Ok(()); + return Ok(false); } } + dbg!("about to quit"); cx.platform().quit(); - anyhow::Ok(()) + anyhow::Ok(true) }) - .detach_and_log_err(cx); } fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { From 43f61ab413a1dfccf5a9c4645c96aad7f8f9b291 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 16 Feb 2023 13:35:32 -0800 Subject: [PATCH 158/180] Added action to autoupdate --- crates/activity_indicator/src/activity_indicator.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 8b9eb4b040..f3a6f7328a 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -252,7 +252,11 @@ impl ActivityIndicator { "Installing Zed update…".to_string(), None, ), - AutoUpdateStatus::Updated => (None, "Restart to update Zed".to_string(), None), + AutoUpdateStatus::Updated => ( + None, + "Click to restart and update Zed".to_string(), + Some(Box::new(workspace::Restart)), + ), AutoUpdateStatus::Errored => ( Some(WARNING_ICON), "Auto update failed".to_string(), From c72a50e20399d4c528f755b58ebdc784aec0dfb2 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 16 Feb 2023 16:37:07 -0500 Subject: [PATCH 159/180] Use correct case for YAML in default settings --- assets/settings/default.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index a4b95603e8..f6fb61d65c 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -224,7 +224,7 @@ "TSX": { "tab_size": 2 }, - "Yaml": { + "YAML": { "tab_size": 2 } }, From 4ea7a24b93a535cb0d26e509a9b26363aced181b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 16 Feb 2023 14:41:03 -0800 Subject: [PATCH 160/180] Made the 'update zed to collaborate' button clickable --- Cargo.lock | 1 + crates/collab_ui/Cargo.toml | 1 + crates/collab_ui/src/collab_titlebar_item.rs | 25 +++++++++++++------- crates/zed/src/zed.rs | 9 +++---- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abf9a12f0e..e45ed5c433 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1253,6 +1253,7 @@ name = "collab_ui" version = "0.1.0" dependencies = [ "anyhow", + "auto_update", "call", "client", "clock", diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index ac13e361fd..2dc4cc769a 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -22,6 +22,7 @@ test-support = [ ] [dependencies] +auto_update = { path = "../auto_update" } call = { path = "../call" } client = { path = "../client" } clock = { path = "../clock" } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 9f2c0fbee9..184a432ea3 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -504,7 +504,9 @@ impl CollabTitlebarItem { workspace: &ViewHandle, cx: &mut RenderContext, ) -> Option { - let theme = &cx.global::().theme; + enum ConnectionStatusButton {} + + let theme = &cx.global::().theme.clone(); match &*workspace.read(cx).client().status().borrow() { client::Status::ConnectionError | client::Status::ConnectionLost @@ -527,13 +529,20 @@ impl CollabTitlebarItem { .boxed(), ), client::Status::UpgradeRequired => Some( - Label::new( - "Please update Zed to collaborate".to_string(), - theme.workspace.titlebar.outdated_warning.text.clone(), - ) - .contained() - .with_style(theme.workspace.titlebar.outdated_warning.container) - .aligned() + MouseEventHandler::::new(0, cx, |_, _| { + Label::new( + "Please update Zed to collaborate".to_string(), + theme.workspace.titlebar.outdated_warning.text.clone(), + ) + .contained() + .with_style(theme.workspace.titlebar.outdated_warning.container) + .aligned() + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(auto_update::Check); + }) .boxed(), ), _ => None, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d6e8a1a4be..7be2818b61 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -408,17 +408,19 @@ pub fn build_window_options( } fn restart(_: &Restart, cx: &mut gpui::MutableAppContext) { - let cli_process = dbg!(cx.platform().path_for_auxiliary_executable("cli")) + let cli_process = cx + .platform() + .path_for_auxiliary_executable("cli") .log_err() .and_then(|path| { Command::new(path) - .args(["--restart-from", &format!("{}", dbg!(std::process::id()))]) + .args(["--restart-from", &format!("{}", std::process::id())]) .spawn() .log_err() }); cx.spawn(|mut cx| async move { - let did_quit = dbg!(cx.update(quit).await?); + let did_quit = cx.update(quit).await?; if !did_quit { if let Some(mut cli_process) = cli_process { cli_process.kill().log_err(); @@ -467,7 +469,6 @@ fn quit(cx: &mut gpui::MutableAppContext) -> Task> { return Ok(false); } } - dbg!("about to quit"); cx.platform().quit(); anyhow::Ok(true) }) From 6e33f33da1aee44535a6d9c4438592b8d0a8ba65 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 16 Feb 2023 16:35:34 -0800 Subject: [PATCH 161/180] Switch to open based restarting --- Cargo.lock | 1 - crates/cli/Cargo.toml | 1 - crates/cli/src/main.rs | 13 +------- crates/gpui/src/platform.rs | 1 + crates/gpui/src/platform/mac/platform.rs | 28 +++++++++++++++++ crates/gpui/src/platform/test.rs | 2 ++ crates/zed/src/main.rs | 4 ++- crates/zed/src/zed.rs | 40 ++++++------------------ 8 files changed, 44 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e45ed5c433..fb94530b6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1097,7 +1097,6 @@ dependencies = [ "ipc-channel", "plist", "serde", - "sysinfo", ] [[package]] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 98c5ba60ab..f2bab22ea7 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -18,7 +18,6 @@ clap = { version = "3.1", features = ["derive"] } dirs = "3.0" ipc-channel = "0.16" serde = { version = "1.0", features = ["derive", "rc"] } -sysinfo = "0.27" [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.9" diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 8ef1b1fb2b..a31e59587f 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -14,10 +14,8 @@ use std::{ fs::{self, OpenOptions}, io, path::{Path, PathBuf}, - ptr, thread, - time::Duration, + ptr, }; -use sysinfo::{Pid, System, SystemExt}; #[derive(Parser)] #[clap(name = "zed", global_setting(clap::AppSettings::NoAutoVersion))] @@ -34,8 +32,6 @@ struct Args { /// Custom Zed.app path #[clap(short, long)] bundle_path: Option, - #[clap(short, long)] - restart_from: Option, } #[derive(Debug, Deserialize)] @@ -64,13 +60,6 @@ fn main() -> Result<()> { return Ok(()); } - if let Some(parent_pid) = args.restart_from { - let mut system = System::new(); - while system.refresh_process(parent_pid) { - thread::sleep(Duration::from_millis(100)); - } - } - for path in args.paths.iter() { if !path.exists() { touch(path.as_path())?; diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 4753450110..1bf19e983e 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -81,6 +81,7 @@ pub trait Platform: Send + Sync { fn app_version(&self) -> Result; fn os_name(&self) -> &'static str; fn os_version(&self) -> Result; + fn restart(&self); } pub(crate) trait ForegroundPlatform { diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index ccbae5a832..3db7c6eedc 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -45,6 +45,7 @@ use std::{ ffi::{c_void, CStr, OsStr}, os::{raw::c_char, unix::ffi::OsStrExt}, path::{Path, PathBuf}, + process::Command, ptr, rc::Rc, slice, str, @@ -803,6 +804,33 @@ impl platform::Platform for MacPlatform { }) } } + + fn restart(&self) { + #[cfg(debug_assertions)] + let path = std::env::current_exe(); + + #[cfg(not(debug_assertions))] + let path = unsafe { + let bundle: id = NSBundle::mainBundle(); + if bundle.is_null() { + std::env::current_exe() + } else { + let path: id = msg_send![bundle, bundlePath]; + let path: *mut c_char = msg_send![path, UTF8String]; + + Ok(PathBuf::from(OsStr::from_bytes( + CStr::from_ptr(path).to_bytes(), + ))) + } + }; + + let command = path.and_then(|path| Command::new("/usr/bin/open").arg(path).spawn()); + + match command { + Err(err) => log::error!("Unable to restart application {}", err), + Ok(_) => self.quit(), + } + } } unsafe fn path_from_objc(path: id) -> PathBuf { diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index cf0e31c2b8..e81daa8788 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -226,6 +226,8 @@ impl super::Platform for Platform { patch: 0, }) } + + fn restart(&self) {} } #[derive(Debug)] diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 5679bce53a..a775b31bc4 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -38,7 +38,9 @@ use terminal_view::{get_working_directory, TerminalView}; use fs::RealFs; use settings::watched_json::{watch_keymap_file, watch_settings_file, WatchedJsonFile}; use theme::ThemeRegistry; -use util::{channel::RELEASE_CHANNEL, paths, ResultExt, StaffMode, TryFutureExt}; +#[cfg(debug_assertions)] +use util::StaffMode; +use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ self, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile, OpenPaths, Workspace, }; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 7be2818b61..16b5413fda 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -23,8 +23,7 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, Platform, PromptLevel, Task, TitlebarOptions, ViewContext, - WindowKind, + AssetSource, AsyncAppContext, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind, }; use language::Rope; use lazy_static::lazy_static; @@ -35,7 +34,7 @@ use search::{BufferSearchBar, ProjectSearchBar}; use serde::Deserialize; use serde_json::to_string_pretty; use settings::{keymap_file_json_schema, settings_file_json_schema, Settings}; -use std::{borrow::Cow, env, path::Path, process::Command, str, sync::Arc}; +use std::{borrow::Cow, env, path::Path, str, sync::Arc}; use util::{channel::ReleaseChannel, paths, ResultExt, StaffMode}; use uuid::Uuid; pub use workspace; @@ -130,9 +129,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { } }, ); - cx.add_global_action(|_: &Quit, cx| { - quit(cx).detach_and_log_err(cx); - }); + cx.add_global_action(quit); cx.add_global_action(restart); cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { @@ -408,30 +405,10 @@ pub fn build_window_options( } fn restart(_: &Restart, cx: &mut gpui::MutableAppContext) { - let cli_process = cx - .platform() - .path_for_auxiliary_executable("cli") - .log_err() - .and_then(|path| { - Command::new(path) - .args(["--restart-from", &format!("{}", std::process::id())]) - .spawn() - .log_err() - }); - - cx.spawn(|mut cx| async move { - let did_quit = cx.update(quit).await?; - if !did_quit { - if let Some(mut cli_process) = cli_process { - cli_process.kill().log_err(); - } - } - Ok::<(), anyhow::Error>(()) - }) - .detach_and_log_err(cx); + cx.platform().restart(); } -fn quit(cx: &mut gpui::MutableAppContext) -> Task> { +fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { let mut workspaces = cx .window_ids() .filter_map(|window_id| cx.root_view::(window_id)) @@ -454,7 +431,7 @@ fn quit(cx: &mut gpui::MutableAppContext) -> Task> { .next() .await; if answer != Some(0) { - return Ok(false); + return Ok(()); } } @@ -466,12 +443,13 @@ fn quit(cx: &mut gpui::MutableAppContext) -> Task> { }) .await? { - return Ok(false); + return Ok(()); } } cx.platform().quit(); - anyhow::Ok(true) + anyhow::Ok(()) }) + .detach_and_log_err(cx); } fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { From a5ad2f544ec5eeba53252a13873b2f8a838c2dae Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 16 Feb 2023 16:51:57 -0800 Subject: [PATCH 162/180] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff9b0fba15..b9c12abea2 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ If you trigger `cmd-alt-i`, Zed will copy a JSON representation of the current w ### Licensing -We use `[cargo-about](https://github.com/EmbarkStudios/cargo-about)` to automatically comply with open source licenses. If CI is failing, check the following: +We use [`cargo-about`](https://github.com/EmbarkStudios/cargo-about) to automatically comply with open source licenses. If CI is failing, check the following: - Is it showing a `no license specified` error for a crate you've created? If so, add `publish = false` under `[package]` in your crate's Cargo.toml. - Is the error `failed to satisfy license requirements` for a dependency? If so, first determine what license the project has and whether this system is sufficient to comply with this license's requirements. If you're unsure, ask a lawyer. Once you've verified that this system is acceptable add the license's SPDX identifier to the `accepted` array in `script/licenses/zed-licenses.toml`. From ff2fb06b2cc89354449c5b6a54dde9d650c741c3 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 17 Feb 2023 10:30:28 -0800 Subject: [PATCH 163/180] Used the pre-existing app_path call in the GPUI platform --- crates/gpui/src/platform/mac/platform.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 3db7c6eedc..c2887eeb23 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -810,25 +810,13 @@ impl platform::Platform for MacPlatform { let path = std::env::current_exe(); #[cfg(not(debug_assertions))] - let path = unsafe { - let bundle: id = NSBundle::mainBundle(); - if bundle.is_null() { - std::env::current_exe() - } else { - let path: id = msg_send![bundle, bundlePath]; - let path: *mut c_char = msg_send![path, UTF8String]; - - Ok(PathBuf::from(OsStr::from_bytes( - CStr::from_ptr(path).to_bytes(), - ))) - } - }; + let path = self.app_path().or_else(|_| std::env::current_exe()); let command = path.and_then(|path| Command::new("/usr/bin/open").arg(path).spawn()); match command { Err(err) => log::error!("Unable to restart application {}", err), - Ok(_) => self.quit(), + Ok(_child) => self.quit(), } } } From 31dac39e3453cb8e20e68336d3e6eef57710f846 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 17 Feb 2023 11:12:57 -0800 Subject: [PATCH 164/180] Fix assignment of language to formerly-untitled buffers When lazy-loading a language, check if it matches plain text buffers. Co-authored-by: Nathan Sobo --- crates/project/src/project.rs | 16 ++++++++++------ crates/project/src/project_tests.rs | 29 +++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ec6a99edec..b1867bb623 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -554,11 +554,13 @@ impl Project { }); } - let languages = Arc::new(LanguageRegistry::test()); + let mut languages = LanguageRegistry::test(); + languages.set_executor(cx.background()); let http_client = client::test::FakeHttpClient::with_404_response(); let client = cx.update(|cx| client::Client::new(http_client.clone(), cx)); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - let project = cx.update(|cx| Project::local(client, user_store, languages, fs, cx)); + let project = + cx.update(|cx| Project::local(client, user_store, Arc::new(languages), fs, cx)); for path in root_paths { let (tree, _) = project .update(cx, |project, cx| { @@ -1789,20 +1791,22 @@ impl Project { while let Some(()) = subscription.next().await { if let Some(project) = project.upgrade(&cx) { project.update(&mut cx, |project, cx| { - let mut buffers_without_language = Vec::new(); + let mut plain_text_buffers = Vec::new(); let mut buffers_with_unknown_injections = Vec::new(); for buffer in project.opened_buffers.values() { if let Some(handle) = buffer.upgrade(cx) { let buffer = &handle.read(cx); - if buffer.language().is_none() { - buffers_without_language.push(handle); + if buffer.language().is_none() + || buffer.language() == Some(&*language::PLAIN_TEXT) + { + plain_text_buffers.push(handle); } else if buffer.contains_unknown_injections() { buffers_with_unknown_injections.push(handle); } } } - for buffer in buffers_without_language { + for buffer in plain_text_buffers { project.assign_language_to_buffer(&buffer, cx); project.register_buffer_with_language_server(&buffer, cx); } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index c9e159f391..0ffa2553cd 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2130,6 +2130,20 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) { fs.insert_tree("/dir", json!({})).await; let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + let languages = project.read_with(cx, |project, _| project.languages().clone()); + languages.register( + "/some/path", + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".into()], + ..Default::default() + }, + tree_sitter_rust::language(), + None, + |_| Default::default(), + ); + let buffer = project.update(cx, |project, cx| { project.create_buffer("", None, cx).unwrap() }); @@ -2137,23 +2151,30 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) { buffer.edit([(0..0, "abc")], None, cx); assert!(buffer.is_dirty()); assert!(!buffer.has_conflict()); + assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text"); }); project .update(cx, |project, cx| { - project.save_buffer_as(buffer.clone(), "/dir/file1".into(), cx) + project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx) }) .await .unwrap(); - assert_eq!(fs.load(Path::new("/dir/file1")).await.unwrap(), "abc"); + assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc"); + + cx.foreground().run_until_parked(); buffer.read_with(cx, |buffer, cx| { - assert_eq!(buffer.file().unwrap().full_path(cx), Path::new("dir/file1")); + assert_eq!( + buffer.file().unwrap().full_path(cx), + Path::new("dir/file1.rs") + ); assert!(!buffer.is_dirty()); assert!(!buffer.has_conflict()); + assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust"); }); let opened_buffer = project .update(cx, |project, cx| { - project.open_local_buffer("/dir/file1", cx) + project.open_local_buffer("/dir/file1.rs", cx) }) .await .unwrap(); From eebce28b32fa3e3bafae779bed3895ba9fc9f865 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 17 Feb 2023 12:38:04 -0800 Subject: [PATCH 165/180] Respect UpdateBufferFile messages on guest buffers without file Co-authored-by: Nathan Sobo --- crates/collab/src/tests/integration_tests.rs | 27 ++++++++++++ crates/language/src/buffer.rs | 45 ++++++++++---------- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index ff9872f31f..fd67dad2e5 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2313,6 +2313,33 @@ async fn test_propagate_saves_and_fs_changes( assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js")); assert_eq!(&*buffer.language().unwrap().name(), "JavaScript"); }); + + let new_buffer_a = project_a + .update(cx_a, |p, cx| p.create_buffer("", None, cx)) + .unwrap(); + let new_buffer_id = new_buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id()); + let new_buffer_b = project_b + .update(cx_b, |p, cx| p.open_buffer_by_id(new_buffer_id, cx)) + .await + .unwrap(); + new_buffer_b.read_with(cx_b, |buffer, _| { + assert!(buffer.file().is_none()); + }); + + project_a + .update(cx_a, |project, cx| { + project.save_buffer_as(new_buffer_a, "/a/file3.rs".into(), cx) + }) + .await + .unwrap(); + + deterministic.run_until_parked(); + new_buffer_b.read_with(cx_b, |buffer, _| { + assert_eq!( + buffer.file().unwrap().path().as_ref(), + Path::new("file3.rs") + ); + }); } #[gpui::test(iterations = 10)] diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index f073fc4760..c59ec89155 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -661,36 +661,35 @@ impl Buffer { new_file: Arc, cx: &mut ModelContext, ) -> Task<()> { - let old_file = if let Some(file) = self.file.as_ref() { - file - } else { - return Task::ready(()); - }; let mut file_changed = false; let mut task = Task::ready(()); - if new_file.path() != old_file.path() { - file_changed = true; - } - - if new_file.is_deleted() { - if !old_file.is_deleted() { + if let Some(old_file) = self.file.as_ref() { + if new_file.path() != old_file.path() { file_changed = true; - if !self.is_dirty() { - cx.emit(Event::DirtyChanged); + } + + if new_file.is_deleted() { + if !old_file.is_deleted() { + file_changed = true; + if !self.is_dirty() { + cx.emit(Event::DirtyChanged); + } + } + } else { + let new_mtime = new_file.mtime(); + if new_mtime != old_file.mtime() { + file_changed = true; + + if !self.is_dirty() { + let reload = self.reload(cx).log_err().map(drop); + task = cx.foreground().spawn(reload); + } } } } else { - let new_mtime = new_file.mtime(); - if new_mtime != old_file.mtime() { - file_changed = true; - - if !self.is_dirty() { - let reload = self.reload(cx).log_err().map(drop); - task = cx.foreground().spawn(reload); - } - } - } + file_changed = true; + }; if file_changed { self.file_update_count += 1; From 57a7ff9a6fce4797ebe13533d0ba911ff832fe31 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 17 Feb 2023 14:52:36 -0800 Subject: [PATCH 166/180] fix vim percent motion to better match the docs and observed behavior --- crates/editor/src/multi_buffer.rs | 84 ++++++++++++------- .../src/test/editor_lsp_test_context.rs | 7 ++ crates/language/src/buffer.rs | 19 ++--- crates/language/src/buffer_tests.rs | 4 +- crates/vim/src/motion.rs | 58 ++++++++++--- crates/vim/src/normal.rs | 53 ++++++++---- crates/vim/test_data/test_o.json | 2 +- crates/vim/test_data/test_percent.json | 1 + 8 files changed, 150 insertions(+), 78 deletions(-) create mode 100644 crates/vim/test_data/test_percent.json diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 7cc0031c4f..ad661b84ee 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2630,6 +2630,9 @@ impl MultiBufferSnapshot { self.parse_count } + /// Returns the smallest enclosing bracket ranges containing the given range or + /// None if no brackets contain range or the range is not contained in a single + /// excerpt pub fn innermost_enclosing_bracket_ranges( &self, range: Range, @@ -2657,46 +2660,59 @@ impl MultiBufferSnapshot { result } - /// Returns enclosinng bracket ranges containing the given range or returns None if the range is + /// Returns enclosing bracket ranges containing the given range or returns None if the range is /// not contained in a single excerpt pub fn enclosing_bracket_ranges<'a, T: ToOffset>( &'a self, range: Range, ) -> Option, Range)> + 'a> { let range = range.start.to_offset(self)..range.end.to_offset(self); - self.excerpt_containing(range.clone()) - .map(|(excerpt, excerpt_offset)| { - let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer); - let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len; - let start_in_buffer = - excerpt_buffer_start + range.start.saturating_sub(excerpt_offset); - let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset); + self.bracket_ranges(range.clone()).map(|range_pairs| { + range_pairs + .filter(move |(open, close)| open.start <= range.start && close.end >= range.end) + }) + } - excerpt - .buffer - .enclosing_bracket_ranges(start_in_buffer..end_in_buffer) - .filter_map(move |(start_bracket_range, end_bracket_range)| { - if start_bracket_range.start < excerpt_buffer_start - || end_bracket_range.end > excerpt_buffer_end - { - return None; - } + /// Returns bracket range pairs overlapping the given `range` or returns None if the `range` is + /// not contained in a single excerpt + pub fn bracket_ranges<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> Option, Range)> + 'a> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + let excerpt = self.excerpt_containing(range.clone()); + excerpt.map(|(excerpt, excerpt_offset)| { + let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len; - let mut start_bracket_range = start_bracket_range.clone(); - start_bracket_range.start = - excerpt_offset + (start_bracket_range.start - excerpt_buffer_start); - start_bracket_range.end = - excerpt_offset + (start_bracket_range.end - excerpt_buffer_start); + let start_in_buffer = excerpt_buffer_start + range.start.saturating_sub(excerpt_offset); + let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset); - let mut end_bracket_range = end_bracket_range.clone(); - end_bracket_range.start = - excerpt_offset + (end_bracket_range.start - excerpt_buffer_start); - end_bracket_range.end = - excerpt_offset + (end_bracket_range.end - excerpt_buffer_start); - Some((start_bracket_range, end_bracket_range)) - }) - }) + excerpt + .buffer + .bracket_ranges(start_in_buffer..end_in_buffer) + .filter_map(move |(start_bracket_range, end_bracket_range)| { + if start_bracket_range.start < excerpt_buffer_start + || end_bracket_range.end > excerpt_buffer_end + { + return None; + } + + let mut start_bracket_range = start_bracket_range.clone(); + start_bracket_range.start = + excerpt_offset + (start_bracket_range.start - excerpt_buffer_start); + start_bracket_range.end = + excerpt_offset + (start_bracket_range.end - excerpt_buffer_start); + + let mut end_bracket_range = end_bracket_range.clone(); + end_bracket_range.start = + excerpt_offset + (end_bracket_range.start - excerpt_buffer_start); + end_bracket_range.end = + excerpt_offset + (end_bracket_range.end - excerpt_buffer_start); + Some((start_bracket_range, end_bracket_range)) + }) + }) } pub fn diagnostics_update_count(&self) -> usize { @@ -2945,10 +2961,14 @@ impl MultiBufferSnapshot { let range = range.start.to_offset(self)..range.end.to_offset(self); let mut cursor = self.excerpts.cursor::(); - cursor.seek(&range.start, Bias::Right, &()); + cursor.seek(&dbg!(range.start), Bias::Right, &()); let start_excerpt = cursor.item(); - cursor.seek(&range.end, Bias::Right, &()); + if range.start == range.end { + return start_excerpt.map(|excerpt| (excerpt, *cursor.start())); + } + + cursor.seek(&dbg!(range.end), Bias::Right, &()); let end_excerpt = cursor.item(); start_excerpt diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 938de169a7..345709abf3 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -139,6 +139,13 @@ impl<'a> EditorLspTestContext<'a> { (_ "<" ">" @end) @indent (_ "{" "}" @end) @indent (_ "(" ")" @end) @indent"#})), + brackets: Some(Cow::from(indoc! {r#" + ("(" @open ")" @close) + ("[" @open "]" @close) + ("{" @open "}" @close) + ("<" @open ">" @close) + ("\"" @open "\"" @close) + (closure_parameters "|" @open "|" @close)"#})), ..Default::default() }) .expect("Could not parse queries"); diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 2119f86bbd..5f7dd97049 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2346,18 +2346,18 @@ impl BufferSnapshot { Some(items) } - pub fn enclosing_bracket_ranges<'a, T: ToOffset>( + /// Returns bracket range pairs overlapping `range` + pub fn bracket_ranges<'a, T: ToOffset>( &'a self, range: Range, ) -> impl Iterator, Range)> + 'a { // Find bracket pairs that *inclusively* contain the given range. - let range = range.start.to_offset(self)..range.end.to_offset(self); + let range = range.start.to_offset(self).saturating_sub(1) + ..self.len().min(range.end.to_offset(self) + 1); - let mut matches = self.syntax.matches( - range.start.saturating_sub(1)..self.len().min(range.end + 1), - &self.text, - |grammar| grammar.brackets_config.as_ref().map(|c| &c.query), - ); + let mut matches = self.syntax.matches(range, &self.text, |grammar| { + grammar.brackets_config.as_ref().map(|c| &c.query) + }); let configs = matches .grammars() .iter() @@ -2380,11 +2380,6 @@ impl BufferSnapshot { matches.advance(); let Some((open, close)) = open.zip(close) else { continue }; - - if open.start > range.start || close.end < range.end { - continue; - } - return Some((open, close)); } None diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 4d2c9670c6..a24c7e227f 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -2072,9 +2072,7 @@ fn assert_enclosing_bracket_pairs( .collect::>(); assert_set_eq!( - buffer - .enclosing_bracket_ranges(selection_range) - .collect::>(), + buffer.bracket_ranges(selection_range).collect::>(), bracket_pairs ); } diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 7c821d6fec..25188a466c 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use editor::{ char_kind, display_map::{DisplaySnapshot, ToDisplayPoint}, - movement, Bias, CharKind, DisplayPoint, + movement, Bias, CharKind, DisplayPoint, ToOffset, }; use gpui::{actions, impl_actions, MutableAppContext}; use language::{Point, Selection, SelectionGoal}; @@ -450,19 +450,53 @@ fn end_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) -> D map.clip_point(new_point, Bias::Left) } -fn matching(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { - let offset = point.to_offset(map, Bias::Left); - if let Some((open_range, close_range)) = map - .buffer_snapshot - .innermost_enclosing_bracket_ranges(offset..offset) - { - if open_range.contains(&offset) { - close_range.start.to_display_point(map) - } else { - open_range.start.to_display_point(map) +fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint { + // https://github.com/vim/vim/blob/1d87e11a1ef201b26ed87585fba70182ad0c468a/runtime/doc/motion.txt#L1200 + let point = display_point.to_point(map); + let offset = point.to_offset(&map.buffer_snapshot); + + // Ensure the range is contained by the current line. + let mut line_end = map.next_line_boundary(point).0; + if line_end == point { + line_end = map.max_point().to_point(map); + } + line_end.column = line_end.column.saturating_sub(1); + + let line_range = map.prev_line_boundary(point).0..line_end; + let ranges = map.buffer_snapshot.bracket_ranges(line_range.clone()); + if let Some(ranges) = ranges { + let line_range = line_range.start.to_offset(&map.buffer_snapshot) + ..line_range.end.to_offset(&map.buffer_snapshot); + let mut closest_pair_destination = None; + let mut closest_distance = usize::MAX; + + for (open_range, close_range) in ranges { + if open_range.start >= offset && line_range.contains(&open_range.start) { + let distance = open_range.start - offset; + if distance < closest_distance { + closest_pair_destination = Some(close_range.start); + closest_distance = distance; + continue; + } + } + + if close_range.start >= offset && line_range.contains(&close_range.start) { + let distance = close_range.start - offset; + if distance < closest_distance { + closest_pair_destination = Some(open_range.start); + closest_distance = distance; + continue; + } + } + + continue; } + + closest_pair_destination + .map(|destination| destination.to_display_point(map)) + .unwrap_or(display_point) } else { - point + display_point } } diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 2dd2d489a5..0cac45fd18 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -824,17 +824,34 @@ mod test { ˇ brown fox"}) .await; - cx.assert(indoc! {" + + cx.assert_manual( + indoc! {" fn test() { println!(ˇ); - } - "}) - .await; - cx.assert(indoc! {" + }"}, + Mode::Normal, + indoc! {" + fn test() { + println!(); + ˇ + }"}, + Mode::Insert, + ); + + cx.assert_manual( + indoc! {" fn test(ˇ) { println!(); - }"}) - .await; + }"}, + Mode::Normal, + indoc! {" + fn test() { + ˇ + println!(); + }"}, + Mode::Insert, + ); } #[gpui::test] @@ -996,14 +1013,14 @@ mod test { #[gpui::test] async fn test_capital_f_and_capital_t(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; - for count in 1..=3 { - let test_case = indoc! {" - ˇaaaˇbˇ ˇbˇ ˇbˇbˇ aˇaaˇbaaa - ˇ ˇbˇaaˇa ˇbˇbˇb - ˇ - ˇb + let test_case = indoc! {" + ˇaaaˇbˇ ˇbˇ ˇbˇbˇ aˇaaˇbaaa + ˇ ˇbˇaaˇa ˇbˇbˇb + ˇ + ˇb "}; + for count in 1..=3 { cx.assert_binding_matches_all([&count.to_string(), "shift-f", "b"], test_case) .await; @@ -1014,10 +1031,10 @@ mod test { #[gpui::test] async fn test_percent(cx: &mut gpui::TestAppContext) { - let mut cx = NeovimBackedTestContext::new(cx).await; - for count in 1..=2 { - // let test_case = indoc! {" - // "} - } + let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]); + cx.assert_all("ˇconsole.logˇ(ˇvaˇrˇ)ˇ;").await; + cx.assert_all("ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;") + .await; + cx.assert_all("let result = curried_funˇ(ˇ)ˇ(ˇ)ˇ;").await; } } diff --git a/crates/vim/test_data/test_o.json b/crates/vim/test_data/test_o.json index 08bea7cae8..fa1a400bc0 100644 --- a/crates/vim/test_data/test_o.json +++ b/crates/vim/test_data/test_o.json @@ -1 +1 @@ -[{"Text":"\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n\nbrown fox\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\n\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over\n"},{"Mode":"Insert"},{"Selection":{"start":[3,0],"end":[3,0]}},{"Mode":"Insert"},{"Text":"The quick\n\n\nbrown fox"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"fn test() {\n println!();\n \n}\n"},{"Mode":"Insert"},{"Selection":{"start":[2,4],"end":[2,4]}},{"Mode":"Insert"},{"Text":"fn test() {\n\n println!();\n}"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}] \ No newline at end of file +[{"Text":"\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n\nbrown fox\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\n\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over\n"},{"Mode":"Insert"},{"Selection":{"start":[3,0],"end":[3,0]}},{"Mode":"Insert"},{"Text":"The quick\n\n\nbrown fox"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_percent.json b/crates/vim/test_data/test_percent.json new file mode 100644 index 0000000000..9dc0fc655b --- /dev/null +++ b/crates/vim/test_data/test_percent.json @@ -0,0 +1 @@ +[{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,15],"end":[0,15]}},{"Mode":"Normal"},{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,15],"end":[0,15]}},{"Mode":"Normal"},{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Normal"},{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Normal"},{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Normal"},{"Text":"console.log(var);"},{"Mode":"Normal"},{"Selection":{"start":[0,16],"end":[0,16]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,28],"end":[0,28]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,28],"end":[0,28]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,27],"end":[0,27]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,27],"end":[0,27]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,19],"end":[0,19]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,19],"end":[0,19]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,19],"end":[0,19]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Normal"},{"Text":"console.log('var', [1, 2, 3]);"},{"Mode":"Normal"},{"Selection":{"start":[0,29],"end":[0,29]}},{"Mode":"Normal"},{"Text":"let result = curried_fun()();"},{"Mode":"Normal"},{"Selection":{"start":[0,25],"end":[0,25]}},{"Mode":"Normal"},{"Text":"let result = curried_fun()();"},{"Mode":"Normal"},{"Selection":{"start":[0,24],"end":[0,24]}},{"Mode":"Normal"},{"Text":"let result = curried_fun()();"},{"Mode":"Normal"},{"Selection":{"start":[0,27],"end":[0,27]}},{"Mode":"Normal"},{"Text":"let result = curried_fun()();"},{"Mode":"Normal"},{"Selection":{"start":[0,26],"end":[0,26]}},{"Mode":"Normal"},{"Text":"let result = curried_fun()();"},{"Mode":"Normal"},{"Selection":{"start":[0,28],"end":[0,28]}},{"Mode":"Normal"}] \ No newline at end of file From 76975c29a907b95dce04454554def0daee24c87b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 17 Feb 2023 15:29:54 -0800 Subject: [PATCH 167/180] Refactor: split Project::format logic into local and remote cases --- crates/project/src/project.rs | 212 +++++++++++++++++----------------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b1867bb623..faf43ddcec 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2822,126 +2822,126 @@ impl Project { trigger: FormatTrigger, cx: &mut ModelContext, ) -> Task> { - let mut local_buffers = Vec::new(); - let mut remote_buffers = None; - for buffer_handle in buffers { - let buffer = buffer_handle.read(cx); - if let Some(file) = File::from_dyn(buffer.file()) { - if let Some(buffer_abs_path) = file.as_local().map(|f| f.abs_path(cx)) { - if let Some((_, server)) = self.language_server_for_buffer(buffer, cx) { - local_buffers.push((buffer_handle, buffer_abs_path, server.clone())); - } - } else { - remote_buffers.get_or_insert(Vec::new()).push(buffer_handle); - } - } else { - return Task::ready(Ok(Default::default())); - } - } + if self.is_local() { + let mut buffers_with_paths_and_servers = buffers + .into_iter() + .filter_map(|buffer_handle| { + let buffer = buffer_handle.read(cx); + let file = File::from_dyn(buffer.file())?; + let buffer_abs_path = file.as_local()?.abs_path(cx); + let (_, server) = self.language_server_for_buffer(buffer, cx)?; + Some((buffer_handle, buffer_abs_path, server.clone())) + }) + .collect::>(); - let remote_buffers = self.remote_id().zip(remote_buffers); - let client = self.client.clone(); - - cx.spawn(|this, mut cx| async move { - let mut project_transaction = ProjectTransaction::default(); - - if let Some((project_id, remote_buffers)) = remote_buffers { - let response = client - .request(proto::FormatBuffers { - project_id, - trigger: trigger as i32, - buffer_ids: remote_buffers - .iter() - .map(|buffer| buffer.read_with(&cx, |buffer, _| buffer.remote_id())) - .collect(), - }) - .await? - .transaction - .ok_or_else(|| anyhow!("missing transaction"))?; - project_transaction = this - .update(&mut cx, |this, cx| { - this.deserialize_project_transaction(response, push_to_history, cx) - }) - .await?; - } - - // Do not allow multiple concurrent formatting requests for the - // same buffer. - this.update(&mut cx, |this, _| { - local_buffers - .retain(|(buffer, _, _)| this.buffers_being_formatted.insert(buffer.id())); - }); - let _cleanup = defer({ - let this = this.clone(); - let mut cx = cx.clone(); - let local_buffers = &local_buffers; - move || { - this.update(&mut cx, |this, _| { - for (buffer, _, _) in local_buffers { - this.buffers_being_formatted.remove(&buffer.id()); - } - }); - } - }); - - for (buffer, buffer_abs_path, language_server) in &local_buffers { - let (format_on_save, formatter, tab_size) = buffer.read_with(&cx, |buffer, cx| { - let settings = cx.global::(); - let language_name = buffer.language().map(|language| language.name()); - ( - settings.format_on_save(language_name.as_deref()), - settings.formatter(language_name.as_deref()), - settings.tab_size(language_name.as_deref()), - ) + cx.spawn(|this, mut cx| async move { + // Do not allow multiple concurrent formatting requests for the + // same buffer. + this.update(&mut cx, |this, _| { + buffers_with_paths_and_servers + .retain(|(buffer, _, _)| this.buffers_being_formatted.insert(buffer.id())); }); - let transaction = match (formatter, format_on_save) { - (_, FormatOnSave::Off) if trigger == FormatTrigger::Save => continue, + let _cleanup = defer({ + let this = this.clone(); + let mut cx = cx.clone(); + let local_buffers = &buffers_with_paths_and_servers; + move || { + this.update(&mut cx, |this, _| { + for (buffer, _, _) in local_buffers { + this.buffers_being_formatted.remove(&buffer.id()); + } + }); + } + }); - (Formatter::LanguageServer, FormatOnSave::On | FormatOnSave::Off) - | (_, FormatOnSave::LanguageServer) => Self::format_via_lsp( - &this, - &buffer, - &buffer_abs_path, - &language_server, - tab_size, - &mut cx, - ) - .await - .context("failed to format via language server")?, + let mut project_transaction = ProjectTransaction::default(); + for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers { + let (format_on_save, formatter, tab_size) = + buffer.read_with(&cx, |buffer, cx| { + let settings = cx.global::(); + let language_name = buffer.language().map(|language| language.name()); + ( + settings.format_on_save(language_name.as_deref()), + settings.formatter(language_name.as_deref()), + settings.tab_size(language_name.as_deref()), + ) + }); - ( - Formatter::External { command, arguments }, - FormatOnSave::On | FormatOnSave::Off, - ) - | (_, FormatOnSave::External { command, arguments }) => { - Self::format_via_external_command( + let transaction = match (formatter, format_on_save) { + (_, FormatOnSave::Off) if trigger == FormatTrigger::Save => continue, + + (Formatter::LanguageServer, FormatOnSave::On | FormatOnSave::Off) + | (_, FormatOnSave::LanguageServer) => Self::format_via_lsp( + &this, &buffer, &buffer_abs_path, - &command, - &arguments, + &language_server, + tab_size, &mut cx, ) .await - .context(format!( - "failed to format via external command {:?}", - command - ))? - } - }; + .context("failed to format via language server")?, - if let Some(transaction) = transaction { - if !push_to_history { - buffer.update(&mut cx, |buffer, _| { - buffer.forget_transaction(transaction.id) - }); + ( + Formatter::External { command, arguments }, + FormatOnSave::On | FormatOnSave::Off, + ) + | (_, FormatOnSave::External { command, arguments }) => { + Self::format_via_external_command( + &buffer, + &buffer_abs_path, + &command, + &arguments, + &mut cx, + ) + .await + .context(format!( + "failed to format via external command {:?}", + command + ))? + } + }; + + if let Some(transaction) = transaction { + if !push_to_history { + buffer.update(&mut cx, |buffer, _| { + buffer.forget_transaction(transaction.id) + }); + } + project_transaction.0.insert(buffer.clone(), transaction); } - project_transaction.0.insert(buffer.clone(), transaction); } - } - Ok(project_transaction) - }) + Ok(project_transaction) + }) + } else { + let remote_id = self.remote_id(); + let client = self.client.clone(); + cx.spawn(|this, mut cx| async move { + let mut project_transaction = ProjectTransaction::default(); + if let Some(project_id) = remote_id { + let response = client + .request(proto::FormatBuffers { + project_id, + trigger: trigger as i32, + buffer_ids: buffers + .iter() + .map(|buffer| buffer.read_with(&cx, |buffer, _| buffer.remote_id())) + .collect(), + }) + .await? + .transaction + .ok_or_else(|| anyhow!("missing transaction"))?; + project_transaction = this + .update(&mut cx, |this, cx| { + this.deserialize_project_transaction(response, push_to_history, cx) + }) + .await?; + } + Ok(project_transaction) + }) + } } async fn format_via_lsp( From de6eb00e2bca6acf7a0fb25e97b219b20d65f1a1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 17 Feb 2023 15:41:39 -0800 Subject: [PATCH 168/180] Start work on making save and save_as code paths more similar --- crates/editor/src/items.rs | 35 +++++++++---------------------- crates/editor/src/multi_buffer.rs | 15 ------------- crates/project/src/project.rs | 33 ++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 41 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index c52d00b7e3..2c8503b8c7 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -2,12 +2,10 @@ use crate::{ display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, - FORMAT_TIMEOUT, }; use anyhow::{anyhow, Context, Result}; use collections::HashSet; use futures::future::try_join_all; -use futures::FutureExt; use gpui::{ elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -16,7 +14,7 @@ use language::{ proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, SelectionGoal, }; -use project::{FormatTrigger, Item as _, Project, ProjectPath}; +use project::{Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; use smallvec::SmallVec; @@ -613,30 +611,17 @@ impl Item for Editor { let buffer = self.buffer().clone(); let buffers = buffer.read(cx).all_buffers(); - let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse(); - let format = project.update(cx, |project, cx| { - project.format(buffers, true, FormatTrigger::Save, cx) - }); + let save = project.update(cx, |project, cx| project.save_buffers(buffers, cx)); cx.spawn(|_, mut cx| async move { - let transaction = futures::select_biased! { - _ = timeout => { - log::warn!("timed out waiting for formatting"); - None - } - transaction = format.log_err().fuse() => transaction, - }; - - buffer - .update(&mut cx, |buffer, cx| { - if let Some(transaction) = transaction { - if !buffer.is_singleton() { - buffer.push_transaction(&transaction.0); - } + let (format_transaction, save) = save.await; + buffer.update(&mut cx, |buffer, _| { + if let Some(transaction) = format_transaction { + if !buffer.is_singleton() { + buffer.push_transaction(&transaction.0); } - - buffer.save(cx) - }) - .await?; + } + }); + save.await?; Ok(()) }) } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 908e5c827d..9e0e22ad40 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1,7 +1,6 @@ mod anchor; pub use anchor::{Anchor, AnchorRangeExt}; -use anyhow::Result; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet}; use futures::{channel::mpsc, SinkExt}; @@ -1279,20 +1278,6 @@ impl MultiBuffer { .map(|state| state.buffer.clone()) } - pub fn save(&mut self, cx: &mut ModelContext) -> Task> { - let mut save_tasks = Vec::new(); - for BufferState { buffer, .. } in self.buffers.borrow().values() { - save_tasks.push(buffer.update(cx, |buffer, cx| buffer.save(cx))); - } - - cx.spawn(|_, _| async move { - for save in save_tasks { - save.await?; - } - Ok(()) - }) - } - pub fn is_completion_trigger(&self, position: T, text: &str, cx: &AppContext) -> bool where T: ToOffset, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index faf43ddcec..7eb2b61990 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -12,7 +12,7 @@ use clock::ReplicaId; use collections::{hash_map, BTreeMap, HashMap, HashSet}; use futures::{ channel::{mpsc, oneshot}, - future::Shared, + future::{try_join_all, Shared}, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, }; use gpui::{ @@ -1428,6 +1428,37 @@ impl Project { } } + pub fn save_buffers( + &mut self, + buffers: HashSet>, + cx: &mut ModelContext, + ) -> Task<(Option, Task>)> { + const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); + + let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse(); + let format = self.format(buffers.clone(), true, FormatTrigger::Save, cx); + cx.spawn(|_, cx| async move { + let transaction = futures::select_biased! { + _ = timeout => { + log::warn!("timed out waiting for formatting"); + None + } + transaction = format.log_err().fuse() => transaction, + }; + + ( + transaction, + cx.spawn(|mut cx| async move { + let save_tasks = buffers + .iter() + .map(|buffer| buffer.update(&mut cx, |buffer, cx| buffer.save(cx))); + try_join_all(save_tasks).await?; + Ok(()) + }), + ) + }) + } + pub fn save_buffer_as( &mut self, buffer: ModelHandle, From 5e4d113308b658c1d1bef4dcfd9625e3b190ea6b Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 17 Feb 2023 17:19:23 -0800 Subject: [PATCH 169/180] fix bracket ranges failing test --- crates/language/src/buffer.rs | 10 ++++++++-- crates/language/src/buffer_tests.rs | 7 ++++--- crates/util/Cargo.toml | 2 +- crates/util/src/{lib.rs => util.rs} | 0 crates/workspace/src/workspace.rs | 19 ++++++++++++++++++- 5 files changed, 31 insertions(+), 7 deletions(-) rename crates/util/src/{lib.rs => util.rs} (100%) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5f7dd97049..857c0a063f 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2346,7 +2346,7 @@ impl BufferSnapshot { Some(items) } - /// Returns bracket range pairs overlapping `range` + /// Returns bracket range pairs overlapping or adjacent to `range` pub fn bracket_ranges<'a, T: ToOffset>( &'a self, range: Range, @@ -2355,7 +2355,7 @@ impl BufferSnapshot { let range = range.start.to_offset(self).saturating_sub(1) ..self.len().min(range.end.to_offset(self) + 1); - let mut matches = self.syntax.matches(range, &self.text, |grammar| { + let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| { grammar.brackets_config.as_ref().map(|c| &c.query) }); let configs = matches @@ -2380,6 +2380,12 @@ impl BufferSnapshot { matches.advance(); let Some((open, close)) = open.zip(close) else { continue }; + + let bracket_range = open.start..=close.end; + if !bracket_range.contains(&range.start) && !bracket_range.contains(&range.end) { + continue; + } + return Some((open, close)); } None diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index a24c7e227f..e6e7544763 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -578,7 +578,7 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) { let mut assert = |selection_text, range_markers| { - assert_enclosing_bracket_pairs(selection_text, range_markers, rust_lang(), cx) + assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx) }; assert( @@ -696,7 +696,7 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children( cx: &mut MutableAppContext, ) { let mut assert = |selection_text, bracket_pair_texts| { - assert_enclosing_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx) + assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx) }; assert( @@ -710,6 +710,7 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children( }"}], ); + eprintln!("-----------------------"); // Regression test: even though the parent node of the parentheses (the for loop) does // intersect the given range, the parentheses themselves do not contain the range, so // they should not be returned. Only the curly braces contain the range. @@ -2047,7 +2048,7 @@ fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> Str } // Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers` -fn assert_enclosing_bracket_pairs( +fn assert_bracket_pairs( selection_text: &'static str, bracket_pair_texts: Vec<&'static str>, language: Language, diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 4cbaa382e8..e8c158b637 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" publish = false [lib] +path = "src/util.rs" doctest = false [features] @@ -22,7 +23,6 @@ serde_json = { version = "1.0", features = ["preserve_order"], optional = true } git2 = { version = "0.15", default-features = false, optional = true } dirs = "3.0" - [dev-dependencies] tempdir = { version = "0.3.7" } serde_json = { version = "1.0", features = ["preserve_order"] } diff --git a/crates/util/src/lib.rs b/crates/util/src/util.rs similarity index 100% rename from crates/util/src/lib.rs rename to crates/util/src/util.rs diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8d81ae7f2e..95969de6b0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -101,6 +101,7 @@ actions!( NewTerminal, NewSearch, Feedback, + Restart ] ); @@ -1329,7 +1330,19 @@ impl Workspace { focus_item: bool, cx: &mut ViewContext, ) -> Task, anyhow::Error>> { - let pane = pane.unwrap_or_else(|| self.active_pane().downgrade()); + let pane = pane.unwrap_or_else(|| { + if !self.dock_active() { + self.active_pane().downgrade() + } else { + self.last_active_center_pane.clone().unwrap_or_else(|| { + self.panes + .first() + .expect("There must be an active pane") + .downgrade() + }) + } + }); + let task = self.load_path(path.into(), cx); cx.spawn(|this, mut cx| async move { let (project_entry_id, build_item) = task.await?; @@ -1636,6 +1649,10 @@ impl Workspace { self.dock.pane() } + fn dock_active(&self) -> bool { + &self.active_pane == self.dock.pane() + } + fn project_remote_id_changed(&mut self, remote_id: Option, cx: &mut ViewContext) { if let Some(remote_id) = remote_id { self.remote_entity_subscription = From 3a7cfc39012e395b2b9f098e84f7be4d17a551d7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 17 Feb 2023 16:53:18 -0800 Subject: [PATCH 170/180] Move the save and save_as code paths close together --- crates/collab/src/tests/integration_tests.rs | 4 +- .../src/tests/randomized_integration_tests.rs | 5 +- crates/editor/src/items.rs | 29 +---- crates/language/src/buffer.rs | 36 ----- crates/project/src/project.rs | 63 +++++---- crates/project/src/project_tests.rs | 34 +++-- crates/project/src/worktree.rs | 123 ++++++++++-------- 7 files changed, 125 insertions(+), 169 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index fd67dad2e5..394dcd808e 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2244,7 +2244,7 @@ async fn test_propagate_saves_and_fs_changes( }); // Edit the buffer as the host and concurrently save as guest B. - let save_b = buffer_b.update(cx_b, |buf, cx| buf.save(cx)); + let save_b = cx_b.update(|cx| Project::save_buffer(buffer_b.clone(), cx)); buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], None, cx)); save_b.await.unwrap(); assert_eq!( @@ -2909,7 +2909,7 @@ async fn test_buffer_conflict_after_save( assert!(!buf.has_conflict()); }); - buffer_b.update(cx_b, |buf, cx| buf.save(cx)).await.unwrap(); + cx_b.update(|cx| Project::save_buffer(buffer_b.clone(), cx)).await.unwrap(); cx_a.foreground().forbid_parking(); buffer_b.read_with(cx_b, |buffer_b, _| assert!(!buffer_b.is_dirty())); buffer_b.read_with(cx_b, |buf, _| { diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index 4783957dbb..434006c5c1 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -1064,15 +1064,16 @@ async fn randomly_query_and_mutate_buffers( } } 30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => { - let (requested_version, save) = buffer.update(cx, |buffer, cx| { + let requested_version = buffer.update(cx, |buffer, cx| { log::info!( "{}: saving buffer {} ({:?})", client.username, buffer.remote_id(), buffer.file().unwrap().full_path(cx) ); - (buffer.version(), buffer.save(cx)) + buffer.version() }); + let save = cx.update(|cx| Project::save_buffer(buffer, cx)); let save = cx.background().spawn(async move { let (saved_version, _, _) = save .await diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 2c8503b8c7..aac8701a22 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -608,20 +608,11 @@ impl Item for Editor { cx: &mut ViewContext, ) -> Task> { self.report_event("save editor", cx); - - let buffer = self.buffer().clone(); - let buffers = buffer.read(cx).all_buffers(); - let save = project.update(cx, |project, cx| project.save_buffers(buffers, cx)); + let format = self.perform_format(project.clone(), cx); + let buffers = self.buffer().clone().read(cx).all_buffers(); cx.spawn(|_, mut cx| async move { - let (format_transaction, save) = save.await; - buffer.update(&mut cx, |buffer, _| { - if let Some(transaction) = format_transaction { - if !buffer.is_singleton() { - buffer.push_transaction(&transaction.0); - } - } - }); - save.await?; + format.await?; + cx.update(|cx| Project::save_buffers(buffers, cx)).await?; Ok(()) }) } @@ -1144,7 +1135,6 @@ fn path_for_file<'a>( mod tests { use super::*; use gpui::MutableAppContext; - use language::RopeFingerprint; use std::{ path::{Path, PathBuf}, sync::Arc, @@ -1190,17 +1180,6 @@ mod tests { todo!() } - fn save( - &self, - _: u64, - _: language::Rope, - _: clock::Global, - _: project::LineEnding, - _: &mut MutableAppContext, - ) -> gpui::Task> { - todo!() - } - fn as_any(&self) -> &dyn std::any::Any { todo!() } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index c59ec89155..ad9345a6bd 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -214,15 +214,6 @@ pub trait File: Send + Sync { fn is_deleted(&self) -> bool; - fn save( - &self, - buffer_id: u64, - text: Rope, - version: clock::Global, - line_ending: LineEnding, - cx: &mut MutableAppContext, - ) -> Task>; - fn as_any(&self) -> &dyn Any; fn to_proto(&self) -> rpc::proto::File; @@ -529,33 +520,6 @@ impl Buffer { self.file.as_ref() } - pub fn save( - &mut self, - cx: &mut ModelContext, - ) -> Task> { - let file = if let Some(file) = self.file.as_ref() { - file - } else { - return Task::ready(Err(anyhow!("buffer has no file"))); - }; - let text = self.as_rope().clone(); - let version = self.version(); - let save = file.save( - self.remote_id(), - text, - version, - self.line_ending(), - cx.as_mut(), - ); - cx.spawn(|this, mut cx| async move { - let (version, fingerprint, mtime) = save.await?; - this.update(&mut cx, |this, cx| { - this.did_save(version.clone(), fingerprint, mtime, None, cx); - }); - Ok((version, fingerprint, mtime)) - }) - } - pub fn saved_version(&self) -> &clock::Global { &self.saved_version } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7eb2b61990..3cde06e317 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -28,8 +28,8 @@ use language::{ range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, - Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, - Unclipped, + Operation, Patch, PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16, + Transaction, Unclipped, }; use lsp::{ DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString, @@ -59,7 +59,7 @@ use std::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, - time::{Duration, Instant}, + time::{Duration, Instant, SystemTime}, }; use terminal::{Terminal, TerminalBuilder}; use util::{debug_panic, defer, post_inc, ResultExt, TryFutureExt as _}; @@ -1429,33 +1429,30 @@ impl Project { } pub fn save_buffers( - &mut self, buffers: HashSet>, - cx: &mut ModelContext, - ) -> Task<(Option, Task>)> { - const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); + cx: &mut MutableAppContext, + ) -> Task> { + cx.spawn(|mut cx| async move { + let save_tasks = buffers + .into_iter() + .map(|buffer| cx.update(|cx| Self::save_buffer(buffer, cx))); + try_join_all(save_tasks).await?; + Ok(()) + }) + } - let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse(); - let format = self.format(buffers.clone(), true, FormatTrigger::Save, cx); - cx.spawn(|_, cx| async move { - let transaction = futures::select_biased! { - _ = timeout => { - log::warn!("timed out waiting for formatting"); - None - } - transaction = format.log_err().fuse() => transaction, - }; - - ( - transaction, - cx.spawn(|mut cx| async move { - let save_tasks = buffers - .iter() - .map(|buffer| buffer.update(&mut cx, |buffer, cx| buffer.save(cx))); - try_join_all(save_tasks).await?; - Ok(()) - }), - ) + pub fn save_buffer( + buffer: ModelHandle, + cx: &mut MutableAppContext, + ) -> Task> { + let Some(file) = File::from_dyn(buffer.read(cx).file()) else { + return Task::ready(Err(anyhow!("buffer doesn't have a file"))); + }; + let worktree = file.worktree.clone(); + let path = file.path.clone(); + worktree.update(cx, |worktree, cx| match worktree { + Worktree::Local(worktree) => worktree.save_buffer(buffer, path, cx), + Worktree::Remote(worktree) => worktree.save_buffer(buffer, cx), }) } @@ -1476,11 +1473,9 @@ impl Project { } let (worktree, path) = worktree_task.await?; worktree - .update(&mut cx, |worktree, cx| { - worktree - .as_local_mut() - .unwrap() - .save_buffer_as(buffer.clone(), path, cx) + .update(&mut cx, |worktree, cx| match worktree { + Worktree::Local(worktree) => worktree.save_buffer_as(buffer.clone(), path, cx), + Worktree::Remote(_) => panic!("cannot remote buffers as new files"), }) .await?; this.update(&mut cx, |this, cx| { @@ -5187,7 +5182,7 @@ impl Project { .await; let (saved_version, fingerprint, mtime) = - buffer.update(&mut cx, |buffer, cx| buffer.save(cx)).await?; + cx.update(|cx| Self::save_buffer(buffer, cx)).await?; Ok(proto::BufferSaved { project_id, buffer_id, diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 0ffa2553cd..2a88714b18 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -243,8 +243,7 @@ async fn test_managing_language_servers( ); // Save notifications are reported to all servers. - toml_buffer - .update(cx, |buffer, cx| buffer.save(cx)) + cx.update(|cx| Project::save_buffer(toml_buffer, cx)) .await .unwrap(); assert_eq!( @@ -2083,12 +2082,12 @@ async fn test_save_file(cx: &mut gpui::TestAppContext) { .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) .await .unwrap(); - buffer - .update(cx, |buffer, cx| { - assert_eq!(buffer.text(), "the old contents"); - buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx); - buffer.save(cx) - }) + buffer.update(cx, |buffer, cx| { + assert_eq!(buffer.text(), "the old contents"); + buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx); + }); + + cx.update(|cx| Project::save_buffer(buffer.clone(), cx)) .await .unwrap(); @@ -2112,11 +2111,11 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) { .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) .await .unwrap(); - buffer - .update(cx, |buffer, cx| { - buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx); - buffer.save(cx) - }) + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx); + }); + + cx.update(|cx| Project::save_buffer(buffer.clone(), cx)) .await .unwrap(); @@ -2703,11 +2702,10 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) { }); // Save a file with windows line endings. The file is written correctly. - buffer2 - .update(cx, |buffer, cx| { - buffer.set_text("one\ntwo\nthree\nfour\n", cx); - buffer.save(cx) - }) + buffer2.update(cx, |buffer, cx| { + buffer.set_text("one\ntwo\nthree\nfour\n", cx); + }); + cx.update(|cx| Project::save_buffer(buffer2, cx)) .await .unwrap(); assert_eq!( diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index d743ba2031..5edb224618 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -724,18 +724,55 @@ impl LocalWorktree { }) } + pub fn save_buffer( + &self, + buffer_handle: ModelHandle, + path: Arc, + cx: &mut ModelContext, + ) -> Task> { + let buffer = buffer_handle.read(cx); + + let rpc = self.client.clone(); + let buffer_id = buffer.remote_id(); + let project_id = self.share.as_ref().map(|share| share.project_id); + + let text = buffer.as_rope().clone(); + let fingerprint = text.fingerprint(); + let version = buffer.version(); + let save = self.write_file(path, text, buffer.line_ending(), cx); + + cx.as_mut().spawn(|mut cx| async move { + let mtime = save.await?.mtime; + if let Some(project_id) = project_id { + rpc.send(proto::BufferSaved { + project_id, + buffer_id, + version: serialize_version(&version), + mtime: Some(mtime.into()), + fingerprint: serialize_fingerprint(fingerprint), + })?; + } + buffer_handle.update(&mut cx, |buffer, cx| { + buffer.did_save(version.clone(), fingerprint, mtime, None, cx); + }); + anyhow::Ok((version, fingerprint, mtime)) + }) + } + pub fn save_buffer_as( &self, buffer_handle: ModelHandle, path: impl Into>, cx: &mut ModelContext, ) -> Task> { + let handle = cx.handle(); let buffer = buffer_handle.read(cx); + let text = buffer.as_rope().clone(); let fingerprint = text.fingerprint(); let version = buffer.version(); let save = self.write_file(path, text, buffer.line_ending(), cx); - let handle = cx.handle(); + cx.as_mut().spawn(|mut cx| async move { let entry = save.await?; let file = File { @@ -1085,6 +1122,39 @@ impl RemoteWorktree { self.disconnected = true; } + pub fn save_buffer( + &self, + buffer_handle: ModelHandle, + cx: &mut ModelContext, + ) -> Task> { + let buffer = buffer_handle.read(cx); + let buffer_id = buffer.remote_id(); + let version = buffer.version(); + let rpc = self.client.clone(); + let project_id = self.project_id; + cx.as_mut().spawn(|mut cx| async move { + let response = rpc + .request(proto::SaveBuffer { + project_id, + buffer_id, + version: serialize_version(&version), + }) + .await?; + let version = deserialize_version(response.version); + let fingerprint = deserialize_fingerprint(&response.fingerprint)?; + let mtime = response + .mtime + .ok_or_else(|| anyhow!("missing mtime"))? + .into(); + + buffer_handle.update(&mut cx, |buffer, cx| { + buffer.did_save(version.clone(), fingerprint, mtime, None, cx); + }); + + Ok((version, fingerprint, mtime)) + }) + } + pub fn update_from_remote(&mut self, update: proto::UpdateWorktree) { if let Some(updates_tx) = &self.updates_tx { updates_tx @@ -1859,57 +1929,6 @@ impl language::File for File { self.is_deleted } - fn save( - &self, - buffer_id: u64, - text: Rope, - version: clock::Global, - line_ending: LineEnding, - cx: &mut MutableAppContext, - ) -> Task> { - self.worktree.update(cx, |worktree, cx| match worktree { - Worktree::Local(worktree) => { - let rpc = worktree.client.clone(); - let project_id = worktree.share.as_ref().map(|share| share.project_id); - let fingerprint = text.fingerprint(); - let save = worktree.write_file(self.path.clone(), text, line_ending, cx); - cx.background().spawn(async move { - let entry = save.await?; - if let Some(project_id) = project_id { - rpc.send(proto::BufferSaved { - project_id, - buffer_id, - version: serialize_version(&version), - mtime: Some(entry.mtime.into()), - fingerprint: serialize_fingerprint(fingerprint), - })?; - } - Ok((version, fingerprint, entry.mtime)) - }) - } - Worktree::Remote(worktree) => { - let rpc = worktree.client.clone(); - let project_id = worktree.project_id; - cx.foreground().spawn(async move { - let response = rpc - .request(proto::SaveBuffer { - project_id, - buffer_id, - version: serialize_version(&version), - }) - .await?; - let version = deserialize_version(response.version); - let fingerprint = deserialize_fingerprint(&response.fingerprint)?; - let mtime = response - .mtime - .ok_or_else(|| anyhow!("missing mtime"))? - .into(); - Ok((version, fingerprint, mtime)) - }) - } - }) - } - fn as_any(&self) -> &dyn Any { self } From cdf64b6cad090cdac1b1886328883095addb4995 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 17 Feb 2023 17:08:28 -0800 Subject: [PATCH 171/180] Unify save and save_as for local worktrees This fixes state propagation bugs due to missing RPC calls in save_as. --- crates/collab/src/tests/integration_tests.rs | 18 ++++-- crates/project/src/project.rs | 6 +- crates/project/src/worktree.rs | 59 ++++++++------------ 3 files changed, 42 insertions(+), 41 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 394dcd808e..8b92216734 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2326,19 +2326,27 @@ async fn test_propagate_saves_and_fs_changes( assert!(buffer.file().is_none()); }); + new_buffer_a.update(cx_a, |buffer, cx| { + buffer.edit([(0..0, "ok")], None, cx); + }); project_a .update(cx_a, |project, cx| { - project.save_buffer_as(new_buffer_a, "/a/file3.rs".into(), cx) + project.save_buffer_as(new_buffer_a.clone(), "/a/file3.rs".into(), cx) }) .await .unwrap(); deterministic.run_until_parked(); - new_buffer_b.read_with(cx_b, |buffer, _| { + new_buffer_b.read_with(cx_b, |buffer_b, _| { assert_eq!( - buffer.file().unwrap().path().as_ref(), + buffer_b.file().unwrap().path().as_ref(), Path::new("file3.rs") ); + + new_buffer_a.read_with(cx_a, |buffer_a, _| { + assert_eq!(buffer_b.saved_mtime(), buffer_a.saved_mtime()); + assert_eq!(buffer_b.saved_version(), buffer_a.saved_version()); + }); }); } @@ -2909,7 +2917,9 @@ async fn test_buffer_conflict_after_save( assert!(!buf.has_conflict()); }); - cx_b.update(|cx| Project::save_buffer(buffer_b.clone(), cx)).await.unwrap(); + cx_b.update(|cx| Project::save_buffer(buffer_b.clone(), cx)) + .await + .unwrap(); cx_a.foreground().forbid_parking(); buffer_b.read_with(cx_b, |buffer_b, _| assert!(!buffer_b.is_dirty())); buffer_b.read_with(cx_b, |buf, _| { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3cde06e317..ba5ca82c02 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1451,7 +1451,7 @@ impl Project { let worktree = file.worktree.clone(); let path = file.path.clone(); worktree.update(cx, |worktree, cx| match worktree { - Worktree::Local(worktree) => worktree.save_buffer(buffer, path, cx), + Worktree::Local(worktree) => worktree.save_buffer(buffer, path, false, cx), Worktree::Remote(worktree) => worktree.save_buffer(buffer, cx), }) } @@ -1474,7 +1474,9 @@ impl Project { let (worktree, path) = worktree_task.await?; worktree .update(&mut cx, |worktree, cx| match worktree { - Worktree::Local(worktree) => worktree.save_buffer_as(buffer.clone(), path, cx), + Worktree::Local(worktree) => { + worktree.save_buffer(buffer.clone(), path.into(), true, cx) + } Worktree::Remote(_) => panic!("cannot remote buffers as new files"), }) .await?; diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 5edb224618..9f4446f060 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -728,8 +728,10 @@ impl LocalWorktree { &self, buffer_handle: ModelHandle, path: Arc, + replace_file: bool, cx: &mut ModelContext, ) -> Task> { + let handle = cx.handle(); let buffer = buffer_handle.read(cx); let rpc = self.client.clone(); @@ -742,53 +744,40 @@ impl LocalWorktree { let save = self.write_file(path, text, buffer.line_ending(), cx); cx.as_mut().spawn(|mut cx| async move { - let mtime = save.await?.mtime; + let entry = save.await?; + if let Some(project_id) = project_id { rpc.send(proto::BufferSaved { project_id, buffer_id, version: serialize_version(&version), - mtime: Some(mtime.into()), + mtime: Some(entry.mtime.into()), fingerprint: serialize_fingerprint(fingerprint), })?; } - buffer_handle.update(&mut cx, |buffer, cx| { - buffer.did_save(version.clone(), fingerprint, mtime, None, cx); - }); - anyhow::Ok((version, fingerprint, mtime)) - }) - } - - pub fn save_buffer_as( - &self, - buffer_handle: ModelHandle, - path: impl Into>, - cx: &mut ModelContext, - ) -> Task> { - let handle = cx.handle(); - let buffer = buffer_handle.read(cx); - - let text = buffer.as_rope().clone(); - let fingerprint = text.fingerprint(); - let version = buffer.version(); - let save = self.write_file(path, text, buffer.line_ending(), cx); - - cx.as_mut().spawn(|mut cx| async move { - let entry = save.await?; - let file = File { - entry_id: entry.id, - worktree: handle, - path: entry.path, - mtime: entry.mtime, - is_local: true, - is_deleted: false, - }; buffer_handle.update(&mut cx, |buffer, cx| { - buffer.did_save(version, fingerprint, file.mtime, Some(Arc::new(file)), cx); + buffer.did_save( + version.clone(), + fingerprint, + entry.mtime, + if replace_file { + Some(Arc::new(File { + entry_id: entry.id, + worktree: handle, + path: entry.path, + mtime: entry.mtime, + is_local: true, + is_deleted: false, + })) + } else { + None + }, + cx, + ); }); - Ok(()) + Ok((version, fingerprint, entry.mtime)) }) } From fc811d14b1c12a8fc5ae06483fe2f59d6110a237 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 17 Feb 2023 22:00:39 -0800 Subject: [PATCH 172/180] Fix failing test --- crates/editor/src/editor.rs | 19 ++---------- crates/editor/src/multi_buffer.rs | 4 +-- crates/language/src/buffer.rs | 4 +-- crates/util/src/util.rs | 51 ++++++++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2cf0c9a36f..14566b9591 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -77,14 +77,14 @@ use std::{ cmp::{self, Ordering, Reverse}, mem, num::NonZeroU32, - ops::{Deref, DerefMut, Range, RangeInclusive}, + ops::{Deref, DerefMut, Range}, path::Path, sync::Arc, time::{Duration, Instant}, }; pub use sum_tree::Bias; use theme::{DiagnosticStyle, Theme}; -use util::{post_inc, ResultExt, TryFutureExt}; +use util::{post_inc, ResultExt, TryFutureExt, RangeExt}; use workspace::{ItemNavHistory, ViewId, Workspace, WorkspaceId}; use crate::git::diff_hunk_to_display; @@ -6959,21 +6959,6 @@ pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator { - fn sorted(&self) -> Range; - fn to_inclusive(&self) -> RangeInclusive; -} - -impl RangeExt for Range { - fn sorted(&self) -> Self { - cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone() - } - - fn to_inclusive(&self) -> RangeInclusive { - self.start.clone()..=self.end.clone() - } -} - trait RangeToAnchorExt { fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range; } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index ad661b84ee..faf6787d00 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2961,14 +2961,14 @@ impl MultiBufferSnapshot { let range = range.start.to_offset(self)..range.end.to_offset(self); let mut cursor = self.excerpts.cursor::(); - cursor.seek(&dbg!(range.start), Bias::Right, &()); + cursor.seek(&range.start, Bias::Right, &()); let start_excerpt = cursor.item(); if range.start == range.end { return start_excerpt.map(|excerpt| (excerpt, *cursor.start())); } - cursor.seek(&dbg!(range.end), Bias::Right, &()); + cursor.seek(&range.end, Bias::Right, &()); let end_excerpt = cursor.item(); start_excerpt diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 857c0a063f..00a754a776 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -41,7 +41,7 @@ pub use text::{Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, Opera use theme::SyntaxTheme; #[cfg(any(test, feature = "test-support"))] use util::RandomCharIter; -use util::TryFutureExt as _; +use util::{RangeExt, TryFutureExt as _}; #[cfg(any(test, feature = "test-support"))] pub use {tree_sitter_rust, tree_sitter_typescript}; @@ -2382,7 +2382,7 @@ impl BufferSnapshot { let Some((open, close)) = open.zip(close) else { continue }; let bracket_range = open.start..=close.end; - if !bracket_range.contains(&range.start) && !bracket_range.contains(&range.end) { + if !bracket_range.overlaps(&range) { continue; } diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index ea8fdee2a8..37e1f29ce2 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -3,16 +3,17 @@ pub mod paths; #[cfg(any(test, feature = "test-support"))] pub mod test; -pub use backtrace::Backtrace; -use futures::Future; -use rand::{seq::SliceRandom, Rng}; use std::{ - cmp::Ordering, - ops::AddAssign, + cmp::{self, Ordering}, + ops::{AddAssign, Range, RangeInclusive}, pin::Pin, task::{Context, Poll}, }; +pub use backtrace::Backtrace; +use futures::Future; +use rand::{seq::SliceRandom, Rng}; + #[derive(Debug, Default)] pub struct StaffMode(pub bool); @@ -245,6 +246,46 @@ macro_rules! async_iife { }; } +pub trait RangeExt { + fn sorted(&self) -> Self; + fn to_inclusive(&self) -> RangeInclusive; + fn overlaps(&self, other: &Range) -> bool; +} + +impl RangeExt for Range { + fn sorted(&self) -> Self { + cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone() + } + + fn to_inclusive(&self) -> RangeInclusive { + self.start.clone()..=self.end.clone() + } + + fn overlaps(&self, other: &Range) -> bool { + self.contains(&other.start) + || self.contains(&other.end) + || other.contains(&self.start) + || other.contains(&self.end) + } +} + +impl RangeExt for RangeInclusive { + fn sorted(&self) -> Self { + cmp::min(self.start(), self.end()).clone()..=cmp::max(self.start(), self.end()).clone() + } + + fn to_inclusive(&self) -> RangeInclusive { + self.clone() + } + + fn overlaps(&self, other: &Range) -> bool { + self.contains(&other.start) + || self.contains(&other.end) + || other.contains(&self.start()) + || other.contains(&self.end()) + } +} + #[cfg(test)] mod tests { use super::*; From dc6f7fd577829c9bae891becf97ed765f5e2b613 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 17 Feb 2023 18:32:04 -0800 Subject: [PATCH 173/180] pull toggle button into its own file --- crates/workspace/src/dock.rs | 121 ++---------------- .../workspace/src/dock/toggle_dock_button.rs | 112 ++++++++++++++++ crates/workspace/src/pane.rs | 2 +- 3 files changed, 123 insertions(+), 112 deletions(-) create mode 100644 crates/workspace/src/dock/toggle_dock_button.rs diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 1702c6e521..bda8295911 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,19 +1,20 @@ +mod toggle_dock_button; + +use serde::Deserialize; + use collections::HashMap; use gpui::{ actions, - elements::{ChildView, Container, Empty, MouseEventHandler, ParentElement, Side, Stack, Svg}, + elements::{ChildView, Container, Empty, MouseEventHandler, ParentElement, Side, Stack}, geometry::vector::Vector2F, - impl_internal_actions, Border, CursorStyle, Element, ElementBox, Entity, MouseButton, - MutableAppContext, RenderContext, SizeConstraint, View, ViewContext, ViewHandle, - WeakViewHandle, + impl_internal_actions, Border, CursorStyle, Element, ElementBox, MouseButton, + MutableAppContext, RenderContext, SizeConstraint, ViewContext, ViewHandle, }; -use serde::Deserialize; use settings::{DockAnchor, Settings}; use theme::Theme; -use crate::{ - handle_dropped_item, sidebar::SidebarSide, ItemHandle, Pane, StatusItemView, Workspace, -}; +use crate::{sidebar::SidebarSide, ItemHandle, Pane, Workspace}; +pub use toggle_dock_button::ToggleDockButton; #[derive(PartialEq, Clone, Deserialize)] pub struct MoveDock(pub DockAnchor); @@ -376,108 +377,6 @@ impl Dock { } } -pub struct ToggleDockButton { - workspace: WeakViewHandle, -} - -impl ToggleDockButton { - pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> Self { - // When dock moves, redraw so that the icon and toggle status matches. - cx.subscribe(&workspace, |_, _, _, cx| cx.notify()).detach(); - - Self { - workspace: workspace.downgrade(), - } - } -} - -impl Entity for ToggleDockButton { - type Event = (); -} - -impl View for ToggleDockButton { - fn ui_name() -> &'static str { - "Dock Toggle" - } - - fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { - let workspace = self.workspace.upgrade(cx); - - if workspace.is_none() { - return Empty::new().boxed(); - } - - let workspace = workspace.unwrap(); - let dock_position = workspace.read(cx).dock.position; - - let theme = cx.global::().theme.clone(); - - let button = MouseEventHandler::::new(0, cx, { - let theme = theme.clone(); - move |state, _| { - let style = theme - .workspace - .status_bar - .sidebar_buttons - .item - .style_for(state, dock_position.is_visible()); - - Svg::new(icon_for_dock_anchor(dock_position.anchor())) - .with_color(style.icon_color) - .constrained() - .with_width(style.icon_size) - .with_height(style.icon_size) - .contained() - .with_style(style.container) - .boxed() - } - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_up(MouseButton::Left, move |event, cx| { - let dock_pane = workspace.read(cx.app).dock_pane(); - let drop_index = dock_pane.read(cx.app).items_len() + 1; - handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx); - }); - - if dock_position.is_visible() { - button - .on_click(MouseButton::Left, |_, cx| { - cx.dispatch_action(HideDock); - }) - .with_tooltip::( - 0, - "Hide Dock".into(), - Some(Box::new(HideDock)), - theme.tooltip.clone(), - cx, - ) - } else { - button - .on_click(MouseButton::Left, |_, cx| { - cx.dispatch_action(FocusDock); - }) - .with_tooltip::( - 0, - "Focus Dock".into(), - Some(Box::new(FocusDock)), - theme.tooltip.clone(), - cx, - ) - } - .boxed() - } -} - -impl StatusItemView for ToggleDockButton { - fn set_active_pane_item( - &mut self, - _active_pane_item: Option<&dyn crate::ItemHandle>, - _cx: &mut ViewContext, - ) { - //Not applicable - } -} - #[cfg(test)] mod tests { use std::{ @@ -485,7 +384,7 @@ mod tests { path::PathBuf, }; - use gpui::{AppContext, TestAppContext, UpdateView, ViewContext}; + use gpui::{AppContext, TestAppContext, UpdateView, View, ViewContext}; use project::{FakeFs, Project}; use settings::Settings; diff --git a/crates/workspace/src/dock/toggle_dock_button.rs b/crates/workspace/src/dock/toggle_dock_button.rs new file mode 100644 index 0000000000..cafbea7db3 --- /dev/null +++ b/crates/workspace/src/dock/toggle_dock_button.rs @@ -0,0 +1,112 @@ +use gpui::{ + elements::{Empty, MouseEventHandler, Svg}, + CursorStyle, Element, ElementBox, Entity, MouseButton, View, ViewContext, ViewHandle, + WeakViewHandle, +}; +use settings::Settings; + +use crate::{handle_dropped_item, StatusItemView, Workspace}; + +use super::{icon_for_dock_anchor, FocusDock, HideDock}; + +pub struct ToggleDockButton { + workspace: WeakViewHandle, +} + +impl ToggleDockButton { + pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> Self { + // When dock moves, redraw so that the icon and toggle status matches. + cx.subscribe(&workspace, |_, _, _, cx| cx.notify()).detach(); + + Self { + workspace: workspace.downgrade(), + } + } +} + +impl Entity for ToggleDockButton { + type Event = (); +} + +impl View for ToggleDockButton { + fn ui_name() -> &'static str { + "Dock Toggle" + } + + fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { + let workspace = self.workspace.upgrade(cx); + + if workspace.is_none() { + return Empty::new().boxed(); + } + + let workspace = workspace.unwrap(); + let dock_position = workspace.read(cx).dock.position; + + let theme = cx.global::().theme.clone(); + + let button = MouseEventHandler::::new(0, cx, { + let theme = theme.clone(); + move |state, _| { + let style = theme + .workspace + .status_bar + .sidebar_buttons + .item + .style_for(state, dock_position.is_visible()); + + Svg::new(icon_for_dock_anchor(dock_position.anchor())) + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .with_height(style.icon_size) + .contained() + .with_style(style.container) + .boxed() + } + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_up(MouseButton::Left, move |event, cx| { + let dock_pane = workspace.read(cx.app).dock_pane(); + let drop_index = dock_pane.read(cx.app).items_len() + 1; + handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx); + }); + + if dock_position.is_visible() { + button + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(HideDock); + }) + .with_tooltip::( + 0, + "Hide Dock".into(), + Some(Box::new(HideDock)), + theme.tooltip.clone(), + cx, + ) + } else { + button + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(FocusDock); + }) + .with_tooltip::( + 0, + "Focus Dock".into(), + Some(Box::new(FocusDock)), + theme.tooltip.clone(), + cx, + ) + } + .boxed() + } +} + +impl StatusItemView for ToggleDockButton { + fn set_active_pane_item( + &mut self, + _active_pane_item: Option<&dyn crate::ItemHandle>, + _cx: &mut ViewContext, + ) { + //Not applicable + } +} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index ccd2dd38e1..8e51a54178 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1432,7 +1432,7 @@ impl View for Pane { enum TabBarEventHandler {} stack.add_child( MouseEventHandler::::new(0, cx, |_, _| { - Flex::row() + Empty::new() .contained() .with_style(theme.workspace.tab_bar.container) .boxed() From 04df00b221c69614eccaa9e559dc6ed4976a8cbb Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sat, 18 Feb 2023 13:10:01 -0800 Subject: [PATCH 174/180] Iterate over keymap then dispatch path when matching keybindings to make precedence more intuitive Rename action which adds the active tab to the dock to be more intuitive Add action which moves the active tab out of the dock and bind it to the same keybinding --- assets/keymaps/default.json | 9 ++++--- crates/gpui/src/keymap_matcher.rs | 33 +++++++++++++----------- crates/workspace/src/dock.rs | 42 +++++++++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index ed0cf89484..e8f055cb7d 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -450,15 +450,16 @@ } }, { - "context": "Dock", + "context": "Pane", "bindings": { - "shift-escape": "dock::HideDock" + "cmd-escape": "dock::AddTabToDock" } }, { - "context": "Pane", + "context": "Dock", "bindings": { - "cmd-escape": "dock::MoveActiveItemToDock" + "shift-escape": "dock::HideDock", + "cmd-escape": "dock::RemoveTabFromDock" } }, { diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index edcc458658..9a702a220c 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -75,29 +75,32 @@ impl KeymapMatcher { self.contexts .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1))); - for (i, (view_id, _)) in dispatch_path.into_iter().enumerate() { - // Don't require pending view entry if there are no pending keystrokes - if !first_keystroke && !self.pending_views.contains_key(&view_id) { - continue; - } - - // If there is a previous view context, invalidate that view if it - // has changed - if let Some(previous_view_context) = self.pending_views.remove(&view_id) { - if previous_view_context != self.contexts[i] { + // Find the bindings which map the pending keystrokes and current context + // Iterate over the bindings in precedence order before the dispatch path so that + // users have more control over precedence rules + for binding in self.keymap.bindings().iter().rev() { + for (i, (view_id, _)) in dispatch_path.iter().enumerate() { + // Don't require pending view entry if there are no pending keystrokes + if !first_keystroke && !self.pending_views.contains_key(view_id) { continue; } - } - // Find the bindings which map the pending keystrokes and current context - for binding in self.keymap.bindings().iter().rev() { + // If there is a previous view context, invalidate that view if it + // has changed + if let Some(previous_view_context) = self.pending_views.remove(view_id) { + if previous_view_context != self.contexts[i] { + continue; + } + } + match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..]) { BindingMatchResult::Complete(action) => { - matched_bindings.push((view_id, action)) + matched_bindings.push((*view_id, action)) } BindingMatchResult::Partial => { - self.pending_views.insert(view_id, self.contexts[i].clone()); + self.pending_views + .insert(*view_id, self.contexts[i].clone()); any_pending = true; } _ => {} diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index bda8295911..057658c3b5 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -30,7 +30,8 @@ actions!( AnchorDockRight, AnchorDockBottom, ExpandDock, - MoveActiveItemToDock, + AddTabToDock, + RemoveTabFromDock, ] ); impl_internal_actions!(dock, [MoveDock, AddDefaultItemToDock]); @@ -55,7 +56,8 @@ pub fn init(cx: &mut MutableAppContext) { }, ); cx.add_action( - |workspace: &mut Workspace, _: &MoveActiveItemToDock, cx: &mut ViewContext| { + |workspace: &mut Workspace, _: &AddTabToDock, cx: &mut ViewContext| { + eprintln!("Add tab to dock"); if let Some(active_item) = workspace.active_item(cx) { let item_id = active_item.id(); @@ -67,6 +69,42 @@ pub fn init(cx: &mut MutableAppContext) { let destination_index = to.read(cx).items_len() + 1; + Pane::move_item( + workspace, + from.clone(), + to.clone(), + item_id, + destination_index, + cx, + ); + } + }, + ); + cx.add_action( + |workspace: &mut Workspace, _: &RemoveTabFromDock, cx: &mut ViewContext| { + eprintln!("Removing tab from dock"); + if let Some(active_item) = workspace.active_item(cx) { + let item_id = active_item.id(); + + let from = workspace.dock_pane(); + let to = workspace + .last_active_center_pane + .as_ref() + .and_then(|pane| pane.upgrade(cx)) + .unwrap_or_else(|| { + workspace + .panes + .first() + .expect("There must be a pane") + .clone() + }); + + if from.id() == to.id() { + return; + } + + let destination_index = to.read(cx).items_len() + 1; + Pane::move_item( workspace, from.clone(), From 3fb6e31b9294bc1fc01f33d88d3b7da4bff8b484 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sat, 18 Feb 2023 13:42:28 -0800 Subject: [PATCH 175/180] revert for loop change and store matched actions in a sorted set instead --- crates/gpui/src/keymap_matcher.rs | 40 +++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index 9a702a220c..93f5d0934a 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -5,7 +5,7 @@ mod keystroke; use std::{any::TypeId, fmt::Debug}; -use collections::HashMap; +use collections::{BTreeMap, HashMap}; use smallvec::SmallVec; use crate::Action; @@ -66,7 +66,10 @@ impl KeymapMatcher { mut dispatch_path: Vec<(usize, KeymapContext)>, ) -> MatchResult { let mut any_pending = false; - let mut matched_bindings: Vec<(usize, Box)> = Vec::new(); + // Collect matched bindings into an ordered list using the position in the bindings + // list as the precedence + let mut matched_bindings: BTreeMap)>> = + Default::default(); let first_keystroke = self.pending_keystrokes.is_empty(); self.pending_keystrokes.push(keystroke.clone()); @@ -76,27 +79,28 @@ impl KeymapMatcher { .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1))); // Find the bindings which map the pending keystrokes and current context - // Iterate over the bindings in precedence order before the dispatch path so that - // users have more control over precedence rules - for binding in self.keymap.bindings().iter().rev() { - for (i, (view_id, _)) in dispatch_path.iter().enumerate() { - // Don't require pending view entry if there are no pending keystrokes - if !first_keystroke && !self.pending_views.contains_key(view_id) { + for (i, (view_id, _)) in dispatch_path.iter().enumerate() { + // Don't require pending view entry if there are no pending keystrokes + if !first_keystroke && !self.pending_views.contains_key(view_id) { + continue; + } + + // If there is a previous view context, invalidate that view if it + // has changed + if let Some(previous_view_context) = self.pending_views.remove(view_id) { + if previous_view_context != self.contexts[i] { continue; } + } - // If there is a previous view context, invalidate that view if it - // has changed - if let Some(previous_view_context) = self.pending_views.remove(view_id) { - if previous_view_context != self.contexts[i] { - continue; - } - } - + for (order, binding) in self.keymap.bindings().iter().rev().enumerate() { match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..]) { BindingMatchResult::Complete(action) => { - matched_bindings.push((*view_id, action)) + matched_bindings + .entry(order) + .or_default() + .push((*view_id, action)); } BindingMatchResult::Partial => { self.pending_views @@ -113,7 +117,7 @@ impl KeymapMatcher { } if !matched_bindings.is_empty() { - MatchResult::Matches(matched_bindings) + MatchResult::Matches(matched_bindings.into_values().flatten().collect()) } else if any_pending { MatchResult::Pending } else { From 159d3ab00c49a3984f02d5008dfeb99548ba3188 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sat, 18 Feb 2023 13:49:08 -0800 Subject: [PATCH 176/180] Add comment explaining push_keystroke --- crates/gpui/src/keymap_matcher.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index 93f5d0934a..26da099a9f 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -60,6 +60,16 @@ impl KeymapMatcher { !self.pending_keystrokes.is_empty() } + /// Pushes a keystroke onto the matcher. + /// The result of the new keystroke is returned: + /// MatchResult::None => + /// No match is valid for this key given any pending keystrokes. + /// MatchResult::Pending => + /// There exist bindings which are still waiting for more keys. + /// MatchResult::Complete(matches) => + /// 1 or more bindings have recieved the necessary key presses. + /// The order of the matched actions is by order in the keymap file first and + /// position of the matching view second. pub fn push_keystroke( &mut self, keystroke: Keystroke, @@ -117,6 +127,8 @@ impl KeymapMatcher { } if !matched_bindings.is_empty() { + // Collect the sorted matched bindings into the final vec for ease of use + // Matched bindings are in order by precedence MatchResult::Matches(matched_bindings.into_values().flatten().collect()) } else if any_pending { MatchResult::Pending From 0981244797e3ed03d26a5db0c50698ab60009146 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sat, 18 Feb 2023 13:53:13 -0800 Subject: [PATCH 177/180] further tweak comment --- crates/gpui/src/keymap_matcher.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index 26da099a9f..cfc26d6869 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -76,8 +76,10 @@ impl KeymapMatcher { mut dispatch_path: Vec<(usize, KeymapContext)>, ) -> MatchResult { let mut any_pending = false; - // Collect matched bindings into an ordered list using the position in the bindings - // list as the precedence + // Collect matched bindings into an ordered list using the position in the matching binding first, + // and then the order the binding matched in the view tree second. + // The key is the reverse position of the binding in the bindings list so that later bindings + // match before earlier ones in the user's config let mut matched_bindings: BTreeMap)>> = Default::default(); From 4bb986b3be9a901d68dde12b6df41279649a4aed Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Mon, 20 Feb 2023 18:57:37 +0200 Subject: [PATCH 178/180] Move reveal_path to ForegroundPlatform So that we can use spawn to use the OS API call. Co-Authored-By: Antonio Scandurra --- crates/gpui/src/app.rs | 10 ++++- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/mac.rs | 8 +++- crates/gpui/src/platform/mac/platform.rs | 54 ++++++++++++++++------- crates/gpui/src/platform/test.rs | 4 +- crates/project_panel/src/project_panel.rs | 3 +- 6 files changed, 57 insertions(+), 24 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 60adadb96c..4bdd775593 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -190,8 +190,8 @@ pub struct AsyncAppContext(Rc>); impl App { pub fn new(asset_source: impl AssetSource) -> Result { let platform = platform::current::platform(); - let foreground_platform = platform::current::foreground_platform(); let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?); + let foreground_platform = platform::current::foreground_platform(foreground.clone()); let app = Self(Rc::new(RefCell::new(MutableAppContext::new( foreground, Arc::new(executor::Background::new()), @@ -900,6 +900,10 @@ impl MutableAppContext { self.foreground_platform.prompt_for_new_path(directory) } + pub fn reveal_path(&self, path: &Path) { + self.foreground_platform.reveal_path(path) + } + pub fn emit_global(&mut self, payload: E) { self.pending_effects.push_back(Effect::GlobalEvent { payload: Box::new(payload), @@ -3637,6 +3641,10 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.prompt_for_new_path(directory) } + pub fn reveal_path(&self, path: &Path) { + self.app.reveal_path(path) + } + pub fn debug_elements(&self) -> crate::json::Value { self.app.debug_elements(self.window_id).unwrap() } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 1bf19e983e..76c2707d26 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -65,7 +65,6 @@ pub trait Platform: Send + Sync { fn write_to_clipboard(&self, item: ClipboardItem); fn read_from_clipboard(&self) -> Option; fn open_url(&self, url: &str); - fn reveal_path(&self, path: &Path); fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>; fn read_credentials(&self, url: &str) -> Result)>>; @@ -101,6 +100,7 @@ pub(crate) trait ForegroundPlatform { options: PathPromptOptions, ) -> oneshot::Receiver>>; fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver>; + fn reveal_path(&self, path: &Path); } pub trait Dispatcher: Send + Sync { diff --git a/crates/gpui/src/platform/mac.rs b/crates/gpui/src/platform/mac.rs index 9e3f104055..342c1c66d0 100644 --- a/crates/gpui/src/platform/mac.rs +++ b/crates/gpui/src/platform/mac.rs @@ -23,12 +23,16 @@ pub use renderer::Surface; use std::{ops::Range, rc::Rc, sync::Arc}; use window::Window; +use crate::executor; + pub(crate) fn platform() -> Arc { Arc::new(MacPlatform::new()) } -pub(crate) fn foreground_platform() -> Rc { - Rc::new(MacForegroundPlatform::default()) +pub(crate) fn foreground_platform( + foreground: Rc, +) -> Rc { + Rc::new(MacForegroundPlatform::new(foreground)) } trait BoolExt { diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index c2887eeb23..57827e1946 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -16,7 +16,7 @@ use cocoa::{ NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, NSSavePanel, NSWindow, }, - base::{id, nil, selector, YES}, + base::{id, nil, selector, BOOL, YES}, foundation::{ NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString, NSUInteger, NSURL, @@ -114,10 +114,8 @@ unsafe fn build_classes() { } } -#[derive(Default)] pub struct MacForegroundPlatform(RefCell); -#[derive(Default)] pub struct MacForegroundPlatformState { become_active: Option>, resign_active: Option>, @@ -129,9 +127,26 @@ pub struct MacForegroundPlatformState { open_urls: Option)>>, finish_launching: Option>, menu_actions: Vec>, + foreground: Rc, } impl MacForegroundPlatform { + pub fn new(foreground: Rc) -> Self { + Self(RefCell::new(MacForegroundPlatformState { + become_active: Default::default(), + resign_active: Default::default(), + quit: Default::default(), + event: Default::default(), + menu_command: Default::default(), + validate_menu_command: Default::default(), + will_open_menu: Default::default(), + open_urls: Default::default(), + finish_launching: Default::default(), + menu_actions: Default::default(), + foreground, + })) + } + unsafe fn create_menu_bar( &self, menus: Vec, @@ -399,6 +414,26 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { done_rx } } + + fn reveal_path(&self, path: &Path) { + unsafe { + let path = path.to_path_buf(); + self.0 + .borrow() + .foreground + .spawn(async move { + let full_path = ns_string(path.to_str().unwrap_or("")); + let root_full_path = ns_string(""); + let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; + let _: BOOL = msg_send![ + workspace, + selectFile: full_path + inFileViewerRootedAtPath: root_full_path + ]; + }) + .detach(); + } + } } pub struct MacPlatform { @@ -600,19 +635,6 @@ impl platform::Platform for MacPlatform { } } - fn reveal_path(&self, path: &Path) { - unsafe { - let full_path = ns_string(path.to_str().unwrap_or("")); - let root_full_path = ns_string(""); - let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; - msg_send![ - workspace, - selectFile: full_path - inFileViewerRootedAtPath: root_full_path - ] - } - } - fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { let url = CFString::from(url); let username = CFString::from(username); diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index e81daa8788..194684bd12 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -92,6 +92,8 @@ impl super::ForegroundPlatform for ForegroundPlatform { *self.last_prompt_for_new_path_args.borrow_mut() = Some((path.to_path_buf(), done_tx)); done_rx } + + fn reveal_path(&self, _: &Path) {} } pub fn platform() -> Platform { @@ -173,8 +175,6 @@ impl super::Platform for Platform { fn open_url(&self, _: &str) {} - fn reveal_path(&self, _: &Path) {} - fn write_credentials(&self, _: &str, _: &str, _: &[u8]) -> Result<()> { Ok(()) } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index f2330dfd4f..2ba920c318 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -792,8 +792,7 @@ impl ProjectPanel { fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext) { if let Some((worktree, entry)) = self.selected_entry(cx) { - cx.platform() - .reveal_path(&worktree.abs_path().join(&entry.path)); + cx.reveal_path(&worktree.abs_path().join(&entry.path)); } } From 56b7eb6b6f2b83529906fa876bb56d25e86452b5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 20 Feb 2023 09:33:02 -0800 Subject: [PATCH 179/180] Only send UpdateBufferFile messages for buffers whose files have changed Send that message when saving a buffer as a new path. --- crates/language/src/buffer.rs | 5 --- crates/project/src/project.rs | 23 +++++++------ crates/project/src/project_tests.rs | 1 - crates/project/src/worktree.rs | 50 +++++++++++++++++------------ 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index ad9345a6bd..e34b3d8282 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -549,16 +549,11 @@ impl Buffer { version: clock::Global, fingerprint: RopeFingerprint, mtime: SystemTime, - new_file: Option>, cx: &mut ModelContext, ) { self.saved_version = version; self.saved_version_fingerprint = fingerprint; self.saved_mtime = mtime; - if let Some(new_file) = new_file { - self.file = Some(new_file); - self.file_update_count += 1; - } cx.emit(Event::Saved); cx.notify(); } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ba5ca82c02..3a0e0a43c0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4461,16 +4461,19 @@ impl Project { renamed_buffers.push((cx.handle(), old_path)); } - if let Some(project_id) = self.remote_id() { - self.client - .send(proto::UpdateBufferFile { - project_id, - buffer_id: *buffer_id as u64, - file: Some(new_file.to_proto()), - }) - .log_err(); + if new_file != *old_file { + if let Some(project_id) = self.remote_id() { + self.client + .send(proto::UpdateBufferFile { + project_id, + buffer_id: *buffer_id as u64, + file: Some(new_file.to_proto()), + }) + .log_err(); + } + + buffer.file_updated(Arc::new(new_file), cx).detach(); } - buffer.file_updated(Arc::new(new_file), cx).detach(); } }); } else { @@ -6054,7 +6057,7 @@ impl Project { .and_then(|buffer| buffer.upgrade(cx)); if let Some(buffer) = buffer { buffer.update(cx, |buffer, cx| { - buffer.did_save(version, fingerprint, mtime, None, cx); + buffer.did_save(version, fingerprint, mtime, cx); }); } Ok(()) diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 2a88714b18..fa3bf9dff8 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2482,7 +2482,6 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) { buffer.version(), buffer.as_rope().fingerprint(), buffer.file().unwrap().mtime(), - None, cx, ); }); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 9f4446f060..8b622ab607 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -20,6 +20,7 @@ use gpui::{ executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, }; +use language::File as _; use language::{ proto::{ deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending, @@ -728,7 +729,7 @@ impl LocalWorktree { &self, buffer_handle: ModelHandle, path: Arc, - replace_file: bool, + has_changed_file: bool, cx: &mut ModelContext, ) -> Task> { let handle = cx.handle(); @@ -746,6 +747,32 @@ impl LocalWorktree { cx.as_mut().spawn(|mut cx| async move { let entry = save.await?; + if has_changed_file { + let new_file = Arc::new(File { + entry_id: entry.id, + worktree: handle, + path: entry.path, + mtime: entry.mtime, + is_local: true, + is_deleted: false, + }); + + if let Some(project_id) = project_id { + rpc.send(proto::UpdateBufferFile { + project_id, + buffer_id, + file: Some(new_file.to_proto()), + }) + .log_err(); + } + + buffer_handle.update(&mut cx, |buffer, cx| { + if has_changed_file { + buffer.file_updated(new_file, cx).detach(); + } + }); + } + if let Some(project_id) = project_id { rpc.send(proto::BufferSaved { project_id, @@ -757,24 +784,7 @@ impl LocalWorktree { } buffer_handle.update(&mut cx, |buffer, cx| { - buffer.did_save( - version.clone(), - fingerprint, - entry.mtime, - if replace_file { - Some(Arc::new(File { - entry_id: entry.id, - worktree: handle, - path: entry.path, - mtime: entry.mtime, - is_local: true, - is_deleted: false, - })) - } else { - None - }, - cx, - ); + buffer.did_save(version.clone(), fingerprint, entry.mtime, cx); }); Ok((version, fingerprint, entry.mtime)) @@ -1137,7 +1147,7 @@ impl RemoteWorktree { .into(); buffer_handle.update(&mut cx, |buffer, cx| { - buffer.did_save(version.clone(), fingerprint, mtime, None, cx); + buffer.did_save(version.clone(), fingerprint, mtime, cx); }); Ok((version, fingerprint, mtime)) From 010eba509c07b642fea09a49e9932b90abd3be89 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 20 Feb 2023 09:42:44 -0800 Subject: [PATCH 180/180] Make Project::save_buffer and ::save_buffers into methods --- crates/collab/src/tests/integration_tests.rs | 4 ++-- .../src/tests/randomized_integration_tests.rs | 2 +- crates/editor/src/items.rs | 6 ++++-- crates/project/src/project.rs | 17 ++++++++++------- crates/project/src/project_tests.rs | 12 ++++++++---- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 8b92216734..f2cb2eddbb 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2244,7 +2244,7 @@ async fn test_propagate_saves_and_fs_changes( }); // Edit the buffer as the host and concurrently save as guest B. - let save_b = cx_b.update(|cx| Project::save_buffer(buffer_b.clone(), cx)); + let save_b = project_b.update(cx_b, |project, cx| project.save_buffer(buffer_b.clone(), cx)); buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], None, cx)); save_b.await.unwrap(); assert_eq!( @@ -2917,7 +2917,7 @@ async fn test_buffer_conflict_after_save( assert!(!buf.has_conflict()); }); - cx_b.update(|cx| Project::save_buffer(buffer_b.clone(), cx)) + project_b.update(cx_b, |project, cx| project.save_buffer(buffer_b.clone(), cx)) .await .unwrap(); cx_a.foreground().forbid_parking(); diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index 434006c5c1..950f12d186 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -1073,7 +1073,7 @@ async fn randomly_query_and_mutate_buffers( ); buffer.version() }); - let save = cx.update(|cx| Project::save_buffer(buffer, cx)); + let save = project.update(cx, |project, cx| project.save_buffer(buffer, cx)); let save = cx.background().spawn(async move { let (saved_version, _, _) = save .await diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index aac8701a22..c3a446faa7 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -610,9 +610,11 @@ impl Item for Editor { self.report_event("save editor", cx); let format = self.perform_format(project.clone(), cx); let buffers = self.buffer().clone().read(cx).all_buffers(); - cx.spawn(|_, mut cx| async move { + cx.as_mut().spawn(|mut cx| async move { format.await?; - cx.update(|cx| Project::save_buffers(buffers, cx)).await?; + project + .update(&mut cx, |project, cx| project.save_buffers(buffers, cx)) + .await?; Ok(()) }) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3a0e0a43c0..8ed37a003c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1429,21 +1429,23 @@ impl Project { } pub fn save_buffers( + &self, buffers: HashSet>, - cx: &mut MutableAppContext, + cx: &mut ModelContext, ) -> Task> { - cx.spawn(|mut cx| async move { + cx.spawn(|this, mut cx| async move { let save_tasks = buffers .into_iter() - .map(|buffer| cx.update(|cx| Self::save_buffer(buffer, cx))); + .map(|buffer| this.update(&mut cx, |this, cx| this.save_buffer(buffer, cx))); try_join_all(save_tasks).await?; Ok(()) }) } pub fn save_buffer( + &self, buffer: ModelHandle, - cx: &mut MutableAppContext, + cx: &mut ModelContext, ) -> Task> { let Some(file) = File::from_dyn(buffer.read(cx).file()) else { return Task::ready(Err(anyhow!("buffer doesn't have a file"))); @@ -1460,7 +1462,7 @@ impl Project { &mut self, buffer: ModelHandle, abs_path: PathBuf, - cx: &mut ModelContext, + cx: &mut ModelContext, ) -> Task> { let worktree_task = self.find_or_create_local_worktree(&abs_path, true, cx); let old_path = @@ -5186,8 +5188,9 @@ impl Project { }) .await; - let (saved_version, fingerprint, mtime) = - cx.update(|cx| Self::save_buffer(buffer, cx)).await?; + let (saved_version, fingerprint, mtime) = this + .update(&mut cx, |this, cx| this.save_buffer(buffer, cx)) + .await?; Ok(proto::BufferSaved { project_id, buffer_id, diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index fa3bf9dff8..2f9f92af4e 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -243,7 +243,8 @@ async fn test_managing_language_servers( ); // Save notifications are reported to all servers. - cx.update(|cx| Project::save_buffer(toml_buffer, cx)) + project + .update(cx, |project, cx| project.save_buffer(toml_buffer, cx)) .await .unwrap(); assert_eq!( @@ -2087,7 +2088,8 @@ async fn test_save_file(cx: &mut gpui::TestAppContext) { buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx); }); - cx.update(|cx| Project::save_buffer(buffer.clone(), cx)) + project + .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)) .await .unwrap(); @@ -2115,7 +2117,8 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) { buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx); }); - cx.update(|cx| Project::save_buffer(buffer.clone(), cx)) + project + .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)) .await .unwrap(); @@ -2704,7 +2707,8 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) { buffer2.update(cx, |buffer, cx| { buffer.set_text("one\ntwo\nthree\nfour\n", cx); }); - cx.update(|cx| Project::save_buffer(buffer2, cx)) + project + .update(cx, |project, cx| project.save_buffer(buffer2, cx)) .await .unwrap(); assert_eq!(