From f20f096a30f02dc565475d0c873f3a0a04ce9664 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 2 Oct 2023 19:15:59 +0300 Subject: [PATCH 001/180] searching the semantic index, and passing returned snippets to prompt generation --- Cargo.lock | 1 + crates/assistant/Cargo.toml | 2 + crates/assistant/src/assistant_panel.rs | 53 ++++++++++++++++++++++++- crates/assistant/src/prompts.rs | 1 + 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 76de671620a2539ce9e1f37b085c9cbbb1b30ad2..2b7d74578d1b816d472c30feba8ddb4a364c7d11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,7 @@ dependencies = [ "regex", "schemars", "search", + "semantic_index", "serde", "serde_json", "settings", diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 5d141b32d5a448c43a9db96f0879461709963697..8b69e82109abf6c0e9e90069caa40c99b1521985 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -23,7 +23,9 @@ theme = { path = "../theme" } util = { path = "../util" } uuid = { version = "1.1.2", features = ["v4"] } workspace = { path = "../workspace" } +semantic_index = { path = "../semantic_index" } +log.workspace = true anyhow.workspace = true chrono = { version = "0.4", features = ["serde"] } futures.workspace = true diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index b69c12a2a328ed8643315f091be11d764dcdc00d..8fa0327134865de41c6cba134b0ff939baae8f99 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -36,6 +36,7 @@ use gpui::{ }; use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; use search::BufferSearchBar; +use semantic_index::SemanticIndex; use settings::SettingsStore; use std::{ cell::{Cell, RefCell}, @@ -145,6 +146,7 @@ pub struct AssistantPanel { include_conversation_in_next_inline_assist: bool, inline_prompt_history: VecDeque, _watch_saved_conversations: Task>, + semantic_index: Option>, } impl AssistantPanel { @@ -191,6 +193,9 @@ impl AssistantPanel { toolbar.add_item(cx.add_view(|cx| BufferSearchBar::new(cx)), cx); toolbar }); + + let semantic_index = SemanticIndex::global(cx); + let mut this = Self { workspace: workspace_handle, active_editor_index: Default::default(), @@ -215,6 +220,7 @@ impl AssistantPanel { include_conversation_in_next_inline_assist: false, inline_prompt_history: Default::default(), _watch_saved_conversations, + semantic_index, }; let mut old_dock_position = this.position(cx); @@ -578,10 +584,55 @@ impl AssistantPanel { let codegen_kind = codegen.read(cx).kind().clone(); let user_prompt = user_prompt.to_string(); + + let project = if let Some(workspace) = self.workspace.upgrade(cx) { + workspace.read(cx).project() + } else { + return; + }; + + let project = project.to_owned(); + let search_results = if let Some(semantic_index) = self.semantic_index.clone() { + let search_results = semantic_index.update(cx, |this, cx| { + this.search_project(project, user_prompt.to_string(), 10, vec![], vec![], cx) + }); + + cx.background() + .spawn(async move { search_results.await.unwrap_or_default() }) + } else { + Task::ready(Vec::new()) + }; + + let snippets = cx.spawn(|_, cx| async move { + let mut snippets = Vec::new(); + for result in search_results.await { + snippets.push(result.buffer.read_with(&cx, |buffer, _| { + buffer + .snapshot() + .text_for_range(result.range) + .collect::() + })); + } + snippets + }); + let prompt = cx.background().spawn(async move { + let snippets = snippets.await; + for snippet in &snippets { + println!("SNIPPET: \n{:?}", snippet); + } + let language_name = language_name.as_deref(); - generate_content_prompt(user_prompt, language_name, &buffer, range, codegen_kind) + generate_content_prompt( + user_prompt, + language_name, + &buffer, + range, + codegen_kind, + snippets, + ) }); + let mut messages = Vec::new(); let mut model = settings::get::(cx) .default_open_ai_model diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 2451369a184b18c312efa1a829b9c37c40e06579..2301cd88ff1acca7d501e223031bbef9e20c28fb 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -121,6 +121,7 @@ pub fn generate_content_prompt( buffer: &BufferSnapshot, range: Range, kind: CodegenKind, + search_results: Vec, ) -> String { let mut prompt = String::new(); From e9637267efb636e3da4b08570ed3b64cc17dce02 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 2 Oct 2023 19:50:57 +0300 Subject: [PATCH 002/180] add placeholder button for retrieving additional context --- crates/assistant/src/assistant_panel.rs | 34 +++++++++++++++ crates/theme/src/theme.rs | 1 + styles/src/style_tree/assistant.ts | 56 +++++++++++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 8fa0327134865de41c6cba134b0ff939baae8f99..8cba4c4d9f89a44378b2cf76ac85d59dfe6ff89b 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -73,6 +73,7 @@ actions!( ResetKey, InlineAssist, ToggleIncludeConversation, + ToggleRetrieveContext, ] ); @@ -109,6 +110,7 @@ pub fn init(cx: &mut AppContext) { cx.add_action(InlineAssistant::confirm); cx.add_action(InlineAssistant::cancel); cx.add_action(InlineAssistant::toggle_include_conversation); + cx.add_action(InlineAssistant::toggle_retrieve_context); cx.add_action(InlineAssistant::move_up); cx.add_action(InlineAssistant::move_down); } @@ -147,6 +149,7 @@ pub struct AssistantPanel { inline_prompt_history: VecDeque, _watch_saved_conversations: Task>, semantic_index: Option>, + retrieve_context_in_next_inline_assist: bool, } impl AssistantPanel { @@ -221,6 +224,7 @@ impl AssistantPanel { inline_prompt_history: Default::default(), _watch_saved_conversations, semantic_index, + retrieve_context_in_next_inline_assist: false, }; let mut old_dock_position = this.position(cx); @@ -314,6 +318,7 @@ impl AssistantPanel { codegen.clone(), self.workspace.clone(), cx, + self.retrieve_context_in_next_inline_assist, ); cx.focus_self(); assistant @@ -446,6 +451,9 @@ impl AssistantPanel { } => { self.include_conversation_in_next_inline_assist = *include_conversation; } + InlineAssistantEvent::RetrieveContextToggled { retrieve_context } => { + self.retrieve_context_in_next_inline_assist = *retrieve_context + } } } @@ -2679,6 +2687,9 @@ enum InlineAssistantEvent { IncludeConversationToggled { include_conversation: bool, }, + RetrieveContextToggled { + retrieve_context: bool, + }, } struct InlineAssistant { @@ -2694,6 +2705,7 @@ struct InlineAssistant { pending_prompt: String, codegen: ModelHandle, _subscriptions: Vec, + retrieve_context: bool, } impl Entity for InlineAssistant { @@ -2722,6 +2734,18 @@ impl View for InlineAssistant { .element() .aligned(), ) + .with_child( + Button::action(ToggleRetrieveContext) + .with_tooltip("Retrieve Context", theme.tooltip.clone()) + .with_id(self.id) + .with_contents(theme::components::svg::Svg::new( + "icons/magnifying_glass.svg", + )) + .toggleable(self.retrieve_context) + .with_style(theme.assistant.inline.retrieve_context.clone()) + .element() + .aligned(), + ) .with_children(if let Some(error) = self.codegen.read(cx).error() { Some( Svg::new("icons/error.svg") @@ -2802,6 +2826,7 @@ impl InlineAssistant { codegen: ModelHandle, workspace: WeakViewHandle, cx: &mut ViewContext, + retrieve_context: bool, ) -> Self { let prompt_editor = cx.add_view(|cx| { let mut editor = Editor::single_line( @@ -2832,6 +2857,7 @@ impl InlineAssistant { pending_prompt: String::new(), codegen, _subscriptions: subscriptions, + retrieve_context, } } @@ -2902,6 +2928,14 @@ impl InlineAssistant { } } + fn toggle_retrieve_context(&mut self, _: &ToggleRetrieveContext, cx: &mut ViewContext) { + self.retrieve_context = !self.retrieve_context; + cx.emit(InlineAssistantEvent::RetrieveContextToggled { + retrieve_context: self.retrieve_context, + }); + cx.notify(); + } + fn toggle_include_conversation( &mut self, _: &ToggleIncludeConversation, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 5ea5ce877829e2c3dae5f622b08ec35e0c283e34..1ebdcd0ba60bc0526934d72ec550f7038332d03b 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1190,6 +1190,7 @@ pub struct InlineAssistantStyle { pub disabled_editor: FieldEditor, pub pending_edit_background: Color, pub include_conversation: ToggleIconButtonStyle, + pub retrieve_context: ToggleIconButtonStyle, } #[derive(Clone, Deserialize, Default, JsonSchema)] diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index cc6ee4b08055e28be68f5602cb8b7c1109dec53b..7fd1388d9cf4523e5429c198e74b0072015a54a9 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -79,6 +79,62 @@ export default function assistant(): any { }, }, pending_edit_background: background(theme.highest, "positive"), + retrieve_context: toggleable({ + base: interactive({ + base: { + icon_size: 12, + color: foreground(theme.highest, "variant"), + + button_width: 12, + background: background(theme.highest, "on"), + corner_radius: 2, + border: { + width: 1., color: background(theme.highest, "on") + }, + padding: { + left: 4, + right: 4, + top: 4, + bottom: 4, + }, + }, + state: { + hovered: { + ...text(theme.highest, "mono", "variant", "hovered"), + background: background(theme.highest, "on", "hovered"), + border: { + width: 1., color: background(theme.highest, "on", "hovered") + }, + }, + clicked: { + ...text(theme.highest, "mono", "variant", "pressed"), + background: background(theme.highest, "on", "pressed"), + border: { + width: 1., color: background(theme.highest, "on", "pressed") + }, + }, + }, + }), + state: { + active: { + default: { + icon_size: 12, + button_width: 12, + color: foreground(theme.highest, "variant"), + background: background(theme.highest, "accent"), + border: border(theme.highest, "accent"), + }, + hovered: { + background: background(theme.highest, "accent", "hovered"), + border: border(theme.highest, "accent", "hovered"), + }, + clicked: { + background: background(theme.highest, "accent", "pressed"), + border: border(theme.highest, "accent", "pressed"), + }, + }, + }, + }), include_conversation: toggleable({ base: interactive({ base: { From bfe76467b03c23f30a00a3055c0699a7dc171615 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 3 Oct 2023 11:19:54 +0300 Subject: [PATCH 003/180] add retrieve context button to inline assistant --- Cargo.lock | 21 +---- crates/assistant/Cargo.toml | 2 +- crates/assistant/src/assistant_panel.rs | 89 +++++++++++-------- crates/assistant/src/prompts.rs | 112 ++++++++++++++++-------- 4 files changed, 131 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b7d74578d1b816d472c30feba8ddb4a364c7d11..92d17ec0dbc7c471fc3de7a665d2f5aeaf01605d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,7 +108,7 @@ dependencies = [ "rusqlite", "serde", "serde_json", - "tiktoken-rs 0.5.4", + "tiktoken-rs", "util", ] @@ -327,7 +327,7 @@ dependencies = [ "settings", "smol", "theme", - "tiktoken-rs 0.4.5", + "tiktoken-rs", "util", "uuid 1.4.1", "workspace", @@ -6798,7 +6798,7 @@ dependencies = [ "smol", "tempdir", "theme", - "tiktoken-rs 0.5.4", + "tiktoken-rs", "tree-sitter", "tree-sitter-cpp", "tree-sitter-elixir", @@ -7875,21 +7875,6 @@ dependencies = [ "weezl", ] -[[package]] -name = "tiktoken-rs" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52aacc1cff93ba9d5f198c62c49c77fa0355025c729eed3326beaf7f33bc8614" -dependencies = [ - "anyhow", - "base64 0.21.4", - "bstr", - "fancy-regex", - "lazy_static", - "parking_lot 0.12.1", - "rustc-hash", -] - [[package]] name = "tiktoken-rs" version = "0.5.4" diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 8b69e82109abf6c0e9e90069caa40c99b1521985..12f52eee02b44b4f6b5f11a62db196b33f79b536 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -38,7 +38,7 @@ schemars.workspace = true serde.workspace = true serde_json.workspace = true smol.workspace = true -tiktoken-rs = "0.4" +tiktoken-rs = "0.5" [dev-dependencies] editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 8cba4c4d9f89a44378b2cf76ac85d59dfe6ff89b..16d7ee6b811c240143406eb3fc5fb749b8985031 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -437,8 +437,15 @@ impl AssistantPanel { InlineAssistantEvent::Confirmed { prompt, include_conversation, + retrieve_context, } => { - self.confirm_inline_assist(assist_id, prompt, *include_conversation, cx); + self.confirm_inline_assist( + assist_id, + prompt, + *include_conversation, + cx, + *retrieve_context, + ); } InlineAssistantEvent::Canceled => { self.finish_inline_assist(assist_id, true, cx); @@ -532,6 +539,7 @@ impl AssistantPanel { user_prompt: &str, include_conversation: bool, cx: &mut ViewContext, + retrieve_context: bool, ) { let conversation = if include_conversation { self.active_editor() @@ -593,42 +601,49 @@ impl AssistantPanel { let codegen_kind = codegen.read(cx).kind().clone(); let user_prompt = user_prompt.to_string(); - let project = if let Some(workspace) = self.workspace.upgrade(cx) { - workspace.read(cx).project() - } else { - return; - }; + let snippets = if retrieve_context { + let project = if let Some(workspace) = self.workspace.upgrade(cx) { + workspace.read(cx).project() + } else { + return; + }; - let project = project.to_owned(); - let search_results = if let Some(semantic_index) = self.semantic_index.clone() { - let search_results = semantic_index.update(cx, |this, cx| { - this.search_project(project, user_prompt.to_string(), 10, vec![], vec![], cx) - }); + let project = project.to_owned(); + let search_results = if let Some(semantic_index) = self.semantic_index.clone() { + let search_results = semantic_index.update(cx, |this, cx| { + this.search_project(project, user_prompt.to_string(), 10, vec![], vec![], cx) + }); - cx.background() - .spawn(async move { search_results.await.unwrap_or_default() }) + cx.background() + .spawn(async move { search_results.await.unwrap_or_default() }) + } else { + Task::ready(Vec::new()) + }; + + let snippets = cx.spawn(|_, cx| async move { + let mut snippets = Vec::new(); + for result in search_results.await { + snippets.push(result.buffer.read_with(&cx, |buffer, _| { + buffer + .snapshot() + .text_for_range(result.range) + .collect::() + })); + } + snippets + }); + snippets } else { Task::ready(Vec::new()) }; - let snippets = cx.spawn(|_, cx| async move { - let mut snippets = Vec::new(); - for result in search_results.await { - snippets.push(result.buffer.read_with(&cx, |buffer, _| { - buffer - .snapshot() - .text_for_range(result.range) - .collect::() - })); - } - snippets - }); + let mut model = settings::get::(cx) + .default_open_ai_model + .clone(); + let model_name = model.full_name(); let prompt = cx.background().spawn(async move { let snippets = snippets.await; - for snippet in &snippets { - println!("SNIPPET: \n{:?}", snippet); - } let language_name = language_name.as_deref(); generate_content_prompt( @@ -638,13 +653,11 @@ impl AssistantPanel { range, codegen_kind, snippets, + model_name, ) }); let mut messages = Vec::new(); - let mut model = settings::get::(cx) - .default_open_ai_model - .clone(); if let Some(conversation) = conversation { let conversation = conversation.read(cx); let buffer = conversation.buffer.read(cx); @@ -1557,12 +1570,14 @@ impl Conversation { Role::Assistant => "assistant".into(), Role::System => "system".into(), }, - content: self - .buffer - .read(cx) - .text_for_range(message.offset_range) - .collect(), + content: Some( + self.buffer + .read(cx) + .text_for_range(message.offset_range) + .collect(), + ), name: None, + function_call: None, }) }) .collect::>(); @@ -2681,6 +2696,7 @@ enum InlineAssistantEvent { Confirmed { prompt: String, include_conversation: bool, + retrieve_context: bool, }, Canceled, Dismissed, @@ -2922,6 +2938,7 @@ impl InlineAssistant { cx.emit(InlineAssistantEvent::Confirmed { prompt, include_conversation: self.include_conversation, + retrieve_context: self.retrieve_context, }); self.confirmed = true; cx.notify(); diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 2301cd88ff1acca7d501e223031bbef9e20c28fb..1e43833feaef8859903819988b0e628f9611b937 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -1,8 +1,10 @@ use crate::codegen::CodegenKind; use language::{BufferSnapshot, OffsetRangeExt, ToOffset}; use std::cmp; +use std::fmt::Write; +use std::iter; use std::ops::Range; -use std::{fmt::Write, iter}; +use tiktoken_rs::ChatCompletionRequestMessage; fn summarize(buffer: &BufferSnapshot, selected_range: Range) -> String { #[derive(Debug)] @@ -122,69 +124,103 @@ pub fn generate_content_prompt( range: Range, kind: CodegenKind, search_results: Vec, + model: &str, ) -> String { - let mut prompt = String::new(); + const MAXIMUM_SNIPPET_TOKEN_COUNT: usize = 500; + + let mut prompts = Vec::new(); // General Preamble if let Some(language_name) = language_name { - writeln!(prompt, "You're an expert {language_name} engineer.\n").unwrap(); + prompts.push(format!("You're an expert {language_name} engineer.\n")); } else { - writeln!(prompt, "You're an expert engineer.\n").unwrap(); + prompts.push("You're an expert engineer.\n".to_string()); } + // Snippets + let mut snippet_position = prompts.len() - 1; + let outline = summarize(buffer, range); - writeln!( - prompt, - "The file you are currently working on has the following outline:" - ) - .unwrap(); + prompts.push("The file you are currently working on has the following outline:".to_string()); if let Some(language_name) = language_name { let language_name = language_name.to_lowercase(); - writeln!(prompt, "```{language_name}\n{outline}\n```").unwrap(); + prompts.push(format!("```{language_name}\n{outline}\n```")); } else { - writeln!(prompt, "```\n{outline}\n```").unwrap(); + prompts.push(format!("```\n{outline}\n```")); } match kind { CodegenKind::Generate { position: _ } => { - writeln!(prompt, "In particular, the user's cursor is current on the '<|START|>' span in the above outline, with no text selected.").unwrap(); - writeln!( - prompt, - "Assume the cursor is located where the `<|START|` marker is." - ) - .unwrap(); - writeln!( - prompt, + prompts.push("In particular, the user's cursor is currently on the '<|START|>' span in the above outline, with no text selected.".to_string()); + prompts + .push("Assume the cursor is located where the `<|START|` marker is.".to_string()); + prompts.push( "Text can't be replaced, so assume your answer will be inserted at the cursor." - ) - .unwrap(); - writeln!( - prompt, + .to_string(), + ); + prompts.push(format!( "Generate text based on the users prompt: {user_prompt}" - ) - .unwrap(); + )); } CodegenKind::Transform { range: _ } => { - writeln!(prompt, "In particular, the user has selected a section of the text between the '<|START|' and '|END|>' spans.").unwrap(); - writeln!( - prompt, + prompts.push("In particular, the user has selected a section of the text between the '<|START|' and '|END|>' spans.".to_string()); + prompts.push(format!( "Modify the users code selected text based upon the users prompt: {user_prompt}" - ) - .unwrap(); - writeln!( - prompt, - "You MUST reply with only the adjusted code (within the '<|START|' and '|END|>' spans), not the entire file." - ) - .unwrap(); + )); + prompts.push("You MUST reply with only the adjusted code (within the '<|START|' and '|END|>' spans), not the entire file.".to_string()); } } if let Some(language_name) = language_name { - writeln!(prompt, "Your answer MUST always be valid {language_name}").unwrap(); + prompts.push(format!("Your answer MUST always be valid {language_name}")); + } + prompts.push("Always wrap your response in a Markdown codeblock".to_string()); + prompts.push("Never make remarks about the output.".to_string()); + + let current_messages = [ChatCompletionRequestMessage { + role: "user".to_string(), + content: Some(prompts.join("\n")), + function_call: None, + name: None, + }]; + + let remaining_token_count = if let Ok(current_token_count) = + tiktoken_rs::num_tokens_from_messages(model, ¤t_messages) + { + let max_token_count = tiktoken_rs::model::get_context_size(model); + max_token_count - current_token_count + } else { + // If tiktoken fails to count token count, assume we have no space remaining. + 0 + }; + + // TODO: + // - add repository name to snippet + // - add file path + // - add language + if let Ok(encoding) = tiktoken_rs::get_bpe_from_model(model) { + let template = "You are working inside a large repository, here are a few code snippets that may be useful"; + + for search_result in search_results { + let mut snippet_prompt = template.to_string(); + writeln!(snippet_prompt, "```\n{search_result}\n```").unwrap(); + + let token_count = encoding + .encode_with_special_tokens(snippet_prompt.as_str()) + .len(); + if token_count <= remaining_token_count { + if token_count < MAXIMUM_SNIPPET_TOKEN_COUNT { + prompts.insert(snippet_position, snippet_prompt); + snippet_position += 1; + } + } else { + break; + } + } } - writeln!(prompt, "Always wrap your response in a Markdown codeblock").unwrap(); - writeln!(prompt, "Never make remarks about the output.").unwrap(); + let prompt = prompts.join("\n"); + println!("PROMPT: {:?}", prompt); prompt } From ed894cc06fc010ae6ea15880d02923130a669e11 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 3 Oct 2023 12:09:35 +0300 Subject: [PATCH 004/180] only render retrieve context button if semantic index is enabled --- crates/assistant/src/assistant_panel.rs | 28 ++++++++++++++----------- crates/assistant/src/prompts.rs | 1 - 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 16d7ee6b811c240143406eb3fc5fb749b8985031..33d42c45dc552c6c10ace5fbea5cb317e25477f4 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2750,18 +2750,22 @@ impl View for InlineAssistant { .element() .aligned(), ) - .with_child( - Button::action(ToggleRetrieveContext) - .with_tooltip("Retrieve Context", theme.tooltip.clone()) - .with_id(self.id) - .with_contents(theme::components::svg::Svg::new( - "icons/magnifying_glass.svg", - )) - .toggleable(self.retrieve_context) - .with_style(theme.assistant.inline.retrieve_context.clone()) - .element() - .aligned(), - ) + .with_children(if SemanticIndex::enabled(cx) { + Some( + Button::action(ToggleRetrieveContext) + .with_tooltip("Retrieve Context", theme.tooltip.clone()) + .with_id(self.id) + .with_contents(theme::components::svg::Svg::new( + "icons/magnifying_glass.svg", + )) + .toggleable(self.retrieve_context) + .with_style(theme.assistant.inline.retrieve_context.clone()) + .element() + .aligned(), + ) + } else { + None + }) .with_children(if let Some(error) = self.codegen.read(cx).error() { Some( Svg::new("icons/error.svg") diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 716fd4350578b8c9436b9f1500d903716287a814..487950dbef24064daf30d9e88fcfa326f743c900 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -2,7 +2,6 @@ use crate::codegen::CodegenKind; use language::{BufferSnapshot, OffsetRangeExt, ToOffset}; use std::cmp::{self, Reverse}; use std::fmt::Write; -use std::iter; use std::ops::Range; use tiktoken_rs::ChatCompletionRequestMessage; From 1a2756a2325ddea88d8f8679b8022a8f17d97a30 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 3 Oct 2023 14:07:42 +0300 Subject: [PATCH 005/180] start greedily indexing when inline assistant is started, if project has been previously indexed --- crates/assistant/Cargo.toml | 1 + crates/assistant/src/assistant_panel.rs | 47 ++++++++++++++++++++----- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 12f52eee02b44b4f6b5f11a62db196b33f79b536..e0f90a428411610495b67dc44b74ed9b008d3d82 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -24,6 +24,7 @@ util = { path = "../util" } uuid = { version = "1.1.2", features = ["v4"] } workspace = { path = "../workspace" } semantic_index = { path = "../semantic_index" } +project = { path = "../project" } log.workspace = true anyhow.workspace = true diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 33d42c45dc552c6c10ace5fbea5cb317e25477f4..be46a63c8f6a90985f1d764335b495d70d3be48d 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -31,10 +31,11 @@ use gpui::{ geometry::vector::{vec2f, Vector2F}, platform::{CursorStyle, MouseButton}, Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelContext, - ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, + ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, + WeakModelHandle, WeakViewHandle, WindowContext, }; use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; +use project::Project; use search::BufferSearchBar; use semantic_index::SemanticIndex; use settings::SettingsStore; @@ -272,12 +273,19 @@ impl AssistantPanel { return; }; + let project = workspace.project(); + this.update(cx, |assistant, cx| { - assistant.new_inline_assist(&active_editor, cx) + assistant.new_inline_assist(&active_editor, cx, project) }); } - fn new_inline_assist(&mut self, editor: &ViewHandle, cx: &mut ViewContext) { + fn new_inline_assist( + &mut self, + editor: &ViewHandle, + cx: &mut ViewContext, + project: &ModelHandle, + ) { let api_key = if let Some(api_key) = self.api_key.borrow().clone() { api_key } else { @@ -308,6 +316,27 @@ impl AssistantPanel { Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx) }); + if let Some(semantic_index) = self.semantic_index.clone() { + let project = project.clone(); + cx.spawn(|_, mut cx| async move { + let previously_indexed = semantic_index + .update(&mut cx, |index, cx| { + index.project_previously_indexed(&project, cx) + }) + .await + .unwrap_or(false); + if previously_indexed { + let _ = semantic_index + .update(&mut cx, |index, cx| { + index.index_project(project.clone(), cx) + }) + .await; + } + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } + let measurements = Rc::new(Cell::new(BlockMeasurements::default())); let inline_assistant = cx.add_view(|cx| { let assistant = InlineAssistant::new( @@ -359,6 +388,7 @@ impl AssistantPanel { editor: editor.downgrade(), inline_assistant: Some((block_id, inline_assistant.clone())), codegen: codegen.clone(), + project: project.downgrade(), _subscriptions: vec![ cx.subscribe(&inline_assistant, Self::handle_inline_assistant_event), cx.subscribe(editor, { @@ -561,6 +591,8 @@ impl AssistantPanel { return; }; + let project = pending_assist.project.clone(); + self.inline_prompt_history .retain(|prompt| prompt != user_prompt); self.inline_prompt_history.push_back(user_prompt.into()); @@ -602,13 +634,10 @@ impl AssistantPanel { let user_prompt = user_prompt.to_string(); let snippets = if retrieve_context { - let project = if let Some(workspace) = self.workspace.upgrade(cx) { - workspace.read(cx).project() - } else { + let Some(project) = project.upgrade(cx) else { return; }; - let project = project.to_owned(); let search_results = if let Some(semantic_index) = self.semantic_index.clone() { let search_results = semantic_index.update(cx, |this, cx| { this.search_project(project, user_prompt.to_string(), 10, vec![], vec![], cx) @@ -2864,6 +2893,7 @@ impl InlineAssistant { cx.observe(&codegen, Self::handle_codegen_changed), cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events), ]; + Self { id, prompt_editor, @@ -3019,6 +3049,7 @@ struct PendingInlineAssist { inline_assistant: Option<(BlockId, ViewHandle)>, codegen: ModelHandle, _subscriptions: Vec, + project: WeakModelHandle, } fn merge_ranges(ranges: &mut Vec>, buffer: &MultiBufferSnapshot) { From f40d3e82c0dbef9633c86bcb9175a4908206f222 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 3 Oct 2023 16:26:08 +0300 Subject: [PATCH 006/180] add user prompt for permission to index the project, for context retrieval --- crates/assistant/src/assistant_panel.rs | 77 +++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index be46a63c8f6a90985f1d764335b495d70d3be48d..99151e5ac27f49965703f9a12db2488291012c91 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -29,7 +29,7 @@ use gpui::{ }, fonts::HighlightStyle, geometry::vector::{vec2f, Vector2F}, - platform::{CursorStyle, MouseButton}, + platform::{CursorStyle, MouseButton, PromptLevel}, Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle, WindowContext, @@ -348,6 +348,8 @@ impl AssistantPanel { self.workspace.clone(), cx, self.retrieve_context_in_next_inline_assist, + self.semantic_index.clone(), + project.clone(), ); cx.focus_self(); assistant @@ -2751,6 +2753,9 @@ struct InlineAssistant { codegen: ModelHandle, _subscriptions: Vec, retrieve_context: bool, + semantic_index: Option>, + semantic_permissioned: Option, + project: ModelHandle, } impl Entity for InlineAssistant { @@ -2876,6 +2881,8 @@ impl InlineAssistant { workspace: WeakViewHandle, cx: &mut ViewContext, retrieve_context: bool, + semantic_index: Option>, + project: ModelHandle, ) -> Self { let prompt_editor = cx.add_view(|cx| { let mut editor = Editor::single_line( @@ -2908,7 +2915,24 @@ impl InlineAssistant { codegen, _subscriptions: subscriptions, retrieve_context, + semantic_permissioned: None, + semantic_index, + project, + } + } + + fn semantic_permissioned(&mut self, cx: &mut ViewContext) -> Task> { + if let Some(value) = self.semantic_permissioned { + return Task::ready(Ok(value)); } + + let project = self.project.clone(); + self.semantic_index + .as_mut() + .map(|semantic| { + semantic.update(cx, |this, cx| this.project_previously_indexed(&project, cx)) + }) + .unwrap_or(Task::ready(Ok(false))) } fn handle_prompt_editor_events( @@ -2980,11 +3004,52 @@ impl InlineAssistant { } fn toggle_retrieve_context(&mut self, _: &ToggleRetrieveContext, cx: &mut ViewContext) { - self.retrieve_context = !self.retrieve_context; - cx.emit(InlineAssistantEvent::RetrieveContextToggled { - retrieve_context: self.retrieve_context, - }); - cx.notify(); + let semantic_permissioned = self.semantic_permissioned(cx); + let project = self.project.clone(); + let project_name = project + .read(cx) + .worktree_root_names(cx) + .collect::>() + .join("/"); + let is_plural = project_name.chars().filter(|letter| *letter == '/').count() > 0; + let prompt_text = format!("Would you like to index the '{}' project{} for context retrieval? This requires sending code to the OpenAI API", project_name, + if is_plural { + "s" + } else {""}); + + cx.spawn(|this, mut cx| async move { + // If Necessary prompt user + if !semantic_permissioned.await.unwrap_or(false) { + let mut answer = this.update(&mut cx, |_, cx| { + cx.prompt( + PromptLevel::Info, + prompt_text.as_str(), + &["Continue", "Cancel"], + ) + })?; + + if answer.next().await == Some(0) { + this.update(&mut cx, |this, _| { + this.semantic_permissioned = Some(true); + })?; + } else { + return anyhow::Ok(()); + } + } + + // If permissioned, update context appropriately + this.update(&mut cx, |this, cx| { + this.retrieve_context = !this.retrieve_context; + + cx.emit(InlineAssistantEvent::RetrieveContextToggled { + retrieve_context: this.retrieve_context, + }); + cx.notify(); + })?; + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } fn toggle_include_conversation( From 933c21f3d3dead2cd0717fe289aff5bda1784edd Mon Sep 17 00:00:00 2001 From: KCaverly Date: Tue, 3 Oct 2023 16:53:57 +0300 Subject: [PATCH 007/180] add initial (non updating status) toast --- crates/assistant/src/assistant_panel.rs | 46 ++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 99151e5ac27f49965703f9a12db2488291012c91..e6c120cd643127a66d58f8190b12becb91720952 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -48,7 +48,7 @@ use std::{ path::{Path, PathBuf}, rc::Rc, sync::Arc, - time::Duration, + time::{Duration, Instant}, }; use theme::{ components::{action_button::Button, ComponentExt}, @@ -3044,6 +3044,16 @@ impl InlineAssistant { cx.emit(InlineAssistantEvent::RetrieveContextToggled { retrieve_context: this.retrieve_context, }); + + if this.retrieve_context { + let context_status = this.retrieve_context_status(cx); + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + workspace.show_toast(Toast::new(0, context_status), cx) + }); + } + } + cx.notify(); })?; @@ -3052,6 +3062,40 @@ impl InlineAssistant { .detach_and_log_err(cx); } + fn retrieve_context_status(&self, cx: &mut ViewContext) -> String { + let project = self.project.clone(); + if let Some(semantic_index) = self.semantic_index.clone() { + let status = semantic_index.update(cx, |index, cx| index.status(&project)); + return match status { + // This theoretically shouldnt be a valid code path + semantic_index::SemanticIndexStatus::NotAuthenticated => { + "Not Authenticated!\nPlease ensure you have an `OPENAI_API_KEY` in your environment variables.".to_string() + } + semantic_index::SemanticIndexStatus::Indexed => { + "Indexing for Context Retrieval Complete!".to_string() + } + semantic_index::SemanticIndexStatus::Indexing { remaining_files, rate_limit_expiry } => { + + let mut status = format!("Indexing for Context Retrieval...\nRemaining files to index: {remaining_files}"); + + if let Some(rate_limit_expiry) = rate_limit_expiry { + let remaining_seconds = + rate_limit_expiry.duration_since(Instant::now()); + if remaining_seconds > Duration::from_secs(0) { + writeln!(status, "Rate limit resets in {}s", remaining_seconds.as_secs()).unwrap(); + } + } + status + } + _ => { + "Indexing for Context Retrieval...\nRemaining files to index: 48".to_string() + } + }; + } + + "".to_string() + } + fn toggle_include_conversation( &mut self, _: &ToggleIncludeConversation, From ec1b4e6f8563d52eaa96977cb780fcd6be61c2c1 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Thu, 5 Oct 2023 13:01:11 +0300 Subject: [PATCH 008/180] added initial working status in inline assistant prompt --- crates/ai/src/embedding.rs | 2 +- crates/assistant/src/assistant_panel.rs | 200 +++++++++++++++--------- crates/theme/src/theme.rs | 1 + styles/src/style_tree/assistant.ts | 3 + 4 files changed, 129 insertions(+), 77 deletions(-) diff --git a/crates/ai/src/embedding.rs b/crates/ai/src/embedding.rs index 332470aa546832fc083ca9064d842dc5b66dafd4..510f987cca2b8760bde03e1f4299d5cdb1b00bd8 100644 --- a/crates/ai/src/embedding.rs +++ b/crates/ai/src/embedding.rs @@ -290,7 +290,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { let mut request_number = 0; let mut rate_limiting = false; - let mut request_timeout: u64 = 15; + let mut request_timeout: u64 = 30; let mut response: Response; while request_number < MAX_RETRIES { response = self diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index e6c120cd643127a66d58f8190b12becb91720952..c49d60b8ee69b7de5e7323a05e3659e551791dc0 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -24,10 +24,10 @@ use futures::StreamExt; use gpui::{ actions, elements::{ - ChildView, Component, Empty, Flex, Label, MouseEventHandler, ParentElement, SafeStylable, - Stack, Svg, Text, UniformList, UniformListState, + ChildView, Component, Empty, Flex, Label, LabelStyle, MouseEventHandler, ParentElement, + SafeStylable, Stack, Svg, Text, UniformList, UniformListState, }, - fonts::HighlightStyle, + fonts::{HighlightStyle, TextStyle}, geometry::vector::{vec2f, Vector2F}, platform::{CursorStyle, MouseButton, PromptLevel}, Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelContext, @@ -37,7 +37,7 @@ use gpui::{ use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; use project::Project; use search::BufferSearchBar; -use semantic_index::SemanticIndex; +use semantic_index::{SemanticIndex, SemanticIndexStatus}; use settings::SettingsStore; use std::{ cell::{Cell, RefCell}, @@ -2756,6 +2756,7 @@ struct InlineAssistant { semantic_index: Option>, semantic_permissioned: Option, project: ModelHandle, + maintain_rate_limit: Option>, } impl Entity for InlineAssistant { @@ -2772,67 +2773,65 @@ impl View for InlineAssistant { let theme = theme::current(cx); Flex::row() - .with_child( - Flex::row() - .with_child( - Button::action(ToggleIncludeConversation) - .with_tooltip("Include Conversation", theme.tooltip.clone()) + .with_children([Flex::row() + .with_child( + Button::action(ToggleIncludeConversation) + .with_tooltip("Include Conversation", theme.tooltip.clone()) + .with_id(self.id) + .with_contents(theme::components::svg::Svg::new("icons/ai.svg")) + .toggleable(self.include_conversation) + .with_style(theme.assistant.inline.include_conversation.clone()) + .element() + .aligned(), + ) + .with_children(if SemanticIndex::enabled(cx) { + Some( + Button::action(ToggleRetrieveContext) + .with_tooltip("Retrieve Context", theme.tooltip.clone()) .with_id(self.id) - .with_contents(theme::components::svg::Svg::new("icons/ai.svg")) - .toggleable(self.include_conversation) - .with_style(theme.assistant.inline.include_conversation.clone()) + .with_contents(theme::components::svg::Svg::new( + "icons/magnifying_glass.svg", + )) + .toggleable(self.retrieve_context) + .with_style(theme.assistant.inline.retrieve_context.clone()) .element() .aligned(), ) - .with_children(if SemanticIndex::enabled(cx) { - Some( - Button::action(ToggleRetrieveContext) - .with_tooltip("Retrieve Context", theme.tooltip.clone()) - .with_id(self.id) - .with_contents(theme::components::svg::Svg::new( - "icons/magnifying_glass.svg", - )) - .toggleable(self.retrieve_context) - .with_style(theme.assistant.inline.retrieve_context.clone()) - .element() - .aligned(), - ) - } else { - None - }) - .with_children(if let Some(error) = self.codegen.read(cx).error() { - Some( - Svg::new("icons/error.svg") - .with_color(theme.assistant.error_icon.color) - .constrained() - .with_width(theme.assistant.error_icon.width) - .contained() - .with_style(theme.assistant.error_icon.container) - .with_tooltip::( - self.id, - error.to_string(), - None, - theme.tooltip.clone(), - cx, - ) - .aligned(), - ) - } else { - None - }) - .aligned() - .constrained() - .dynamically({ - let measurements = self.measurements.clone(); - move |constraint, _, _| { - let measurements = measurements.get(); - SizeConstraint { - min: vec2f(measurements.gutter_width, constraint.min.y()), - max: vec2f(measurements.gutter_width, constraint.max.y()), - } + } else { + None + }) + .with_children(if let Some(error) = self.codegen.read(cx).error() { + Some( + Svg::new("icons/error.svg") + .with_color(theme.assistant.error_icon.color) + .constrained() + .with_width(theme.assistant.error_icon.width) + .contained() + .with_style(theme.assistant.error_icon.container) + .with_tooltip::( + self.id, + error.to_string(), + None, + theme.tooltip.clone(), + cx, + ) + .aligned(), + ) + } else { + None + }) + .aligned() + .constrained() + .dynamically({ + let measurements = self.measurements.clone(); + move |constraint, _, _| { + let measurements = measurements.get(); + SizeConstraint { + min: vec2f(measurements.gutter_width, constraint.min.y()), + max: vec2f(measurements.gutter_width, constraint.max.y()), } - }), - ) + } + })]) .with_child(Empty::new().constrained().dynamically({ let measurements = self.measurements.clone(); move |constraint, _, _| { @@ -2855,6 +2854,19 @@ impl View for InlineAssistant { .left() .flex(1., true), ) + .with_children(if self.retrieve_context { + Some( + Flex::row() + .with_child(Label::new( + self.retrieve_context_status(cx), + theme.assistant.inline.context_status.text.clone(), + )) + .flex(1., true) + .aligned(), + ) + } else { + None + }) .contained() .with_style(theme.assistant.inline.container) .into_any() @@ -2896,11 +2908,15 @@ impl InlineAssistant { editor.set_placeholder_text(placeholder, cx); editor }); - let subscriptions = vec![ + let mut subscriptions = vec![ cx.observe(&codegen, Self::handle_codegen_changed), cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events), ]; + if let Some(semantic_index) = semantic_index.clone() { + subscriptions.push(cx.observe(&semantic_index, Self::semantic_index_changed)); + } + Self { id, prompt_editor, @@ -2918,6 +2934,7 @@ impl InlineAssistant { semantic_permissioned: None, semantic_index, project, + maintain_rate_limit: None, } } @@ -2947,6 +2964,34 @@ impl InlineAssistant { } } + fn semantic_index_changed( + &mut self, + semantic_index: ModelHandle, + cx: &mut ViewContext, + ) { + let project = self.project.clone(); + let status = semantic_index.read(cx).status(&project); + match status { + SemanticIndexStatus::Indexing { + rate_limit_expiry: Some(_), + .. + } => { + if self.maintain_rate_limit.is_none() { + self.maintain_rate_limit = Some(cx.spawn(|this, mut cx| async move { + loop { + cx.background().timer(Duration::from_secs(1)).await; + this.update(&mut cx, |_, cx| cx.notify()).log_err(); + } + })); + } + return; + } + _ => { + self.maintain_rate_limit = None; + } + } + } + fn handle_codegen_changed(&mut self, _: ModelHandle, cx: &mut ViewContext) { let is_read_only = !self.codegen.read(cx).idle(); self.prompt_editor.update(cx, |editor, cx| { @@ -3044,16 +3089,7 @@ impl InlineAssistant { cx.emit(InlineAssistantEvent::RetrieveContextToggled { retrieve_context: this.retrieve_context, }); - - if this.retrieve_context { - let context_status = this.retrieve_context_status(cx); - if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - workspace.show_toast(Toast::new(0, context_status), cx) - }); - } - } - + this.index_project(project, cx).log_err(); cx.notify(); })?; @@ -3062,6 +3098,18 @@ impl InlineAssistant { .detach_and_log_err(cx); } + fn index_project( + &self, + project: ModelHandle, + cx: &mut ViewContext, + ) -> anyhow::Result<()> { + if let Some(semantic_index) = self.semantic_index.clone() { + let _ = semantic_index.update(cx, |index, cx| index.index_project(project, cx)); + } + + anyhow::Ok(()) + } + fn retrieve_context_status(&self, cx: &mut ViewContext) -> String { let project = self.project.clone(); if let Some(semantic_index) = self.semantic_index.clone() { @@ -3072,23 +3120,23 @@ impl InlineAssistant { "Not Authenticated!\nPlease ensure you have an `OPENAI_API_KEY` in your environment variables.".to_string() } semantic_index::SemanticIndexStatus::Indexed => { - "Indexing for Context Retrieval Complete!".to_string() + "Indexing Complete!".to_string() } semantic_index::SemanticIndexStatus::Indexing { remaining_files, rate_limit_expiry } => { - let mut status = format!("Indexing for Context Retrieval...\nRemaining files to index: {remaining_files}"); + let mut status = format!("Remaining files to index for Context Retrieval: {remaining_files}"); if let Some(rate_limit_expiry) = rate_limit_expiry { let remaining_seconds = rate_limit_expiry.duration_since(Instant::now()); if remaining_seconds > Duration::from_secs(0) { - writeln!(status, "Rate limit resets in {}s", remaining_seconds.as_secs()).unwrap(); + write!(status, " (rate limit resets in {}s)", remaining_seconds.as_secs()).unwrap(); } } status } - _ => { - "Indexing for Context Retrieval...\nRemaining files to index: 48".to_string() + semantic_index::SemanticIndexStatus::NotIndexed => { + "Not Indexed for Context Retrieval".to_string() } }; } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 600ac7f14ae877f065d2069bc93b8a7457443c35..4ed32b6d1bb87b78f213d0ee1c4a2d5fe57511f4 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1191,6 +1191,7 @@ pub struct InlineAssistantStyle { pub pending_edit_background: Color, pub include_conversation: ToggleIconButtonStyle, pub retrieve_context: ToggleIconButtonStyle, + pub context_status: ContainedText, } #[derive(Clone, Deserialize, Default, JsonSchema)] diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index 7fd1388d9cf4523e5429c198e74b0072015a54a9..7e7b597956cf90fa8d6a233245036f773de34afe 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -79,6 +79,9 @@ export default function assistant(): any { }, }, pending_edit_background: background(theme.highest, "positive"), + context_status: { + ...text(theme.highest, "mono", "disabled", { size: "sm" }), + }, retrieve_context: toggleable({ base: interactive({ base: { From 0666fa80ac934f91a744988b405be9e80a4ccfb3 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Thu, 5 Oct 2023 16:49:25 +0300 Subject: [PATCH 009/180] moved status to icon with additional information in tooltip --- crates/ai/src/embedding.rs | 22 +--- crates/assistant/src/assistant_panel.rs | 166 +++++++++++++++++++----- crates/theme/src/theme.rs | 9 +- styles/src/style_tree/assistant.ts | 17 ++- 4 files changed, 162 insertions(+), 52 deletions(-) diff --git a/crates/ai/src/embedding.rs b/crates/ai/src/embedding.rs index 510f987cca2b8760bde03e1f4299d5cdb1b00bd8..4587ece0a23d116c55f07405e009497486d583d7 100644 --- a/crates/ai/src/embedding.rs +++ b/crates/ai/src/embedding.rs @@ -85,25 +85,6 @@ impl Embedding { } } -// impl FromSql for Embedding { -// fn column_result(value: ValueRef) -> FromSqlResult { -// let bytes = value.as_blob()?; -// let embedding: Result, Box> = bincode::deserialize(bytes); -// if embedding.is_err() { -// return Err(rusqlite::types::FromSqlError::Other(embedding.unwrap_err())); -// } -// Ok(Embedding(embedding.unwrap())) -// } -// } - -// impl ToSql for Embedding { -// fn to_sql(&self) -> rusqlite::Result { -// let bytes = bincode::serialize(&self.0) -// .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?; -// Ok(ToSqlOutput::Owned(rusqlite::types::Value::Blob(bytes))) -// } -// } - #[derive(Clone)] pub struct OpenAIEmbeddings { pub client: Arc, @@ -290,7 +271,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { let mut request_number = 0; let mut rate_limiting = false; - let mut request_timeout: u64 = 30; + let mut request_timeout: u64 = 15; let mut response: Response; while request_number < MAX_RETRIES { response = self @@ -300,6 +281,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { request_timeout, ) .await?; + request_number += 1; match response.status() { diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index c49d60b8ee69b7de5e7323a05e3659e551791dc0..25c724168884836c53013b5d3c17cefa78a1917e 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -52,7 +52,7 @@ use std::{ }; use theme::{ components::{action_button::Button, ComponentExt}, - AssistantStyle, + AssistantStyle, Icon, }; use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt}; use uuid::Uuid; @@ -2857,10 +2857,7 @@ impl View for InlineAssistant { .with_children(if self.retrieve_context { Some( Flex::row() - .with_child(Label::new( - self.retrieve_context_status(cx), - theme.assistant.inline.context_status.text.clone(), - )) + .with_children(self.retrieve_context_status(cx)) .flex(1., true) .aligned(), ) @@ -3110,40 +3107,149 @@ impl InlineAssistant { anyhow::Ok(()) } - fn retrieve_context_status(&self, cx: &mut ViewContext) -> String { + fn retrieve_context_status( + &self, + cx: &mut ViewContext, + ) -> Option> { + enum ContextStatusIcon {} let project = self.project.clone(); - if let Some(semantic_index) = self.semantic_index.clone() { - let status = semantic_index.update(cx, |index, cx| index.status(&project)); - return match status { - // This theoretically shouldnt be a valid code path - semantic_index::SemanticIndexStatus::NotAuthenticated => { - "Not Authenticated!\nPlease ensure you have an `OPENAI_API_KEY` in your environment variables.".to_string() - } - semantic_index::SemanticIndexStatus::Indexed => { - "Indexing Complete!".to_string() - } - semantic_index::SemanticIndexStatus::Indexing { remaining_files, rate_limit_expiry } => { - - let mut status = format!("Remaining files to index for Context Retrieval: {remaining_files}"); + if let Some(semantic_index) = SemanticIndex::global(cx) { + let status = semantic_index.update(cx, |index, _| index.status(&project)); + let theme = theme::current(cx); + match status { + SemanticIndexStatus::NotAuthenticated {} => Some( + Svg::new("icons/error.svg") + .with_color(theme.assistant.error_icon.color) + .constrained() + .with_width(theme.assistant.error_icon.width) + .contained() + .with_style(theme.assistant.error_icon.container) + .with_tooltip::( + self.id, + "Not Authenticated. Please ensure you have a valid 'OPENAI_API_KEY' in your environment variables.", + None, + theme.tooltip.clone(), + cx, + ) + .aligned() + .into_any(), + ), + SemanticIndexStatus::NotIndexed {} => Some( + Svg::new("icons/error.svg") + .with_color(theme.assistant.inline.context_status.error_icon.color) + .constrained() + .with_width(theme.assistant.inline.context_status.error_icon.width) + .contained() + .with_style(theme.assistant.inline.context_status.error_icon.container) + .with_tooltip::( + self.id, + "Not Indexed", + None, + theme.tooltip.clone(), + cx, + ) + .aligned() + .into_any(), + ), + SemanticIndexStatus::Indexing { + remaining_files, + rate_limit_expiry, + } => { + + let mut status_text = if remaining_files == 0 { + "Indexing...".to_string() + } else { + format!("Remaining files to index: {remaining_files}") + }; if let Some(rate_limit_expiry) = rate_limit_expiry { - let remaining_seconds = - rate_limit_expiry.duration_since(Instant::now()); - if remaining_seconds > Duration::from_secs(0) { - write!(status, " (rate limit resets in {}s)", remaining_seconds.as_secs()).unwrap(); + let remaining_seconds = rate_limit_expiry.duration_since(Instant::now()); + if remaining_seconds > Duration::from_secs(0) && remaining_files > 0 { + write!( + status_text, + " (rate limit expires in {}s)", + remaining_seconds.as_secs() + ) + .unwrap(); } } - status - } - semantic_index::SemanticIndexStatus::NotIndexed => { - "Not Indexed for Context Retrieval".to_string() + Some( + Svg::new("icons/bolt.svg") + .with_color(theme.assistant.inline.context_status.in_progress_icon.color) + .constrained() + .with_width(theme.assistant.inline.context_status.in_progress_icon.width) + .contained() + .with_style(theme.assistant.inline.context_status.in_progress_icon.container) + .with_tooltip::( + self.id, + status_text, + None, + theme.tooltip.clone(), + cx, + ) + .aligned() + .into_any(), + ) } - }; + SemanticIndexStatus::Indexed {} => Some( + Svg::new("icons/circle_check.svg") + .with_color(theme.assistant.inline.context_status.complete_icon.color) + .constrained() + .with_width(theme.assistant.inline.context_status.complete_icon.width) + .contained() + .with_style(theme.assistant.inline.context_status.complete_icon.container) + .with_tooltip::( + self.id, + "Indexing Complete", + None, + theme.tooltip.clone(), + cx, + ) + .aligned() + .into_any(), + ), + } + } else { + None } - - "".to_string() } + // fn retrieve_context_status(&self, cx: &mut ViewContext) -> String { + // let project = self.project.clone(); + // if let Some(semantic_index) = self.semantic_index.clone() { + // let status = semantic_index.update(cx, |index, cx| index.status(&project)); + // return match status { + // // This theoretically shouldnt be a valid code path + // // As the inline assistant cant be launched without an API key + // // We keep it here for safety + // semantic_index::SemanticIndexStatus::NotAuthenticated => { + // "Not Authenticated!\nPlease ensure you have an `OPENAI_API_KEY` in your environment variables.".to_string() + // } + // semantic_index::SemanticIndexStatus::Indexed => { + // "Indexing Complete!".to_string() + // } + // semantic_index::SemanticIndexStatus::Indexing { remaining_files, rate_limit_expiry } => { + + // let mut status = format!("Remaining files to index for Context Retrieval: {remaining_files}"); + + // if let Some(rate_limit_expiry) = rate_limit_expiry { + // let remaining_seconds = + // rate_limit_expiry.duration_since(Instant::now()); + // if remaining_seconds > Duration::from_secs(0) { + // write!(status, " (rate limit resets in {}s)", remaining_seconds.as_secs()).unwrap(); + // } + // } + // status + // } + // semantic_index::SemanticIndexStatus::NotIndexed => { + // "Not Indexed for Context Retrieval".to_string() + // } + // }; + // } + + // "".to_string() + // } + fn toggle_include_conversation( &mut self, _: &ToggleIncludeConversation, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 4ed32b6d1bb87b78f213d0ee1c4a2d5fe57511f4..21673b0f0480753ac5621f1770b639523eb2c2f4 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1191,7 +1191,14 @@ pub struct InlineAssistantStyle { pub pending_edit_background: Color, pub include_conversation: ToggleIconButtonStyle, pub retrieve_context: ToggleIconButtonStyle, - pub context_status: ContainedText, + pub context_status: ContextStatusStyle, +} + +#[derive(Clone, Deserialize, Default, JsonSchema)] +pub struct ContextStatusStyle { + pub error_icon: Icon, + pub in_progress_icon: Icon, + pub complete_icon: Icon, } #[derive(Clone, Deserialize, Default, JsonSchema)] diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index 7e7b597956cf90fa8d6a233245036f773de34afe..57737eab069c7e7fad210863d35f646b1819ba97 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -80,7 +80,21 @@ export default function assistant(): any { }, pending_edit_background: background(theme.highest, "positive"), context_status: { - ...text(theme.highest, "mono", "disabled", { size: "sm" }), + error_icon: { + margin: { left: 8, right: 8 }, + color: foreground(theme.highest, "negative"), + width: 12, + }, + in_progress_icon: { + margin: { left: 8, right: 8 }, + color: foreground(theme.highest, "warning"), + width: 12, + }, + complete_icon: { + margin: { left: 8, right: 8 }, + color: foreground(theme.highest, "positive"), + width: 12, + } }, retrieve_context: toggleable({ base: interactive({ @@ -94,6 +108,7 @@ export default function assistant(): any { border: { width: 1., color: background(theme.highest, "on") }, + margin: { left: 2 }, padding: { left: 4, right: 4, From a63eccf18839ecbfd35f45bd2da87b0c73a4cb2d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 4 Oct 2023 22:46:28 -0600 Subject: [PATCH 010/180] Add url schemes to Zed --- crates/util/src/channel.rs | 26 ++++++++++++++++++++++++++ crates/zed/Cargo.toml | 3 +++ 2 files changed, 29 insertions(+) diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 274fd576a050076511c8c1253b7187fbd437e8c3..89d42ffba6bdfc2b31b36fb7c78aeed5217f6823 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -1,6 +1,7 @@ use std::env; use lazy_static::lazy_static; +use url::Url; lazy_static! { pub static ref RELEASE_CHANNEL_NAME: String = if cfg!(debug_assertions) { @@ -15,6 +16,23 @@ lazy_static! { "stable" => ReleaseChannel::Stable, _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME), }; + + static ref URL_SCHEME: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + "dev" => "zed-dev:/", + "preview" => "zed-preview:/", + "stable" => "zed:/", + // NOTE: this must be kept in sync with ./script/bundle and https://zed.dev. + _ => unreachable!(), + }) + .unwrap(); + static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + "dev" => "http://localhost:3000/dev/", + "preview" => "https://zed.dev/preview/", + "stable" => "https://zed.dev/", + // NOTE: this must be kept in sync with https://zed.dev. + _ => unreachable!(), + }) + .unwrap(); } #[derive(Copy, Clone, PartialEq, Eq, Default)] @@ -41,4 +59,12 @@ impl ReleaseChannel { ReleaseChannel::Stable => "stable", } } + + pub fn url_scheme(&self) -> &'static Url { + &URL_SCHEME + } + + pub fn link_prefix(&self) -> &'static Url { + &LINK_PREFIX + } } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d4ac972a5dfce65a1688fc2517347ea7138bd31a..7eb14559be8ab64e44eabf55eaf8837338473d06 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -162,6 +162,7 @@ identifier = "dev.zed.Zed-Dev" name = "Zed Dev" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] +osx_url_schemes = ["zed-dev"] [package.metadata.bundle-preview] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] @@ -169,6 +170,7 @@ identifier = "dev.zed.Zed-Preview" name = "Zed Preview" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] +osx_url_schemes = ["zed-preview"] [package.metadata.bundle-stable] @@ -177,3 +179,4 @@ identifier = "dev.zed.Zed" name = "Zed" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] +osx_url_schemes = ["zed"] From b258ee5f77f9e84e2d0f7d7aad802dca9076b321 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 5 Oct 2023 11:04:23 -0600 Subject: [PATCH 011/180] Fix ./script/bundle -l --- crates/zed/Cargo.toml | 2 +- script/bundle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 7eb14559be8ab64e44eabf55eaf8837338473d06..3c93462d4b9182fb362d623455d52c9212d8cc9d 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -156,7 +156,7 @@ workspace = { path = "../workspace", features = ["test-support"] } unindent.workspace = true -[package.metadata.bundle-dev] +[package.metadata.bundle] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] identifier = "dev.zed.Zed-Dev" name = "Zed Dev" diff --git a/script/bundle b/script/bundle index 49da1072ceaff28bff27e101caadf85e58d2e2c4..4882189c225f577b0ca82c3ce82631e0191037b5 100755 --- a/script/bundle +++ b/script/bundle @@ -16,7 +16,7 @@ Usage: ${0##*/} [options] [bundle_name] Build the application bundle. Options: - -d Compile in debug mode and print the app bundle's path. + -d Compile in debug mode (doesn't currently work without -l) -l Compile for local architecture only and copy bundle to /Applications. -o Open the resulting DMG or the app itself in local mode. -f Overwrite the local app bundle if it exists. @@ -92,7 +92,7 @@ sed \ Cargo.toml if [ "$local_only" = true ]; then - app_path=$(cargo bundle ${build_flag} --select-workspace-root | xargs) + app_path=$(cargo bundle ${build_flag} --target "$local_target_triple" --select-workspace-root | xargs) else app_path=$(cargo bundle ${build_flag} --target x86_64-apple-darwin --select-workspace-root | xargs) fi From 13192fa03ca4548bd29abed93597c6ac1274340b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 5 Oct 2023 14:23:14 -0600 Subject: [PATCH 012/180] Code to allow opening zed:/channel/1234 Refactored a bit how url arguments are handled to avoid adding too much extra complexity to main. --- crates/cli/src/main.rs | 1 + crates/collab_ui/src/collab_panel.rs | 83 ++++++---------- crates/util/src/channel.rs | 17 +--- crates/workspace/src/workspace.rs | 82 ++++++++++++++++ crates/zed/src/main.rs | 137 +++++++++++---------------- crates/zed/src/open_url.rs | 101 ++++++++++++++++++++ 6 files changed, 272 insertions(+), 149 deletions(-) create mode 100644 crates/zed/src/open_url.rs diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 2f742814a8dbfdfeb7719fbe906858448d8253f8..69cfb7102bbe1b7ef4bb2d182794027e30a45148 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -182,6 +182,7 @@ impl Bundle { kCFStringEncodingUTF8, ptr::null(), )); + // equivalent to: open zed-cli:... -a /Applications/Zed\ Preview.app let urls_to_open = CFArray::from_copyable(&[url_to_open.as_concrete_TypeRef()]); LSOpenFromURLSpec( &LSLaunchURLSpec { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 951c8bf70ca647644943b3804f45cc6646eb395b..3d66e8450ae0435bdc8d69908b43402992575458 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -1969,18 +1969,21 @@ impl CollabPanel { let style = collab_theme.channel_name.inactive_state(); Flex::row() .with_child( - Label::new(channel.name.clone(), style.text.clone()) - .contained() - .with_style(style.container) - .aligned() - .left() - .with_tooltip::( - ix, - "Join channel", - None, - theme.tooltip.clone(), - cx, - ), + Label::new( + channel.name.clone().to_owned() + channel_id.to_string().as_str(), + style.text.clone(), + ) + .contained() + .with_style(style.container) + .aligned() + .left() + .with_tooltip::( + ix, + "Join channel", + None, + theme.tooltip.clone(), + cx, + ), ) .with_children({ let participants = @@ -3187,49 +3190,19 @@ impl CollabPanel { } fn join_channel(&self, channel_id: u64, cx: &mut ViewContext) { - let workspace = self.workspace.clone(); - let window = cx.window(); - let active_call = ActiveCall::global(cx); - cx.spawn(|_, mut cx| async move { - if active_call.read_with(&mut cx, |active_call, cx| { - if let Some(room) = active_call.room() { - let room = room.read(cx); - room.is_sharing_project() && room.remote_participants().len() > 0 - } else { - false - } - }) { - let answer = window.prompt( - PromptLevel::Warning, - "Leaving this call will unshare your current project.\nDo you want to switch channels?", - &["Yes, Join Channel", "Cancel"], - &mut cx, - ); - - if let Some(mut answer) = answer { - if answer.next().await == Some(1) { - return anyhow::Ok(()); - } - } - } - - let room = active_call - .update(&mut cx, |call, cx| call.join_channel(channel_id, cx)) - .await?; - - let task = room.update(&mut cx, |room, cx| { - let workspace = workspace.upgrade(cx)?; - let (project, host) = room.most_active_project()?; - let app_state = workspace.read(cx).app_state().clone(); - Some(workspace::join_remote_project(project, host, app_state, cx)) - }); - if let Some(task) = task { - task.await?; - } - - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + let Some(workspace) = self.workspace.upgrade(cx) else { + return; + }; + let Some(handle) = cx.window().downcast::() else { + return; + }; + workspace::join_channel( + channel_id, + workspace.read(cx).app_state().clone(), + Some(handle), + cx, + ) + .detach_and_log_err(cx) } fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext) { diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 89d42ffba6bdfc2b31b36fb7c78aeed5217f6823..761b17e6af0d74f67f603808a7f515bf4ffdf9b7 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -17,15 +17,14 @@ lazy_static! { _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME), }; - static ref URL_SCHEME: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + pub static ref URL_SCHEME_PREFIX: String = match RELEASE_CHANNEL_NAME.as_str() { "dev" => "zed-dev:/", "preview" => "zed-preview:/", "stable" => "zed:/", - // NOTE: this must be kept in sync with ./script/bundle and https://zed.dev. + // NOTE: this must be kept in sync with osx_url_schemes in Cargo.toml and with https://zed.dev. _ => unreachable!(), - }) - .unwrap(); - static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + }.to_string(); + pub static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { "dev" => "http://localhost:3000/dev/", "preview" => "https://zed.dev/preview/", "stable" => "https://zed.dev/", @@ -59,12 +58,4 @@ impl ReleaseChannel { ReleaseChannel::Stable => "stable", } } - - pub fn url_scheme(&self) -> &'static Url { - &URL_SCHEME - } - - pub fn link_prefix(&self) -> &'static Url { - &LINK_PREFIX - } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f7bb4092291f2b677d8c4b0e0fbc0bdd887ad535..5ec847b28b337ca52879511c8531a9a4e0e380b5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4154,6 +4154,88 @@ pub async fn last_opened_workspace_paths() -> Option { DB.last_workspace().await.log_err().flatten() } +pub fn join_channel( + channel_id: u64, + app_state: Arc, + requesting_window: Option>, + cx: &mut AppContext, +) -> Task> { + let active_call = ActiveCall::global(cx); + cx.spawn(|mut cx| async move { + let should_prompt = active_call.read_with(&mut cx, |active_call, cx| { + let Some(room) = active_call.room().map( |room| room.read(cx) ) else { + return false + }; + + room.is_sharing_project() && room.remote_participants().len() > 0 && + room.channel_id() != Some(channel_id) + }); + + if should_prompt { + if let Some(workspace) = requesting_window { + if let Some(window) = workspace.update(&mut cx, |cx| { + cx.window() + }) { + let answer = window.prompt( + PromptLevel::Warning, + "Leaving this call will unshare your current project.\nDo you want to switch channels?", + &["Yes, Join Channel", "Cancel"], + &mut cx, + ); + + if let Some(mut answer) = answer { + if answer.next().await == Some(1) { + return Ok(()); + } + } + } + } + } + + let room = active_call.update(&mut cx, |active_call, cx| { + active_call.join_channel(channel_id, cx) + }).await?; + + let task = room.update(&mut cx, |room, cx| { + if let Some((project, host)) = room.most_active_project() { + return Some(join_remote_project(project, host, app_state.clone(), cx)) + } + + None + }); + if let Some(task) = task { + task.await?; + return anyhow::Ok(()); + } + + if requesting_window.is_some() { + return anyhow::Ok(()); + } + + // find an existing workspace to focus and show call controls + for window in cx.windows() { + let found = window.update(&mut cx, |cx| { + let is_workspace = cx.root_view().clone().downcast::().is_some(); + if is_workspace { + cx.activate_window(); + } + is_workspace + }); + + if found.unwrap_or(false) { + return anyhow::Ok(()) + } + } + + // no open workspaces + cx.update(|cx| { + Workspace::new_local(vec![], app_state.clone(), requesting_window, cx) + }).await; + + return anyhow::Ok(()); + }) +} + #[allow(clippy::type_complexity)] pub fn open_paths( abs_paths: &[PathBuf], diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index d6f3be2b464f3453ca83f602b56d974989d53cf6..c491d406aff75a17d873d5186b75d397f67f51b3 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -45,7 +45,7 @@ use std::{ }; use sum_tree::Bias; use util::{ - channel::ReleaseChannel, + channel::{ReleaseChannel, URL_SCHEME_PREFIX}, http::{self, HttpClient}, paths::PathLikeWithPosition, }; @@ -61,6 +61,10 @@ use zed::{ only_instance::{ensure_only_instance, IsOnlyInstance}, }; +use crate::open_url::{OpenListener, OpenRequest}; + +mod open_url; + fn main() { let http = http::client(); init_paths(); @@ -92,29 +96,20 @@ fn main() { }) }; - let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded(); - let cli_connections_tx = Arc::new(cli_connections_tx); - let (open_paths_tx, mut open_paths_rx) = mpsc::unbounded(); - let open_paths_tx = Arc::new(open_paths_tx); - let urls_callback_triggered = Arc::new(AtomicBool::new(false)); - - let callback_cli_connections_tx = Arc::clone(&cli_connections_tx); - let callback_open_paths_tx = Arc::clone(&open_paths_tx); - let callback_urls_callback_triggered = Arc::clone(&urls_callback_triggered); - app.on_open_urls(move |urls, _| { - callback_urls_callback_triggered.store(true, Ordering::Release); - open_urls(urls, &callback_cli_connections_tx, &callback_open_paths_tx); - }) - .on_reopen(move |cx| { - if cx.has_global::>() { - if let Some(app_state) = cx.global::>().upgrade() { - workspace::open_new(&app_state, cx, |workspace, cx| { - Editor::new_file(workspace, &Default::default(), cx) - }) - .detach(); + let (listener, mut open_rx) = OpenListener::new(); + let listener = Arc::new(listener); + let callback_listener = listener.clone(); + app.on_open_urls(move |urls, _| callback_listener.open_urls(urls)) + .on_reopen(move |cx| { + if cx.has_global::>() { + if let Some(app_state) = cx.global::>().upgrade() { + workspace::open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } } - } - }); + }); app.run(move |cx| { cx.set_global(*RELEASE_CHANNEL); @@ -226,41 +221,52 @@ fn main() { // TODO Development mode that forces the CLI mode usually runs Zed binary as is instead // of an *app, hence gets no specific callbacks run. Emulate them here, if needed. if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some() - && !urls_callback_triggered.load(Ordering::Acquire) + && !listener.triggered.load(Ordering::Acquire) { - open_urls(collect_url_args(), &cli_connections_tx, &open_paths_tx) + listener.open_urls(collect_url_args()) } - if let Ok(Some(connection)) = cli_connections_rx.try_next() { - cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) - .detach(); - } else if let Ok(Some(paths)) = open_paths_rx.try_next() { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); - } else { - cx.spawn({ - let app_state = app_state.clone(); - |cx| async move { restore_or_create_workspace(&app_state, cx).await } - }) - .detach() - } - - cx.spawn(|cx| { - let app_state = app_state.clone(); - async move { - while let Some(connection) = cli_connections_rx.next().await { - handle_cli_connection(connection, app_state.clone(), cx.clone()).await; - } + match open_rx.try_next() { + Ok(Some(OpenRequest::Paths { paths })) => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .detach(); } - }) - .detach(); + Ok(Some(OpenRequest::CliConnection { connection })) => { + cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) + .detach(); + } + Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx + .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) + .detach(), + Ok(None) | Err(_) => cx + .spawn({ + let app_state = app_state.clone(); + |cx| async move { restore_or_create_workspace(&app_state, cx).await } + }) + .detach(), + } cx.spawn(|mut cx| { let app_state = app_state.clone(); async move { - while let Some(paths) = open_paths_rx.next().await { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); + while let Some(request) = open_rx.next().await { + match request { + OpenRequest::Paths { paths } => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .detach(); + } + OpenRequest::CliConnection { connection } => { + cx.spawn(|cx| { + handle_cli_connection(connection, app_state.clone(), cx) + }) + .detach(); + } + OpenRequest::JoinChannel { channel_id } => cx + .update(|cx| { + workspace::join_channel(channel_id, app_state.clone(), None, cx) + }) + .detach(), + } } } }) @@ -297,37 +303,6 @@ async fn installation_id() -> Result { } } -fn open_urls( - urls: Vec, - cli_connections_tx: &mpsc::UnboundedSender<( - mpsc::Receiver, - IpcSender, - )>, - open_paths_tx: &mpsc::UnboundedSender>, -) { - if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { - if let Some(cli_connection) = connect_to_cli(server_name).log_err() { - cli_connections_tx - .unbounded_send(cli_connection) - .map_err(|_| anyhow!("no listener for cli connections")) - .log_err(); - }; - } else { - let paths: Vec<_> = urls - .iter() - .flat_map(|url| url.strip_prefix("file://")) - .map(|url| { - let decoded = urlencoding::decode_binary(url.as_bytes()); - PathBuf::from(OsStr::from_bytes(decoded.as_ref())) - }) - .collect(); - open_paths_tx - .unbounded_send(paths) - .map_err(|_| anyhow!("no listener for open urls requests")) - .log_err(); - } -} - async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncAppContext) { if let Some(location) = workspace::last_opened_workspace_paths().await { cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx)) diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs new file mode 100644 index 0000000000000000000000000000000000000000..f421633d5b886d691456aeac777533e2d0ed0dcb --- /dev/null +++ b/crates/zed/src/open_url.rs @@ -0,0 +1,101 @@ +use anyhow::anyhow; +use cli::{ipc::IpcSender, CliRequest, CliResponse}; +use futures::channel::mpsc; +use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; +use std::ffi::OsStr; +use std::os::unix::prelude::OsStrExt; +use std::sync::atomic::Ordering; +use std::{path::PathBuf, sync::atomic::AtomicBool}; +use util::channel::URL_SCHEME_PREFIX; +use util::ResultExt; + +use crate::{connect_to_cli, handle_cli_connection}; + +pub enum OpenRequest { + Paths { + paths: Vec, + }, + CliConnection { + connection: (mpsc::Receiver, IpcSender), + }, + JoinChannel { + channel_id: u64, + }, +} + +pub struct OpenListener { + tx: UnboundedSender, + pub triggered: AtomicBool, +} + +impl OpenListener { + pub fn new() -> (Self, UnboundedReceiver) { + let (tx, rx) = mpsc::unbounded(); + ( + OpenListener { + tx, + triggered: AtomicBool::new(false), + }, + rx, + ) + } + + pub fn open_urls(&self, urls: Vec) { + self.triggered.store(true, Ordering::Release); + dbg!(&urls); + let request = if let Some(server_name) = + urls.first().and_then(|url| url.strip_prefix("zed-cli://")) + { + self.handle_cli_connection(server_name) + } else if let Some(request_path) = urls + .first() + .and_then(|url| url.strip_prefix(URL_SCHEME_PREFIX.as_str())) + { + self.handle_zed_url_scheme(request_path) + } else { + self.handle_file_urls(urls) + }; + + if let Some(request) = request { + self.tx + .unbounded_send(request) + .map_err(|_| anyhow!("no listener for open requests")) + .log_err(); + } + } + + fn handle_cli_connection(&self, server_name: &str) -> Option { + if let Some(connection) = connect_to_cli(server_name).log_err() { + return Some(OpenRequest::CliConnection { connection }); + } + + None + } + + fn handle_zed_url_scheme(&self, request_path: &str) -> Option { + let mut parts = request_path.split("/"); + if parts.next() == Some("channel") { + if let Some(slug) = parts.next() { + if let Some(id_str) = slug.split("-").last() { + if let Ok(channel_id) = id_str.parse::() { + return Some(OpenRequest::JoinChannel { channel_id }); + } + } + } + } + None + } + + fn handle_file_urls(&self, urls: Vec) -> Option { + let paths: Vec<_> = urls + .iter() + .flat_map(|url| url.strip_prefix("file://")) + .map(|url| { + let decoded = urlencoding::decode_binary(url.as_bytes()); + PathBuf::from(OsStr::from_bytes(decoded.as_ref())) + }) + .collect(); + + Some(OpenRequest::Paths { paths }) + } +} From 31062d424f385c34ccc3496e74dbd0d57978cf90 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 5 Oct 2023 16:41:08 -0700 Subject: [PATCH 013/180] make bundle script incremental when using debug or local builds --- crates/zed/resources/zed.entitlements | 10 ++-------- crates/zed/src/main.rs | 6 ++---- crates/zed/src/open_url.rs | 2 +- script/bundle | 27 +++++++++++++++++++-------- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/crates/zed/resources/zed.entitlements b/crates/zed/resources/zed.entitlements index f40a8a253ad082bc08e48ed8684b2d553d5065e8..dca0ff676633b9bac7b69114c416536f99262599 100644 --- a/crates/zed/resources/zed.entitlements +++ b/crates/zed/resources/zed.entitlements @@ -10,14 +10,8 @@ com.apple.security.device.camera - com.apple.security.personal-information.addressbook - - com.apple.security.personal-information.calendars - - com.apple.security.personal-information.location - - com.apple.security.personal-information.photos-library - + com.apple.security.keychain-access-groups + MQ55VZLNZQ.dev.zed.Shared diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c491d406aff75a17d873d5186b75d397f67f51b3..9d0451ecfa97cf7da8001ccbcee03fe56a274d5f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -32,12 +32,10 @@ use std::{ ffi::OsStr, fs::OpenOptions, io::{IsTerminal, Write as _}, - os::unix::prelude::OsStrExt, panic, path::{Path, PathBuf}, - str, sync::{ - atomic::{AtomicBool, AtomicU32, Ordering}, + atomic::{AtomicU32, Ordering}, Arc, Weak, }, thread, @@ -45,7 +43,7 @@ use std::{ }; use sum_tree::Bias; use util::{ - channel::{ReleaseChannel, URL_SCHEME_PREFIX}, + channel::ReleaseChannel, http::{self, HttpClient}, paths::PathLikeWithPosition, }; diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index f421633d5b886d691456aeac777533e2d0ed0dcb..1c741a02c89d3ec20d88d866c2d2c4b469385ab2 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -9,7 +9,7 @@ use std::{path::PathBuf, sync::atomic::AtomicBool}; use util::channel::URL_SCHEME_PREFIX; use util::ResultExt; -use crate::{connect_to_cli, handle_cli_connection}; +use crate::connect_to_cli; pub enum OpenRequest { Paths { diff --git a/script/bundle b/script/bundle index 4882189c225f577b0ca82c3ce82631e0191037b5..94741d290fc95839362de6c256b41431f216d7e3 100755 --- a/script/bundle +++ b/script/bundle @@ -5,6 +5,7 @@ set -e build_flag="--release" target_dir="release" open_result=false +local_arch=false local_only=false overwrite_local_app=false bundle_name="" @@ -16,8 +17,8 @@ Usage: ${0##*/} [options] [bundle_name] Build the application bundle. Options: - -d Compile in debug mode (doesn't currently work without -l) - -l Compile for local architecture only and copy bundle to /Applications. + -d Compile in debug mode + -l Compile for local architecture and copy bundle to /Applications, implies -d. -o Open the resulting DMG or the app itself in local mode. -f Overwrite the local app bundle if it exists. -h Display this help and exit. @@ -32,10 +33,20 @@ do case "${flag}" in o) open_result=true;; d) + export CARGO_INCREMENTAL=true + export CARGO_BUNDLE_SKIP_BUILD=true build_flag=""; + local_arch=true + target_dir="debug" + ;; + l) + export CARGO_INCREMENTAL=true + export CARGO_BUNDLE_SKIP_BUILD=true + build_flag="" + local_arch=true + local_only=true target_dir="debug" ;; - l) local_only=true;; f) overwrite_local_app=true;; h) help_info @@ -67,7 +78,7 @@ version_info=$(rustc --version --verbose) host_line=$(echo "$version_info" | grep host) local_target_triple=${host_line#*: } -if [ "$local_only" = true ]; then +if [ "$local_arch" = true ]; then echo "Building for local target only." cargo build ${build_flag} --package zed cargo build ${build_flag} --package cli @@ -91,8 +102,8 @@ sed \ "s/package.metadata.bundle-${channel}/package.metadata.bundle/" \ Cargo.toml -if [ "$local_only" = true ]; then - app_path=$(cargo bundle ${build_flag} --target "$local_target_triple" --select-workspace-root | xargs) +if [ "$local_arch" = true ]; then + app_path=$(cargo bundle ${build_flag} --select-workspace-root | xargs) else app_path=$(cargo bundle ${build_flag} --target x86_64-apple-darwin --select-workspace-root | xargs) fi @@ -101,7 +112,7 @@ mv Cargo.toml.backup Cargo.toml popd echo "Bundled ${app_path}" -if [ "$local_only" = false ]; then +if [ "$local_arch" = false ]; then echo "Creating fat binaries" lipo \ -create \ @@ -136,7 +147,7 @@ else codesign --force --deep --entitlements crates/zed/resources/zed.entitlements --sign - "${app_path}" -v fi -if [ "$target_dir" = "debug" ]; then +if [[ "$target_dir" = "debug" && "$local_only" = false ]]; then if [ "$open_result" = true ]; then open "$app_path" else From c0a13285321754a27cb04b0ad3ff034262e515ef Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 6 Oct 2023 08:30:54 +0300 Subject: [PATCH 014/180] fix spawn bug from calling --- crates/assistant/src/assistant_panel.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 25c724168884836c53013b5d3c17cefa78a1917e..e25514a4e49716e7296fbb9c0be4a4dfc65daa8c 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -3100,8 +3100,13 @@ impl InlineAssistant { project: ModelHandle, cx: &mut ViewContext, ) -> anyhow::Result<()> { - if let Some(semantic_index) = self.semantic_index.clone() { - let _ = semantic_index.update(cx, |index, cx| index.index_project(project, cx)); + if let Some(semantic_index) = SemanticIndex::global(cx) { + cx.spawn(|_, mut cx| async move { + semantic_index + .update(&mut cx, |index, cx| index.index_project(project, cx)) + .await + }) + .detach_and_log_err(cx); } anyhow::Ok(()) From 38ccf23567f134fc6e43bdfc6fecb64e6d358eb8 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 6 Oct 2023 08:46:40 +0300 Subject: [PATCH 015/180] add indexing on inline assistant opening --- crates/assistant/src/assistant_panel.rs | 58 +++++++++++++++++-------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index e25514a4e49716e7296fbb9c0be4a4dfc65daa8c..7e199a4a2f39384e5a749a1e9846050465ab36ff 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -24,10 +24,10 @@ use futures::StreamExt; use gpui::{ actions, elements::{ - ChildView, Component, Empty, Flex, Label, LabelStyle, MouseEventHandler, ParentElement, - SafeStylable, Stack, Svg, Text, UniformList, UniformListState, + ChildView, Component, Empty, Flex, Label, MouseEventHandler, ParentElement, SafeStylable, + Stack, Svg, Text, UniformList, UniformListState, }, - fonts::{HighlightStyle, TextStyle}, + fonts::HighlightStyle, geometry::vector::{vec2f, Vector2F}, platform::{CursorStyle, MouseButton, PromptLevel}, Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelContext, @@ -52,7 +52,7 @@ use std::{ }; use theme::{ components::{action_button::Button, ComponentExt}, - AssistantStyle, Icon, + AssistantStyle, }; use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt}; use uuid::Uuid; @@ -2755,7 +2755,7 @@ struct InlineAssistant { retrieve_context: bool, semantic_index: Option>, semantic_permissioned: Option, - project: ModelHandle, + project: WeakModelHandle, maintain_rate_limit: Option>, } @@ -2914,7 +2914,7 @@ impl InlineAssistant { subscriptions.push(cx.observe(&semantic_index, Self::semantic_index_changed)); } - Self { + let assistant = Self { id, prompt_editor, workspace, @@ -2930,9 +2930,13 @@ impl InlineAssistant { retrieve_context, semantic_permissioned: None, semantic_index, - project, + project: project.downgrade(), maintain_rate_limit: None, - } + }; + + assistant.index_project(cx).log_err(); + + assistant } fn semantic_permissioned(&mut self, cx: &mut ViewContext) -> Task> { @@ -2940,7 +2944,10 @@ impl InlineAssistant { return Task::ready(Ok(value)); } - let project = self.project.clone(); + let Some(project) = self.project.upgrade(cx) else { + return Task::ready(Err(anyhow!("project was dropped"))); + }; + self.semantic_index .as_mut() .map(|semantic| { @@ -2966,7 +2973,10 @@ impl InlineAssistant { semantic_index: ModelHandle, cx: &mut ViewContext, ) { - let project = self.project.clone(); + let Some(project) = self.project.upgrade(cx) else { + return; + }; + let status = semantic_index.read(cx).status(&project); match status { SemanticIndexStatus::Indexing { @@ -3047,7 +3057,11 @@ impl InlineAssistant { fn toggle_retrieve_context(&mut self, _: &ToggleRetrieveContext, cx: &mut ViewContext) { let semantic_permissioned = self.semantic_permissioned(cx); - let project = self.project.clone(); + + let Some(project) = self.project.upgrade(cx) else { + return; + }; + let project_name = project .read(cx) .worktree_root_names(cx) @@ -3086,7 +3100,11 @@ impl InlineAssistant { cx.emit(InlineAssistantEvent::RetrieveContextToggled { retrieve_context: this.retrieve_context, }); - this.index_project(project, cx).log_err(); + + if this.retrieve_context { + this.index_project(cx).log_err(); + } + cx.notify(); })?; @@ -3095,11 +3113,11 @@ impl InlineAssistant { .detach_and_log_err(cx); } - fn index_project( - &self, - project: ModelHandle, - cx: &mut ViewContext, - ) -> anyhow::Result<()> { + fn index_project(&self, cx: &mut ViewContext) -> anyhow::Result<()> { + let Some(project) = self.project.upgrade(cx) else { + return Err(anyhow!("project was dropped!")); + }; + if let Some(semantic_index) = SemanticIndex::global(cx) { cx.spawn(|_, mut cx| async move { semantic_index @@ -3117,7 +3135,11 @@ impl InlineAssistant { cx: &mut ViewContext, ) -> Option> { enum ContextStatusIcon {} - let project = self.project.clone(); + + let Some(project) = self.project.upgrade(cx) else { + return None; + }; + if let Some(semantic_index) = SemanticIndex::global(cx) { let status = semantic_index.update(cx, |index, _| index.status(&project)); let theme = theme::current(cx); From 84553899f6d3cb5f423857fd301cb53c46c2dfb9 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 6 Oct 2023 15:43:28 +0200 Subject: [PATCH 016/180] updated spacing for assistant context status icon --- styles/src/style_tree/assistant.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index 57737eab069c7e7fad210863d35f646b1819ba97..08297731bb8957331f10a9d6fa98c0d1e26ac4b5 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -81,17 +81,17 @@ export default function assistant(): any { pending_edit_background: background(theme.highest, "positive"), context_status: { error_icon: { - margin: { left: 8, right: 8 }, + margin: { left: 8, right: 18 }, color: foreground(theme.highest, "negative"), width: 12, }, in_progress_icon: { - margin: { left: 8, right: 8 }, - color: foreground(theme.highest, "warning"), + margin: { left: 8, right: 18 }, + color: foreground(theme.highest, "positive"), width: 12, }, complete_icon: { - margin: { left: 8, right: 8 }, + margin: { left: 8, right: 18 }, color: foreground(theme.highest, "positive"), width: 12, } From ed548a0de223d03dcb1067309be060e556f1ca55 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 6 Oct 2023 16:08:36 +0200 Subject: [PATCH 017/180] ensure indexing is only done when permissioned --- crates/assistant/src/assistant_panel.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 7e199a4a2f39384e5a749a1e9846050465ab36ff..17e5c161c727bbabc77116a86c1ddd817a2ad8e3 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2939,7 +2939,7 @@ impl InlineAssistant { assistant } - fn semantic_permissioned(&mut self, cx: &mut ViewContext) -> Task> { + fn semantic_permissioned(&self, cx: &mut ViewContext) -> Task> { if let Some(value) = self.semantic_permissioned { return Task::ready(Ok(value)); } @@ -2949,7 +2949,7 @@ impl InlineAssistant { }; self.semantic_index - .as_mut() + .as_ref() .map(|semantic| { semantic.update(cx, |this, cx| this.project_previously_indexed(&project, cx)) }) @@ -3118,11 +3118,17 @@ impl InlineAssistant { return Err(anyhow!("project was dropped!")); }; + let semantic_permissioned = self.semantic_permissioned(cx); if let Some(semantic_index) = SemanticIndex::global(cx) { cx.spawn(|_, mut cx| async move { - semantic_index - .update(&mut cx, |index, cx| index.index_project(project, cx)) - .await + // This has to be updated to accomodate for semantic_permissions + if semantic_permissioned.await.unwrap_or(false) { + semantic_index + .update(&mut cx, |index, cx| index.index_project(project, cx)) + .await + } else { + Err(anyhow!("project is not permissioned for semantic indexing")) + } }) .detach_and_log_err(cx); } From 391179657cdd30f2d4f851c6c945b39fa9b6b9da Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 6 Oct 2023 16:43:19 +0200 Subject: [PATCH 018/180] clean up redundancies in prompts and ensure tokens are being reserved for generation when filling semantic context --- crates/assistant/src/prompts.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 487950dbef24064daf30d9e88fcfa326f743c900..a3a2be1a00bb5ef8d025a34504b8f31d4da9d059 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -125,6 +125,7 @@ pub fn generate_content_prompt( model: &str, ) -> String { const MAXIMUM_SNIPPET_TOKEN_COUNT: usize = 500; + const RESERVED_TOKENS_FOR_GENERATION: usize = 1000; let mut prompts = Vec::new(); @@ -182,11 +183,17 @@ pub fn generate_content_prompt( name: None, }]; - let remaining_token_count = if let Ok(current_token_count) = + let mut remaining_token_count = if let Ok(current_token_count) = tiktoken_rs::num_tokens_from_messages(model, ¤t_messages) { let max_token_count = tiktoken_rs::model::get_context_size(model); - max_token_count - current_token_count + let intermediate_token_count = max_token_count - current_token_count; + + if intermediate_token_count < RESERVED_TOKENS_FOR_GENERATION { + 0 + } else { + intermediate_token_count - RESERVED_TOKENS_FOR_GENERATION + } } else { // If tiktoken fails to count token count, assume we have no space remaining. 0 @@ -197,7 +204,7 @@ pub fn generate_content_prompt( // - add file path // - add language if let Ok(encoding) = tiktoken_rs::get_bpe_from_model(model) { - let template = "You are working inside a large repository, here are a few code snippets that may be useful"; + let mut template = "You are working inside a large repository, here are a few code snippets that may be useful"; for search_result in search_results { let mut snippet_prompt = template.to_string(); @@ -210,6 +217,9 @@ pub fn generate_content_prompt( if token_count < MAXIMUM_SNIPPET_TOKEN_COUNT { prompts.insert(snippet_position, snippet_prompt); snippet_position += 1; + remaining_token_count -= token_count; + // If you have already added the template to the prompt, remove the template. + template = ""; } } else { break; From 2d99b327fc2e7f094692ffd241193180d66a8818 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 5 Oct 2023 19:26:22 -0600 Subject: [PATCH 019/180] Don't wrap on paragraphs For zed-industries/community#2116 --- .cargo/config.toml | 2 +- crates/editor/src/editor_tests.rs | 31 ++++++------------- crates/editor/src/movement.rs | 4 +-- crates/vim/src/test.rs | 25 +++++++++++++++ .../test_data/test_paragraphs_dont_wrap.json | 8 +++++ 5 files changed, 45 insertions(+), 25 deletions(-) create mode 100644 crates/vim/test_data/test_paragraphs_dont_wrap.json diff --git a/.cargo/config.toml b/.cargo/config.toml index 9da6b3be080072d89d16a199e2d60d527eeacd07..e22bdb0f2c70a1ffda714674253cc533e9e7c1d1 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0"] +rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index dee27e0121256edaa38a90bb175d436eba768f96..dc723c70127496cef84b6667799ee04e4e558326 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -1333,7 +1333,7 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx)); cx.assert_editor_state( - &r#"ˇone + &r#"one two three @@ -1344,54 +1344,41 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon .unindent(), ); - cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx)); + cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); cx.assert_editor_state( - &r#"ˇone + &r#"one two - ˇ + three four five - + ˇ six"# .unindent(), ); cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); cx.assert_editor_state( - &r#"ˇone + &r#"one two - + ˇ three four five - sixˇ"# + six"# .unindent(), ); cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); cx.assert_editor_state( - &r#"one + &r#"ˇone two three four five - ˇ - sixˇ"# - .unindent(), - ); - cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); - cx.assert_editor_state( - &r#"one - two - ˇ - three - four - five - ˇ six"# .unindent(), ); diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 974af4bc24070ffb46870badbcb57915a802c1c7..245c2d99770c6c04f6d8e438f8bc62cea1e762ea 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -234,7 +234,7 @@ pub fn start_of_paragraph( ) -> DisplayPoint { let point = display_point.to_point(map); if point.row == 0 { - return map.max_point(); + return DisplayPoint::zero(); } let mut found_non_blank_line = false; @@ -261,7 +261,7 @@ pub fn end_of_paragraph( ) -> DisplayPoint { let point = display_point.to_point(map); if point.row == map.max_buffer_row() { - return DisplayPoint::zero(); + return map.max_point(); } let mut found_non_blank_line = false; diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 52dcb54ce24eb112b7990d9bd9078fdf86698602..34b9e387686f144d577177e86690e59a1f17f3b7 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -652,3 +652,28 @@ async fn test_selection_goal(cx: &mut gpui::TestAppContext) { Lorem Ipsum"}) .await; } + +#[gpui::test] +async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc! {" + one + ˇ + two"}) + .await; + + cx.simulate_shared_keystrokes(["}", "}"]).await; + cx.assert_shared_state(indoc! {" + one + + twˇo"}) + .await; + + cx.simulate_shared_keystrokes(["{", "{", "{"]).await; + cx.assert_shared_state(indoc! {" + ˇone + + two"}) + .await; +} diff --git a/crates/vim/test_data/test_paragraphs_dont_wrap.json b/crates/vim/test_data/test_paragraphs_dont_wrap.json new file mode 100644 index 0000000000000000000000000000000000000000..9e729651be329d1d1c3ff2ba73f8ea8250b095d1 --- /dev/null +++ b/crates/vim/test_data/test_paragraphs_dont_wrap.json @@ -0,0 +1,8 @@ +{"Put":{"state":"one\nˇ\ntwo"}} +{"Key":"}"} +{"Key":"}"} +{"Get":{"state":"one\n\ntwˇo","mode":"Normal"}} +{"Key":"{"} +{"Key":"{"} +{"Key":"{"} +{"Get":{"state":"ˇone\n\ntwo","mode":"Normal"}} From e802c072f7e3e32503355e44cbaa32b5e382e14d Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 5 Sep 2023 22:16:12 -0400 Subject: [PATCH 020/180] Start hacking in autocomplete docs --- crates/editor/src/editor.rs | 91 ++++--- crates/editor/src/hover_popover.rs | 392 ++++++++++++++++++++++++----- crates/theme/src/theme.rs | 8 +- styles/src/style_tree/editor.ts | 8 +- 4 files changed, 382 insertions(+), 117 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 24ffa64a6af73aef827cea17757e5c0e6a4e94c2..b1c5e35703c83b59fe55e3dca46490c4ea6e42be 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -859,7 +859,6 @@ struct CompletionsMenu { id: CompletionId, initial_position: Anchor, buffer: ModelHandle, - project: Option>, completions: Arc<[Completion]>, match_candidates: Vec, matches: Arc<[StringMatch]>, @@ -903,42 +902,17 @@ impl CompletionsMenu { fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { enum CompletionTag {} - let language_servers = self.project.as_ref().map(|project| { - project - .read(cx) - .language_servers_for_buffer(self.buffer.read(cx), cx) - .filter(|(_, server)| server.capabilities().completion_provider.is_some()) - .map(|(adapter, server)| (server.server_id(), adapter.short_name)) - .collect::>() - }); - let needs_server_name = language_servers - .as_ref() - .map_or(false, |servers| servers.len() > 1); - - let get_server_name = - move |lookup_server_id: lsp::LanguageServerId| -> Option<&'static str> { - language_servers - .iter() - .flatten() - .find_map(|(server_id, server_name)| { - if *server_id == lookup_server_id { - Some(*server_name) - } else { - None - } - }) - }; - let widest_completion_ix = self .matches .iter() .enumerate() .max_by_key(|(_, mat)| { let completion = &self.completions[mat.candidate_id]; - let mut len = completion.label.text.chars().count(); + let documentation = &completion.lsp_completion.documentation; - if let Some(server_name) = get_server_name(completion.server_id) { - len += server_name.chars().count(); + let mut len = completion.label.text.chars().count(); + if let Some(lsp::Documentation::String(text)) = documentation { + len += text.chars().count(); } len @@ -948,8 +922,16 @@ impl CompletionsMenu { let completions = self.completions.clone(); let matches = self.matches.clone(); let selected_item = self.selected_item; - let container_style = style.autocomplete.container; - UniformList::new( + + let alongside_docs_text_style = TextStyle { + soft_wrap: true, + ..style.text.clone() + }; + let alongside_docs_width = style.autocomplete.alongside_docs_width; + let alongside_docs_container_style = style.autocomplete.alongside_docs_container; + let outer_container_style = style.autocomplete.container; + + let list = UniformList::new( self.list.clone(), matches.len(), cx, @@ -957,7 +939,9 @@ impl CompletionsMenu { let start_ix = range.start; for (ix, mat) in matches[range].iter().enumerate() { let completion = &completions[mat.candidate_id]; + let documentation = &completion.lsp_completion.documentation; let item_ix = start_ix + ix; + items.push( MouseEventHandler::new::( mat.candidate_id, @@ -986,22 +970,18 @@ impl CompletionsMenu { ), ); - if let Some(server_name) = get_server_name(completion.server_id) { + if let Some(lsp::Documentation::String(text)) = documentation { Flex::row() .with_child(completion_label) .with_children((|| { - if !needs_server_name { - return None; - } - let text_style = TextStyle { - color: style.autocomplete.server_name_color, + color: style.autocomplete.inline_docs_color, font_size: style.text.font_size - * style.autocomplete.server_name_size_percent, + * style.autocomplete.inline_docs_size_percent, ..style.text.clone() }; - let label = Text::new(server_name, text_style) + let label = Text::new(text.clone(), text_style) .aligned() .constrained() .dynamically(move |constraint, _, _| { @@ -1021,7 +1001,7 @@ impl CompletionsMenu { .with_style( style .autocomplete - .server_name_container, + .inline_docs_container, ) .into_any(), ) @@ -1065,10 +1045,29 @@ impl CompletionsMenu { } }, ) - .with_width_from_item(widest_completion_ix) - .contained() - .with_style(container_style) - .into_any() + .with_width_from_item(widest_completion_ix); + + Flex::row() + .with_child(list) + .with_children({ + let completion = &self.completions[selected_item]; + let documentation = &completion.lsp_completion.documentation; + + if let Some(lsp::Documentation::MarkupContent(content)) = documentation { + Some( + Text::new(content.value.clone(), alongside_docs_text_style) + .constrained() + .with_width(alongside_docs_width) + .contained() + .with_style(alongside_docs_container_style), + ) + } else { + None + } + }) + .contained() + .with_style(outer_container_style) + .into_any() } pub async fn filter(&mut self, query: Option<&str>, executor: Arc) { @@ -3150,7 +3149,6 @@ impl Editor { }); let id = post_inc(&mut self.next_completion_id); - let project = self.project.clone(); let task = cx.spawn(|this, mut cx| { async move { let menu = if let Some(completions) = completions.await.log_err() { @@ -3169,7 +3167,6 @@ impl Editor { }) .collect(), buffer, - project, completions: completions.into(), matches: Vec::new().into(), selected_item: 0, diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 553cb321c399b4edd75c650b0b186cefd55517b5..69b5562c34c61b2282f83d429f349431f619b2f1 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,6 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, - link_go_to_definition::{InlayHighlight, RangeInEditor}, + link_go_to_definition::{DocumentRange, InlayRange}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -8,12 +8,12 @@ use futures::FutureExt; use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, + fonts::{HighlightStyle, Underline, Weight}, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, + AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry}; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; -use rich_text::{new_paragraph, render_code, render_markdown_mut, RichText}; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; @@ -50,18 +50,19 @@ pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewC pub struct InlayHover { pub excerpt: ExcerptId, - pub range: InlayHighlight, + pub triggered_from: InlayOffset, + pub range: InlayRange, pub tooltip: HoverBlock, } pub fn find_hovered_hint_part( label_parts: Vec, - hint_start: InlayOffset, + hint_range: Range, hovered_offset: InlayOffset, ) -> Option<(InlayHintLabelPart, Range)> { - if hovered_offset >= hint_start { - let mut hovered_character = (hovered_offset - hint_start).0; - let mut part_start = hint_start; + if hovered_offset >= hint_range.start && hovered_offset <= hint_range.end { + let mut hovered_character = (hovered_offset - hint_range.start).0; + let mut part_start = hint_range.start; for part in label_parts { let part_len = part.value.chars().count(); if hovered_character > part_len { @@ -87,8 +88,10 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie }; if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { - if let RangeInEditor::Inlay(range) = symbol_range { - if range == &inlay_hover.range { + if let DocumentRange::Inlay(range) = symbol_range { + if (range.highlight_start..range.highlight_end) + .contains(&inlay_hover.triggered_from) + { // Hover triggered from same location as last time. Don't show again. return; } @@ -96,6 +99,18 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie hide_hover(editor, cx); } + let snapshot = editor.snapshot(cx); + // Don't request again if the location is the same as the previous request + if let Some(triggered_from) = editor.hover_state.triggered_from { + if inlay_hover.triggered_from + == snapshot + .display_snapshot + .anchor_to_inlay_offset(triggered_from) + { + return; + } + } + let task = cx.spawn(|this, mut cx| { async move { cx.background() @@ -107,7 +122,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie let hover_popover = InfoPopover { project: project.clone(), - symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), + symbol_range: DocumentRange::Inlay(inlay_hover.range), blocks: vec![inlay_hover.tooltip], language: None, rendered_content: None, @@ -311,7 +326,7 @@ fn show_hover( Some(InfoPopover { project: project.clone(), - symbol_range: RangeInEditor::Text(range), + symbol_range: DocumentRange::Text(range), blocks: hover_result.contents, language: hover_result.language, rendered_content: None, @@ -346,43 +361,237 @@ fn show_hover( } fn render_blocks( + theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, language: Option<&Arc>, -) -> RichText { - let mut data = RichText { - text: Default::default(), - highlights: Default::default(), - region_ranges: Default::default(), - regions: Default::default(), - }; + style: &EditorStyle, +) -> RenderedInfo { + let mut text = String::new(); + let mut highlights = Vec::new(); + let mut region_ranges = Vec::new(); + let mut regions = Vec::new(); for block in blocks { match &block.kind { HoverBlockKind::PlainText => { - new_paragraph(&mut data.text, &mut Vec::new()); - data.text.push_str(&block.text); + new_paragraph(&mut text, &mut Vec::new()); + text.push_str(&block.text); } + HoverBlockKind::Markdown => { - render_markdown_mut(&block.text, language_registry, language, &mut data) + use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; + + let mut bold_depth = 0; + let mut italic_depth = 0; + let mut link_url = None; + let mut current_language = None; + let mut list_stack = Vec::new(); + + for event in Parser::new_ext(&block.text, Options::all()) { + let prev_len = text.len(); + match event { + Event::Text(t) => { + if let Some(language) = ¤t_language { + render_code( + &mut text, + &mut highlights, + t.as_ref(), + language, + style, + ); + } else { + text.push_str(t.as_ref()); + + let mut style = HighlightStyle::default(); + if bold_depth > 0 { + style.weight = Some(Weight::BOLD); + } + if italic_depth > 0 { + style.italic = Some(true); + } + if let Some(link_url) = link_url.clone() { + region_ranges.push(prev_len..text.len()); + regions.push(RenderedRegion { + link_url: Some(link_url), + code: false, + }); + style.underline = Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style != HighlightStyle::default() { + let mut new_highlight = true; + if let Some((last_range, last_style)) = highlights.last_mut() { + if last_range.end == prev_len && last_style == &style { + last_range.end = text.len(); + new_highlight = false; + } + } + if new_highlight { + highlights.push((prev_len..text.len(), style)); + } + } + } + } + + Event::Code(t) => { + text.push_str(t.as_ref()); + region_ranges.push(prev_len..text.len()); + if link_url.is_some() { + highlights.push(( + prev_len..text.len(), + HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + }, + )); + } + regions.push(RenderedRegion { + code: true, + link_url: link_url.clone(), + }); + } + + Event::Start(tag) => match tag { + Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), + + Tag::Heading(_, _, _) => { + new_paragraph(&mut text, &mut list_stack); + bold_depth += 1; + } + + Tag::CodeBlock(kind) => { + new_paragraph(&mut text, &mut list_stack); + current_language = if let CodeBlockKind::Fenced(language) = kind { + language_registry + .language_for_name(language.as_ref()) + .now_or_never() + .and_then(Result::ok) + } else { + language.cloned() + } + } + + Tag::Emphasis => italic_depth += 1, + + Tag::Strong => bold_depth += 1, + + Tag::Link(_, url, _) => link_url = Some(url.to_string()), + + Tag::List(number) => { + list_stack.push((number, false)); + } + + Tag::Item => { + let len = list_stack.len(); + if let Some((list_number, has_content)) = list_stack.last_mut() { + *has_content = false; + if !text.is_empty() && !text.ends_with('\n') { + text.push('\n'); + } + for _ in 0..len - 1 { + text.push_str(" "); + } + if let Some(number) = list_number { + text.push_str(&format!("{}. ", number)); + *number += 1; + *has_content = false; + } else { + text.push_str("- "); + } + } + } + + _ => {} + }, + + Event::End(tag) => match tag { + Tag::Heading(_, _, _) => bold_depth -= 1, + Tag::CodeBlock(_) => current_language = None, + Tag::Emphasis => italic_depth -= 1, + Tag::Strong => bold_depth -= 1, + Tag::Link(_, _, _) => link_url = None, + Tag::List(_) => drop(list_stack.pop()), + _ => {} + }, + + Event::HardBreak => text.push('\n'), + + Event::SoftBreak => text.push(' '), + + _ => {} + } + } } + HoverBlockKind::Code { language } => { if let Some(language) = language_registry .language_for_name(language) .now_or_never() .and_then(Result::ok) { - render_code(&mut data.text, &mut data.highlights, &block.text, &language); + render_code(&mut text, &mut highlights, &block.text, &language, style); } else { - data.text.push_str(&block.text); + text.push_str(&block.text); } } } } - data.text = data.text.trim().to_string(); + RenderedInfo { + theme_id, + text: text.trim().to_string(), + highlights, + region_ranges, + regions, + } +} - data +fn render_code( + text: &mut String, + highlights: &mut Vec<(Range, HighlightStyle)>, + content: &str, + language: &Arc, + style: &EditorStyle, +) { + let prev_len = text.len(); + text.push_str(content); + for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { + if let Some(style) = highlight_id.style(&style.syntax) { + highlights.push((prev_len + range.start..prev_len + range.end, style)); + } + } +} + +fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { + let mut is_subsequent_paragraph_of_list = false; + if let Some((_, has_content)) = list_stack.last_mut() { + if *has_content { + is_subsequent_paragraph_of_list = true; + } else { + *has_content = true; + return; + } + } + + if !text.is_empty() { + if !text.ends_with('\n') { + text.push('\n'); + } + text.push('\n'); + } + for _ in 0..list_stack.len().saturating_sub(1) { + text.push_str(" "); + } + if is_subsequent_paragraph_of_list { + text.push_str(" "); + } } #[derive(Default)] @@ -415,8 +624,8 @@ impl HoverState { self.info_popover .as_ref() .map(|info_popover| match &info_popover.symbol_range { - RangeInEditor::Text(range) => &range.start, - RangeInEditor::Inlay(range) => &range.inlay_position, + DocumentRange::Text(range) => &range.start, + DocumentRange::Inlay(range) => &range.inlay_position, }) })?; let point = anchor.to_display_point(&snapshot.display_snapshot); @@ -442,10 +651,25 @@ impl HoverState { #[derive(Debug, Clone)] pub struct InfoPopover { pub project: ModelHandle, - symbol_range: RangeInEditor, + symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - rendered_content: Option, + rendered_content: Option, +} + +#[derive(Debug, Clone)] +struct RenderedInfo { + theme_id: usize, + text: String, + highlights: Vec<(Range, HighlightStyle)>, + region_ranges: Vec>, + regions: Vec, +} + +#[derive(Debug, Clone)] +struct RenderedRegion { + code: bool, + link_url: Option, } impl InfoPopover { @@ -454,24 +678,63 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { + if let Some(rendered) = &self.rendered_content { + if rendered.theme_id != style.theme_id { + self.rendered_content = None; + } + } + let rendered_content = self.rendered_content.get_or_insert_with(|| { render_blocks( + style.theme_id, &self.blocks, self.project.read(cx).languages(), self.language.as_ref(), + style, ) }); - MouseEventHandler::new::(0, cx, move |_, cx| { + MouseEventHandler::new::(0, cx, |_, cx| { + let mut region_id = 0; + let view_id = cx.view_id(); + let code_span_background_color = style.document_highlight_read_background; + let regions = rendered_content.regions.clone(); Flex::column() .scrollable::(1, None, cx) - .with_child(rendered_content.element( - style.syntax.clone(), - style.text.clone(), - code_span_background_color, - cx, - )) + .with_child( + Text::new(rendered_content.text.clone(), style.text.clone()) + .with_highlights(rendered_content.highlights.clone()) + .with_custom_runs( + rendered_content.region_ranges.clone(), + move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::( + MouseButton::Left, + move |_, _, cx| cx.platform().open_url(&url), + ), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }, + ) + .with_soft_wrap(true), + ) .contained() .with_style(style.hover_popover.container) }) @@ -564,15 +827,13 @@ mod tests { inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, link_go_to_definition::update_inlay_link_and_hover_points, test::editor_lsp_test_context::EditorLspTestContext, - InlayId, }; use collections::BTreeSet; - use gpui::fonts::{HighlightStyle, Underline, Weight}; + use gpui::fonts::Weight; use indoc::indoc; use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; use lsp::LanguageServerId; use project::{HoverBlock, HoverBlockKind}; - use rich_text::Highlight; use smol::stream::StreamExt; use unindent::Unindent; use util::test::marked_text_ranges; @@ -783,7 +1044,7 @@ mod tests { .await; cx.condition(|editor, _| editor.hover_state.visible()).await; - cx.editor(|editor, _| { + cx.editor(|editor, cx| { let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; assert_eq!( blocks, @@ -793,7 +1054,8 @@ mod tests { }], ); - let rendered = render_blocks(&blocks, &Default::default(), None); + let style = editor.style(cx); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); assert_eq!( rendered.text, code_str.trim(), @@ -985,7 +1247,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(&blocks, &Default::default(), None); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges @@ -996,21 +1258,8 @@ mod tests { rendered.text, expected_text, "wrong text for input {blocks:?}" ); - - let rendered_highlights: Vec<_> = rendered - .highlights - .iter() - .filter_map(|(range, highlight)| { - let style = match highlight { - Highlight::Id(id) => id.style(&style.syntax)?, - Highlight::Highlight(style) => style.clone(), - }; - Some((range.clone(), style)) - }) - .collect(); - assert_eq!( - rendered_highlights, expected_highlights, + rendered.highlights, expected_highlights, "wrong highlights for input {blocks:?}" ); } @@ -1244,16 +1493,25 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); + let entire_inlay_start = snapshot.display_point_to_inlay_offset( + inlay_range.start.to_display_point(&snapshot), + Bias::Left, + ); + + let expected_new_type_label_start = InlayOffset(entire_inlay_start.0 + ": ".len()); assert_eq!( popover.symbol_range, - RangeInEditor::Inlay(InlayHighlight { - inlay: InlayId::Hint(0), + DocumentRange::Inlay(InlayRange { inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - range: ": ".len()..": ".len() + new_type_label.len(), + highlight_start: expected_new_type_label_start, + highlight_end: InlayOffset( + expected_new_type_label_start.0 + new_type_label.len() + ), }), "Popover range should match the new type label part" ); @@ -1301,17 +1559,23 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); + let entire_inlay_start = snapshot.display_point_to_inlay_offset( + inlay_range.start.to_display_point(&snapshot), + Bias::Left, + ); + let expected_struct_label_start = + InlayOffset(entire_inlay_start.0 + ": ".len() + new_type_label.len() + "<".len()); assert_eq!( popover.symbol_range, - RangeInEditor::Inlay(InlayHighlight { - inlay: InlayId::Hint(0), + DocumentRange::Inlay(InlayRange { inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - range: ": ".len() + new_type_label.len() + "<".len() - ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(), + highlight_start: expected_struct_label_start, + highlight_end: InlayOffset(expected_struct_label_start.0 + struct_label.len()), }), "Popover range should match the struct label part" ); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index e534ba4260e11b89acf2eb625701bfea921ff411..9f7530ec18da0f3af5016870d916b4edaaf17a0b 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -867,9 +867,11 @@ pub struct AutocompleteStyle { pub selected_item: ContainerStyle, pub hovered_item: ContainerStyle, pub match_highlight: HighlightStyle, - pub server_name_container: ContainerStyle, - pub server_name_color: Color, - pub server_name_size_percent: f32, + pub inline_docs_container: ContainerStyle, + pub inline_docs_color: Color, + pub inline_docs_size_percent: f32, + pub alongside_docs_width: f32, + pub alongside_docs_container: ContainerStyle, } #[derive(Clone, Copy, Default, Deserialize, JsonSchema)] diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index e55a73c365faba50c97001b417167f00e33a534f..37d6c4ea1ebe1715e692bfe00a0dc632d766ced6 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -206,9 +206,11 @@ export default function editor(): any { match_highlight: foreground(theme.middle, "accent", "active"), background: background(theme.middle, "active"), }, - server_name_container: { padding: { left: 40 } }, - server_name_color: text(theme.middle, "sans", "disabled", {}).color, - server_name_size_percent: 0.75, + inline_docs_container: { padding: { left: 40 } }, + inline_docs_color: text(theme.middle, "sans", "disabled", {}).color, + inline_docs_size_percent: 0.75, + alongside_docs_width: 400, + alongside_docs_container: { padding: autocomplete_item.padding } }, diagnostic_header: { background: background(theme.middle), From 1584dae9c211c65120825314e4a6b5fbda39cd1d Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 5 Sep 2023 22:23:16 -0400 Subject: [PATCH 021/180] Actually display the correct completion's doc --- crates/editor/src/editor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b1c5e35703c83b59fe55e3dca46490c4ea6e42be..ba2b50c1c1e090e47ea1f7b052b4772fa17a809d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1050,7 +1050,8 @@ impl CompletionsMenu { Flex::row() .with_child(list) .with_children({ - let completion = &self.completions[selected_item]; + let mat = &self.matches[selected_item]; + let completion = &self.completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; if let Some(lsp::Documentation::MarkupContent(content)) = documentation { From 370a3cafd0381b088ca1ac2122ecea968c7f9839 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 14 Sep 2023 15:24:46 -0400 Subject: [PATCH 022/180] Add markdown rendering to alongside completion docs --- crates/editor/src/editor.rs | 51 ++++--- crates/editor/src/markdown.rs | 246 ++++++++++++++++++++++++++++++++ styles/src/style_tree/editor.ts | 2 +- 3 files changed, 280 insertions(+), 19 deletions(-) create mode 100644 crates/editor/src/markdown.rs diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ba2b50c1c1e090e47ea1f7b052b4772fa17a809d..2035bd35f010107d7d7b614f1a996b8a79b29fc4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9,6 +9,7 @@ mod highlight_matching_bracket; mod hover_popover; pub mod items; mod link_go_to_definition; +mod markdown; mod mouse_context_menu; pub mod movement; pub mod multi_buffer; @@ -845,11 +846,12 @@ impl ContextMenu { fn render( &self, cursor_position: DisplayPoint, + editor: &Editor, style: EditorStyle, cx: &mut ViewContext, ) -> (DisplayPoint, AnyElement) { match self { - ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), + ContextMenu::Completions(menu) => (cursor_position, menu.render(editor, style, cx)), ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), } } @@ -899,7 +901,12 @@ impl CompletionsMenu { !self.matches.is_empty() } - fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { + fn render( + &self, + editor: &Editor, + style: EditorStyle, + cx: &mut ViewContext, + ) -> AnyElement { enum CompletionTag {} let widest_completion_ix = self @@ -923,18 +930,12 @@ impl CompletionsMenu { let matches = self.matches.clone(); let selected_item = self.selected_item; - let alongside_docs_text_style = TextStyle { - soft_wrap: true, - ..style.text.clone() - }; let alongside_docs_width = style.autocomplete.alongside_docs_width; let alongside_docs_container_style = style.autocomplete.alongside_docs_container; let outer_container_style = style.autocomplete.container; - let list = UniformList::new( - self.list.clone(), - matches.len(), - cx, + let list = UniformList::new(self.list.clone(), matches.len(), cx, { + let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; for (ix, mat) in matches[range].iter().enumerate() { @@ -1043,8 +1044,8 @@ impl CompletionsMenu { .into_any(), ); } - }, - ) + } + }) .with_width_from_item(widest_completion_ix); Flex::row() @@ -1055,12 +1056,26 @@ impl CompletionsMenu { let documentation = &completion.lsp_completion.documentation; if let Some(lsp::Documentation::MarkupContent(content)) = documentation { + let registry = editor + .project + .as_ref() + .unwrap() + .read(cx) + .languages() + .clone(); + let language = self.buffer.read(cx).language().map(Arc::clone); Some( - Text::new(content.value.clone(), alongside_docs_text_style) - .constrained() - .with_width(alongside_docs_width) - .contained() - .with_style(alongside_docs_container_style), + crate::markdown::render_markdown( + &content.value, + ®istry, + &language, + &style, + cx, + ) + .constrained() + .with_width(alongside_docs_width) + .contained() + .with_style(alongside_docs_container_style), ) } else { None @@ -3985,7 +4000,7 @@ impl Editor { ) -> Option<(DisplayPoint, AnyElement)> { self.context_menu .as_ref() - .map(|menu| menu.render(cursor_position, style, cx)) + .map(|menu| menu.render(cursor_position, self, style, cx)) } fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { diff --git a/crates/editor/src/markdown.rs b/crates/editor/src/markdown.rs new file mode 100644 index 0000000000000000000000000000000000000000..3ea8db34b361103744717e6e0dd06bc50e067f2d --- /dev/null +++ b/crates/editor/src/markdown.rs @@ -0,0 +1,246 @@ +use std::ops::Range; +use std::sync::Arc; + +use futures::FutureExt; +use gpui::{ + elements::Text, + fonts::{HighlightStyle, Underline, Weight}, + platform::{CursorStyle, MouseButton}, + CursorRegion, MouseRegion, ViewContext, +}; +use language::{Language, LanguageRegistry}; +use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; + +use crate::{Editor, EditorStyle}; + +#[derive(Debug, Clone)] +struct RenderedRegion { + code: bool, + link_url: Option, +} + +pub fn render_markdown( + markdown: &str, + language_registry: &Arc, + language: &Option>, + style: &EditorStyle, + cx: &mut ViewContext, +) -> Text { + let mut text = String::new(); + let mut highlights = Vec::new(); + let mut region_ranges = Vec::new(); + let mut regions = Vec::new(); + + let mut bold_depth = 0; + let mut italic_depth = 0; + let mut link_url = None; + let mut current_language = None; + let mut list_stack = Vec::new(); + + for event in Parser::new_ext(&markdown, Options::all()) { + let prev_len = text.len(); + match event { + Event::Text(t) => { + if let Some(language) = ¤t_language { + render_code(&mut text, &mut highlights, t.as_ref(), language, style); + } else { + text.push_str(t.as_ref()); + + let mut style = HighlightStyle::default(); + if bold_depth > 0 { + style.weight = Some(Weight::BOLD); + } + if italic_depth > 0 { + style.italic = Some(true); + } + if let Some(link_url) = link_url.clone() { + region_ranges.push(prev_len..text.len()); + regions.push(RenderedRegion { + link_url: Some(link_url), + code: false, + }); + style.underline = Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style != HighlightStyle::default() { + let mut new_highlight = true; + if let Some((last_range, last_style)) = highlights.last_mut() { + if last_range.end == prev_len && last_style == &style { + last_range.end = text.len(); + new_highlight = false; + } + } + if new_highlight { + highlights.push((prev_len..text.len(), style)); + } + } + } + } + + Event::Code(t) => { + text.push_str(t.as_ref()); + region_ranges.push(prev_len..text.len()); + if link_url.is_some() { + highlights.push(( + prev_len..text.len(), + HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + }, + )); + } + regions.push(RenderedRegion { + code: true, + link_url: link_url.clone(), + }); + } + + Event::Start(tag) => match tag { + Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), + + Tag::Heading(_, _, _) => { + new_paragraph(&mut text, &mut list_stack); + bold_depth += 1; + } + + Tag::CodeBlock(kind) => { + new_paragraph(&mut text, &mut list_stack); + current_language = if let CodeBlockKind::Fenced(language) = kind { + language_registry + .language_for_name(language.as_ref()) + .now_or_never() + .and_then(Result::ok) + } else { + language.clone() + } + } + + Tag::Emphasis => italic_depth += 1, + + Tag::Strong => bold_depth += 1, + + Tag::Link(_, url, _) => link_url = Some(url.to_string()), + + Tag::List(number) => { + list_stack.push((number, false)); + } + + Tag::Item => { + let len = list_stack.len(); + if let Some((list_number, has_content)) = list_stack.last_mut() { + *has_content = false; + if !text.is_empty() && !text.ends_with('\n') { + text.push('\n'); + } + for _ in 0..len - 1 { + text.push_str(" "); + } + if let Some(number) = list_number { + text.push_str(&format!("{}. ", number)); + *number += 1; + *has_content = false; + } else { + text.push_str("- "); + } + } + } + + _ => {} + }, + + Event::End(tag) => match tag { + Tag::Heading(_, _, _) => bold_depth -= 1, + Tag::CodeBlock(_) => current_language = None, + Tag::Emphasis => italic_depth -= 1, + Tag::Strong => bold_depth -= 1, + Tag::Link(_, _, _) => link_url = None, + Tag::List(_) => drop(list_stack.pop()), + _ => {} + }, + + Event::HardBreak => text.push('\n'), + + Event::SoftBreak => text.push(' '), + + _ => {} + } + } + + let code_span_background_color = style.document_highlight_read_background; + let view_id = cx.view_id(); + let mut region_id = 0; + Text::new(text, style.text.clone()) + .with_highlights(highlights) + .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) +} + +fn render_code( + text: &mut String, + highlights: &mut Vec<(Range, HighlightStyle)>, + content: &str, + language: &Arc, + style: &EditorStyle, +) { + let prev_len = text.len(); + text.push_str(content); + for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { + if let Some(style) = highlight_id.style(&style.syntax) { + highlights.push((prev_len + range.start..prev_len + range.end, style)); + } + } +} + +fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { + let mut is_subsequent_paragraph_of_list = false; + if let Some((_, has_content)) = list_stack.last_mut() { + if *has_content { + is_subsequent_paragraph_of_list = true; + } else { + *has_content = true; + return; + } + } + + if !text.is_empty() { + if !text.ends_with('\n') { + text.push('\n'); + } + text.push('\n'); + } + for _ in 0..list_stack.len().saturating_sub(1) { + text.push_str(" "); + } + if is_subsequent_paragraph_of_list { + text.push_str(" "); + } +} diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index 37d6c4ea1ebe1715e692bfe00a0dc632d766ced6..e7717583a848aec1d0993002fcb2dfcb43b36b87 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -209,7 +209,7 @@ export default function editor(): any { inline_docs_container: { padding: { left: 40 } }, inline_docs_color: text(theme.middle, "sans", "disabled", {}).color, inline_docs_size_percent: 0.75, - alongside_docs_width: 400, + alongside_docs_width: 700, alongside_docs_container: { padding: autocomplete_item.padding } }, diagnostic_header: { From e8be14e5d64d6b574ea626ff50e59e84875ebf39 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 15 Sep 2023 11:51:57 -0400 Subject: [PATCH 023/180] Merge info popover's and autocomplete docs' markdown rendering --- crates/editor/src/hover_popover.rs | 230 ++++------------------------- crates/editor/src/markdown.rs | 102 ++++++++----- 2 files changed, 87 insertions(+), 245 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 69b5562c34c61b2282f83d429f349431f619b2f1..16ecb2dc0173103f56344ee39ebbef8059ed31c1 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,7 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, link_go_to_definition::{DocumentRange, InlayRange}, + markdown::{self, RenderedRegion}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -8,7 +9,7 @@ use futures::FutureExt; use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, - fonts::{HighlightStyle, Underline, Weight}, + fonts::HighlightStyle, platform::{CursorStyle, MouseButton}, AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; @@ -364,7 +365,7 @@ fn render_blocks( theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, - language: Option<&Arc>, + language: &Option>, style: &EditorStyle, ) -> RenderedInfo { let mut text = String::new(); @@ -375,160 +376,20 @@ fn render_blocks( for block in blocks { match &block.kind { HoverBlockKind::PlainText => { - new_paragraph(&mut text, &mut Vec::new()); + markdown::new_paragraph(&mut text, &mut Vec::new()); text.push_str(&block.text); } - HoverBlockKind::Markdown => { - use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; - - let mut bold_depth = 0; - let mut italic_depth = 0; - let mut link_url = None; - let mut current_language = None; - let mut list_stack = Vec::new(); - - for event in Parser::new_ext(&block.text, Options::all()) { - let prev_len = text.len(); - match event { - Event::Text(t) => { - if let Some(language) = ¤t_language { - render_code( - &mut text, - &mut highlights, - t.as_ref(), - language, - style, - ); - } else { - text.push_str(t.as_ref()); - - let mut style = HighlightStyle::default(); - if bold_depth > 0 { - style.weight = Some(Weight::BOLD); - } - if italic_depth > 0 { - style.italic = Some(true); - } - if let Some(link_url) = link_url.clone() { - region_ranges.push(prev_len..text.len()); - regions.push(RenderedRegion { - link_url: Some(link_url), - code: false, - }); - style.underline = Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }); - } - - if style != HighlightStyle::default() { - let mut new_highlight = true; - if let Some((last_range, last_style)) = highlights.last_mut() { - if last_range.end == prev_len && last_style == &style { - last_range.end = text.len(); - new_highlight = false; - } - } - if new_highlight { - highlights.push((prev_len..text.len(), style)); - } - } - } - } - - Event::Code(t) => { - text.push_str(t.as_ref()); - region_ranges.push(prev_len..text.len()); - if link_url.is_some() { - highlights.push(( - prev_len..text.len(), - HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), - ..Default::default() - }, - )); - } - regions.push(RenderedRegion { - code: true, - link_url: link_url.clone(), - }); - } - - Event::Start(tag) => match tag { - Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), - - Tag::Heading(_, _, _) => { - new_paragraph(&mut text, &mut list_stack); - bold_depth += 1; - } - - Tag::CodeBlock(kind) => { - new_paragraph(&mut text, &mut list_stack); - current_language = if let CodeBlockKind::Fenced(language) = kind { - language_registry - .language_for_name(language.as_ref()) - .now_or_never() - .and_then(Result::ok) - } else { - language.cloned() - } - } - - Tag::Emphasis => italic_depth += 1, - - Tag::Strong => bold_depth += 1, - - Tag::Link(_, url, _) => link_url = Some(url.to_string()), - - Tag::List(number) => { - list_stack.push((number, false)); - } - - Tag::Item => { - let len = list_stack.len(); - if let Some((list_number, has_content)) = list_stack.last_mut() { - *has_content = false; - if !text.is_empty() && !text.ends_with('\n') { - text.push('\n'); - } - for _ in 0..len - 1 { - text.push_str(" "); - } - if let Some(number) = list_number { - text.push_str(&format!("{}. ", number)); - *number += 1; - *has_content = false; - } else { - text.push_str("- "); - } - } - } - - _ => {} - }, - - Event::End(tag) => match tag { - Tag::Heading(_, _, _) => bold_depth -= 1, - Tag::CodeBlock(_) => current_language = None, - Tag::Emphasis => italic_depth -= 1, - Tag::Strong => bold_depth -= 1, - Tag::Link(_, _, _) => link_url = None, - Tag::List(_) => drop(list_stack.pop()), - _ => {} - }, - - Event::HardBreak => text.push('\n'), - - Event::SoftBreak => text.push(' '), - - _ => {} - } - } - } + HoverBlockKind::Markdown => markdown::render_markdown_block( + &block.text, + language_registry, + language, + style, + &mut text, + &mut highlights, + &mut region_ranges, + &mut regions, + ), HoverBlockKind::Code { language } => { if let Some(language) = language_registry @@ -536,7 +397,13 @@ fn render_blocks( .now_or_never() .and_then(Result::ok) { - render_code(&mut text, &mut highlights, &block.text, &language, style); + markdown::render_code( + &mut text, + &mut highlights, + &block.text, + &language, + style, + ); } else { text.push_str(&block.text); } @@ -553,47 +420,6 @@ fn render_blocks( } } -fn render_code( - text: &mut String, - highlights: &mut Vec<(Range, HighlightStyle)>, - content: &str, - language: &Arc, - style: &EditorStyle, -) { - let prev_len = text.len(); - text.push_str(content); - for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { - if let Some(style) = highlight_id.style(&style.syntax) { - highlights.push((prev_len + range.start..prev_len + range.end, style)); - } - } -} - -fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { - let mut is_subsequent_paragraph_of_list = false; - if let Some((_, has_content)) = list_stack.last_mut() { - if *has_content { - is_subsequent_paragraph_of_list = true; - } else { - *has_content = true; - return; - } - } - - if !text.is_empty() { - if !text.ends_with('\n') { - text.push('\n'); - } - text.push('\n'); - } - for _ in 0..list_stack.len().saturating_sub(1) { - text.push_str(" "); - } - if is_subsequent_paragraph_of_list { - text.push_str(" "); - } -} - #[derive(Default)] pub struct HoverState { pub info_popover: Option, @@ -666,12 +492,6 @@ struct RenderedInfo { regions: Vec, } -#[derive(Debug, Clone)] -struct RenderedRegion { - code: bool, - link_url: Option, -} - impl InfoPopover { pub fn render( &mut self, @@ -689,7 +509,7 @@ impl InfoPopover { style.theme_id, &self.blocks, self.project.read(cx).languages(), - self.language.as_ref(), + &self.language, style, ) }); @@ -829,7 +649,7 @@ mod tests { test::editor_lsp_test_context::EditorLspTestContext, }; use collections::BTreeSet; - use gpui::fonts::Weight; + use gpui::fonts::{Underline, Weight}; use indoc::indoc; use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; use lsp::LanguageServerId; @@ -1055,7 +875,7 @@ mod tests { ); let style = editor.style(cx); - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); assert_eq!( rendered.text, code_str.trim(), @@ -1247,7 +1067,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges diff --git a/crates/editor/src/markdown.rs b/crates/editor/src/markdown.rs index 3ea8db34b361103744717e6e0dd06bc50e067f2d..df5041c0db3f1bd79678dd97aa36576587ea94fb 100644 --- a/crates/editor/src/markdown.rs +++ b/crates/editor/src/markdown.rs @@ -14,9 +14,9 @@ use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; use crate::{Editor, EditorStyle}; #[derive(Debug, Clone)] -struct RenderedRegion { - code: bool, - link_url: Option, +pub struct RenderedRegion { + pub code: bool, + pub link_url: Option, } pub fn render_markdown( @@ -31,6 +31,59 @@ pub fn render_markdown( let mut region_ranges = Vec::new(); let mut regions = Vec::new(); + render_markdown_block( + markdown, + language_registry, + language, + style, + &mut text, + &mut highlights, + &mut region_ranges, + &mut regions, + ); + + let code_span_background_color = style.document_highlight_read_background; + let view_id = cx.view_id(); + let mut region_id = 0; + Text::new(text, style.text.clone()) + .with_highlights(highlights) + .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) +} + +pub fn render_markdown_block( + markdown: &str, + language_registry: &Arc, + language: &Option>, + style: &EditorStyle, + text: &mut String, + highlights: &mut Vec<(Range, HighlightStyle)>, + region_ranges: &mut Vec>, + regions: &mut Vec, +) { let mut bold_depth = 0; let mut italic_depth = 0; let mut link_url = None; @@ -42,7 +95,7 @@ pub fn render_markdown( match event { Event::Text(t) => { if let Some(language) = ¤t_language { - render_code(&mut text, &mut highlights, t.as_ref(), language, style); + render_code(text, highlights, t.as_ref(), language, style); } else { text.push_str(t.as_ref()); @@ -102,15 +155,15 @@ pub fn render_markdown( } Event::Start(tag) => match tag { - Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), + Tag::Paragraph => new_paragraph(text, &mut list_stack), Tag::Heading(_, _, _) => { - new_paragraph(&mut text, &mut list_stack); + new_paragraph(text, &mut list_stack); bold_depth += 1; } Tag::CodeBlock(kind) => { - new_paragraph(&mut text, &mut list_stack); + new_paragraph(text, &mut list_stack); current_language = if let CodeBlockKind::Fenced(language) = kind { language_registry .language_for_name(language.as_ref()) @@ -171,40 +224,9 @@ pub fn render_markdown( _ => {} } } - - let code_span_background_color = style.document_highlight_read_background; - let view_id = cx.view_id(); - let mut region_id = 0; - Text::new(text, style.text.clone()) - .with_highlights(highlights) - .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) - }), - ); - } - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }) - .with_soft_wrap(true) } -fn render_code( +pub fn render_code( text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, content: &str, @@ -220,7 +242,7 @@ fn render_code( } } -fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { +pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { let mut is_subsequent_paragraph_of_list = false; if let Some((_, has_content)) = list_stack.last_mut() { if *has_content { From ca88717f0c3145b441f3efeb81d53d578b1a9c7e Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 15 Sep 2023 15:12:04 -0400 Subject: [PATCH 024/180] Make completion docs scrollable --- crates/editor/src/editor.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2035bd35f010107d7d7b614f1a996b8a79b29fc4..2f02ac59b066705c3929919b5ed717ed10c32f68 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1064,18 +1064,22 @@ impl CompletionsMenu { .languages() .clone(); let language = self.buffer.read(cx).language().map(Arc::clone); + + enum CompletionDocsMarkdown {} Some( - crate::markdown::render_markdown( - &content.value, - ®istry, - &language, - &style, - cx, - ) - .constrained() - .with_width(alongside_docs_width) - .contained() - .with_style(alongside_docs_container_style), + Flex::column() + .scrollable::(0, None, cx) + .with_child(crate::markdown::render_markdown( + &content.value, + ®istry, + &language, + &style, + cx, + )) + .constrained() + .with_width(alongside_docs_width) + .contained() + .with_style(alongside_docs_container_style), ) } else { None From 77ba25328cbeee3bfec5c3acef71fa8ff4394870 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 22 Sep 2023 15:10:48 -0400 Subject: [PATCH 025/180] Most of getting completion documentation resolved & cached MD parsing --- Cargo.lock | 2 +- crates/editor/Cargo.toml | 1 - crates/editor/src/editor.rs | 170 ++++++++++++++++---- crates/editor/src/hover_popover.rs | 6 +- crates/language/Cargo.toml | 1 + crates/language/src/buffer.rs | 2 + crates/language/src/language.rs | 1 + crates/{editor => language}/src/markdown.rs | 90 ++++++----- crates/language/src/proto.rs | 1 + crates/lsp/src/lsp.rs | 14 +- crates/project/src/lsp_command.rs | 1 + crates/zed/src/languages.rs | 4 +- 12 files changed, 217 insertions(+), 76 deletions(-) rename crates/{editor => language}/src/markdown.rs (79%) diff --git a/Cargo.lock b/Cargo.lock index c971846a5dd4d8851b12548a5951da9c4380141c..147760ab14919f6671c00a6b457922464d0ecc58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2404,7 +2404,6 @@ dependencies = [ "parking_lot 0.11.2", "postage", "project", - "pulldown-cmark", "rand 0.8.5", "rich_text", "rpc", @@ -3990,6 +3989,7 @@ dependencies = [ "lsp", "parking_lot 0.11.2", "postage", + "pulldown-cmark", "rand 0.8.5", "regex", "rpc", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 2c3d6227a9abe0828e410d4ca5a1c2c82b8ef701..d03e1c110649013d6acf34e64ba69d6231003878 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -57,7 +57,6 @@ log.workspace = true ordered-float.workspace = true parking_lot.workspace = true postage.workspace = true -pulldown-cmark = { version = "0.9.2", default-features = false } rand.workspace = true schemars.workspace = true serde.workspace = true diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2f02ac59b066705c3929919b5ed717ed10c32f68..c0d2b4ee0b576789180097ac0512bed14a1a143b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9,7 +9,6 @@ mod highlight_matching_bracket; mod hover_popover; pub mod items; mod link_go_to_definition; -mod markdown; mod mouse_context_menu; pub mod movement; pub mod multi_buffer; @@ -78,6 +77,7 @@ pub use multi_buffer::{ ToPoint, }; use ordered_float::OrderedFloat; +use parking_lot::RwLock; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use rand::{seq::SliceRandom, thread_rng}; use rpc::proto::PeerId; @@ -788,10 +788,14 @@ enum ContextMenu { } impl ContextMenu { - fn select_first(&mut self, cx: &mut ViewContext) -> bool { + fn select_first( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_first(cx), + ContextMenu::Completions(menu) => menu.select_first(project, cx), ContextMenu::CodeActions(menu) => menu.select_first(cx), } true @@ -800,10 +804,14 @@ impl ContextMenu { } } - fn select_prev(&mut self, cx: &mut ViewContext) -> bool { + fn select_prev( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_prev(cx), + ContextMenu::Completions(menu) => menu.select_prev(project, cx), ContextMenu::CodeActions(menu) => menu.select_prev(cx), } true @@ -812,10 +820,14 @@ impl ContextMenu { } } - fn select_next(&mut self, cx: &mut ViewContext) -> bool { + fn select_next( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_next(cx), + ContextMenu::Completions(menu) => menu.select_next(project, cx), ContextMenu::CodeActions(menu) => menu.select_next(cx), } true @@ -824,10 +836,14 @@ impl ContextMenu { } } - fn select_last(&mut self, cx: &mut ViewContext) -> bool { + fn select_last( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_last(cx), + ContextMenu::Completions(menu) => menu.select_last(project, cx), ContextMenu::CodeActions(menu) => menu.select_last(cx), } true @@ -861,7 +877,7 @@ struct CompletionsMenu { id: CompletionId, initial_position: Anchor, buffer: ModelHandle, - completions: Arc<[Completion]>, + completions: Arc>>, match_candidates: Vec, matches: Arc<[StringMatch]>, selected_item: usize, @@ -869,34 +885,115 @@ struct CompletionsMenu { } impl CompletionsMenu { - fn select_first(&mut self, cx: &mut ViewContext) { + fn select_first( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } - fn select_prev(&mut self, cx: &mut ViewContext) { + fn select_prev( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { if self.selected_item > 0 { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } - fn select_next(&mut self, cx: &mut ViewContext) { + fn select_next( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { if self.selected_item + 1 < self.matches.len() { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } - fn select_last(&mut self, cx: &mut ViewContext) { + fn select_last( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } + fn attempt_resolve_selected_completion( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { + println!("attempt_resolve_selected_completion"); + let index = self.matches[dbg!(self.selected_item)].candidate_id; + dbg!(index); + let Some(project) = project else { + println!("no project"); + return; + }; + + let completions = self.completions.clone(); + let completions_guard = completions.read(); + let completion = &completions_guard[index]; + if completion.lsp_completion.documentation.is_some() { + println!("has existing documentation"); + return; + } + + let server_id = completion.server_id; + let completion = completion.lsp_completion.clone(); + drop(completions_guard); + + let Some(server) = project.read(cx).language_server_for_id(server_id) else { + println!("no server"); + return; + }; + + let can_resolve = server + .capabilities() + .completion_provider + .as_ref() + .and_then(|options| options.resolve_provider) + .unwrap_or(false); + if !dbg!(can_resolve) { + return; + } + + cx.spawn(|this, mut cx| async move { + println!("in spawn"); + let request = server.request::(completion); + let Some(completion_item) = request.await.log_err() else { + println!("errored"); + return; + }; + + if completion_item.documentation.is_some() { + println!("got new documentation"); + let mut completions = completions.write(); + completions[index].lsp_completion.documentation = completion_item.documentation; + println!("notifying"); + _ = this.update(&mut cx, |_, cx| cx.notify()); + } else { + println!("did not get anything"); + } + }) + .detach(); + } + fn visible(&self) -> bool { !self.matches.is_empty() } @@ -914,7 +1011,8 @@ impl CompletionsMenu { .iter() .enumerate() .max_by_key(|(_, mat)| { - let completion = &self.completions[mat.candidate_id]; + let completions = self.completions.read(); + let completion = &completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; let mut len = completion.label.text.chars().count(); @@ -938,6 +1036,7 @@ impl CompletionsMenu { let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; + let completions = completions.read(); for (ix, mat) in matches[range].iter().enumerate() { let completion = &completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; @@ -1052,7 +1151,8 @@ impl CompletionsMenu { .with_child(list) .with_children({ let mat = &self.matches[selected_item]; - let completion = &self.completions[mat.candidate_id]; + let completions = self.completions.read(); + let completion = &completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; if let Some(lsp::Documentation::MarkupContent(content)) = documentation { @@ -1069,13 +1169,12 @@ impl CompletionsMenu { Some( Flex::column() .scrollable::(0, None, cx) - .with_child(crate::markdown::render_markdown( - &content.value, - ®istry, - &language, - &style, - cx, - )) + // .with_child(language::markdown::render_markdown( + // &content.value, + // ®istry, + // &language, + // &style, + // )) .constrained() .with_width(alongside_docs_width) .contained() @@ -1130,17 +1229,20 @@ impl CompletionsMenu { } } + let completions = self.completions.read(); matches.sort_unstable_by_key(|mat| { - let completion = &self.completions[mat.candidate_id]; + let completion = &completions[mat.candidate_id]; ( completion.lsp_completion.sort_text.as_ref(), Reverse(OrderedFloat(mat.score)), completion.sort_key(), ) }); + drop(completions); for mat in &mut matches { - let filter_start = self.completions[mat.candidate_id].label.filter_range.start; + let completions = self.completions.read(); + let filter_start = completions[mat.candidate_id].label.filter_range.start; for position in &mut mat.positions { *position += filter_start; } @@ -3187,7 +3289,7 @@ impl Editor { }) .collect(), buffer, - completions: completions.into(), + completions: Arc::new(RwLock::new(completions.into())), matches: Vec::new().into(), selected_item: 0, list: Default::default(), @@ -3196,6 +3298,9 @@ impl Editor { if menu.matches.is_empty() { None } else { + _ = this.update(&mut cx, |editor, cx| { + menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); + }); Some(menu) } } else { @@ -3252,7 +3357,8 @@ impl Editor { .matches .get(action.item_ix.unwrap_or(completions_menu.selected_item))?; let buffer_handle = completions_menu.buffer; - let completion = completions_menu.completions.get(mat.candidate_id)?; + let completions = completions_menu.completions.read(); + let completion = completions.get(mat.candidate_id)?; let snippet; let text; @@ -5372,7 +5478,7 @@ impl Editor { if self .context_menu .as_mut() - .map(|menu| menu.select_last(cx)) + .map(|menu| menu.select_last(self.project.as_ref(), cx)) .unwrap_or(false) { return; @@ -5416,25 +5522,25 @@ impl Editor { pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_first(cx); + context_menu.select_first(self.project.as_ref(), cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_prev(cx); + context_menu.select_prev(self.project.as_ref(), cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_next(cx); + context_menu.select_next(self.project.as_ref(), cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_last(cx); + context_menu.select_last(self.project.as_ref(), cx); } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 16ecb2dc0173103f56344ee39ebbef8059ed31c1..ea6eac3a66072b548d67949f0d223da299152cfb 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,7 +1,6 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, link_go_to_definition::{DocumentRange, InlayRange}, - markdown::{self, RenderedRegion}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -13,7 +12,10 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; -use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry}; +use language::{ + markdown::{self, RenderedRegion}, + Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, +}; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 4771fc70833660ccb018d9ffd45362f018901e4a..d5d5bdd1af826f2031289cdfe4756ce873ba2445 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -46,6 +46,7 @@ lazy_static.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true +pulldown-cmark = { version = "0.9.2", default-features = false } regex.workspace = true schemars.workspace = true serde.workspace = true diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 207c41e7cdf21a8ec4d61232e071296184b52a5f..10585633ae09928001666f68c83df1d89485d397 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,6 +1,7 @@ pub use crate::{ diagnostic_set::DiagnosticSet, highlight_map::{HighlightId, HighlightMap}, + markdown::RenderedMarkdown, proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT, }; use crate::{ @@ -148,6 +149,7 @@ pub struct Completion { pub old_range: Range, pub new_text: String, pub label: CodeLabel, + pub alongside_documentation: Option, pub server_id: LanguageServerId, pub lsp_completion: lsp::CompletionItem, } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 7d113a88af1de8e7b0b243bf54c15565de544730..12f76f1df37e06b7045cddd2e645513e74ffff9e 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -2,6 +2,7 @@ mod buffer; mod diagnostic_set; mod highlight_map; pub mod language_settings; +pub mod markdown; mod outline; pub mod proto; mod syntax_map; diff --git a/crates/editor/src/markdown.rs b/crates/language/src/markdown.rs similarity index 79% rename from crates/editor/src/markdown.rs rename to crates/language/src/markdown.rs index df5041c0db3f1bd79678dd97aa36576587ea94fb..e033820a215f105657a299a538793b2d20740e4f 100644 --- a/crates/editor/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -1,6 +1,7 @@ use std::ops::Range; use std::sync::Arc; +use crate::{Language, LanguageRegistry}; use futures::FutureExt; use gpui::{ elements::Text, @@ -8,10 +9,50 @@ use gpui::{ platform::{CursorStyle, MouseButton}, CursorRegion, MouseRegion, ViewContext, }; -use language::{Language, LanguageRegistry}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; -use crate::{Editor, EditorStyle}; +#[derive(Debug, Clone)] +pub struct RenderedMarkdown { + text: String, + highlights: Vec<(Range, HighlightStyle)>, + region_ranges: Vec>, + regions: Vec, +} + +// impl RenderedMarkdown { +// pub fn render(&self, style: &theme::Editor, cx: &mut ViewContext) -> Text { +// let code_span_background_color = style.document_highlight_read_background; +// let view_id = cx.view_id(); +// let mut region_id = 0; +// Text::new(text, style.text.clone()) +// .with_highlights(highlights) +// .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { +// region_id += 1; +// let region = regions[ix].clone(); +// if let Some(url) = region.link_url { +// scene.push_cursor_region(CursorRegion { +// bounds, +// style: CursorStyle::PointingHand, +// }); +// scene.push_mouse_region( +// MouseRegion::new::(view_id, region_id, bounds) +// .on_click::(MouseButton::Left, move |_, _, cx| { +// cx.platform().open_url(&url) +// }), +// ); +// } +// if region.code { +// scene.push_quad(gpui::Quad { +// bounds, +// background: Some(code_span_background_color), +// border: Default::default(), +// corner_radii: (2.0).into(), +// }); +// } +// }) +// .with_soft_wrap(true) +// } +// } #[derive(Debug, Clone)] pub struct RenderedRegion { @@ -23,9 +64,8 @@ pub fn render_markdown( markdown: &str, language_registry: &Arc, language: &Option>, - style: &EditorStyle, - cx: &mut ViewContext, -) -> Text { + style: &theme::Editor, +) -> RenderedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); @@ -42,43 +82,19 @@ pub fn render_markdown( &mut regions, ); - let code_span_background_color = style.document_highlight_read_background; - let view_id = cx.view_id(); - let mut region_id = 0; - Text::new(text, style.text.clone()) - .with_highlights(highlights) - .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) - }), - ); - } - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }) - .with_soft_wrap(true) + RenderedMarkdown { + text, + highlights, + region_ranges, + regions, + } } pub fn render_markdown_block( markdown: &str, language_registry: &Arc, language: &Option>, - style: &EditorStyle, + style: &theme::Editor, text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, region_ranges: &mut Vec>, @@ -231,7 +247,7 @@ pub fn render_code( highlights: &mut Vec<(Range, HighlightStyle)>, content: &str, language: &Arc, - style: &EditorStyle, + style: &theme::Editor, ) { let prev_len = text.len(); text.push_str(content); diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index c4abe39d4782aafbe90594e3a0bc5de70787fa03..49b332b4fb6d8626bcb99ee3b60bfbc021882cc3 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -482,6 +482,7 @@ pub async fn deserialize_completion( lsp_completion.filter_text.as_deref(), ) }), + alongside_documentation: None, server_id: LanguageServerId(completion.server_id as usize), lsp_completion, }) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 33581721ae8fff8bc32f085ad3b7359209ee6cf2..b4099e2f6ea629e13343f0a022bf45cfd0712083 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -466,7 +466,10 @@ impl LanguageServer { completion_item: Some(CompletionItemCapability { snippet_support: Some(true), resolve_support: Some(CompletionItemCapabilityResolveSupport { - properties: vec!["additionalTextEdits".to_string()], + properties: vec![ + "documentation".to_string(), + "additionalTextEdits".to_string(), + ], }), ..Default::default() }), @@ -748,6 +751,15 @@ impl LanguageServer { ) } + // some child of string literal (be it "" or ``) which is the child of an attribute + + // + // + // + // + // const classes = "awesome "; + // + fn request_internal( next_id: &AtomicUsize, response_handlers: &Mutex>>, diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 8beaea5031c1e663909a1fd5a627c90b1c84a367..000fd3928c2c7bc6f18477e12c817d76fc74ddc0 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1462,6 +1462,7 @@ impl LspCommand for GetCompletions { lsp_completion.filter_text.as_deref(), ) }), + alongside_documentation: None, server_id, lsp_completion, } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 04e5292a7df6cf4fa9c58d1999aed246f963c352..3b65255a3de2cd3fcdb6322405302ad78f647214 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -128,8 +128,8 @@ pub fn init( "tsx", tree_sitter_typescript::language_tsx(), vec![ - Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), - Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), + // Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + // Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), ], ); From fcaf48eb4965349bedc7daab6561060686c8c757 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 22 Sep 2023 17:03:40 -0400 Subject: [PATCH 026/180] Use completion item default `data` when provided --- crates/project/src/lsp_command.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 000fd3928c2c7bc6f18477e12c817d76fc74ddc0..16ebb7467bc774fdea0461787d41321d3b968ea6 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1358,7 +1358,7 @@ impl LspCommand for GetCompletions { } } } else { - Default::default() + Vec::new() }; let completions = buffer.read_with(&cx, |buffer, _| { @@ -1370,6 +1370,14 @@ impl LspCommand for GetCompletions { completions .into_iter() .filter_map(move |mut lsp_completion| { + if let Some(response_list) = &response_list { + if let Some(item_defaults) = &response_list.item_defaults { + if let Some(data) = &item_defaults.data { + lsp_completion.data = Some(data.clone()); + } + } + } + let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() { // If the language server provides a range to overwrite, then // check that the range is valid. From fe62423344fedb81c17a5f6aa81b56805fc8d2bc Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 28 Sep 2023 14:16:30 -0400 Subject: [PATCH 027/180] Asynchronously request completion documentation if not present --- crates/editor/src/editor.rs | 78 +++++++++++++++++++++++---------- crates/language/src/markdown.rs | 50 +++------------------ 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c0d2b4ee0b576789180097ac0512bed14a1a143b..d35a9dd30e51b9294c5020f90e6ab2f0c8cde6af 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -48,9 +48,9 @@ use gpui::{ impl_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton}, - serde_json, AnyElement, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, - Entity, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, + serde_json, AnyElement, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, + CursorRegion, Element, Entity, ModelHandle, MouseRegion, Subscription, Task, View, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -119,6 +119,46 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); +pub fn render_rendered_markdown( + md: &language::RenderedMarkdown, + style: &EditorStyle, + cx: &mut ViewContext, +) -> Text { + enum RenderedRenderedMarkdown {} + + let md = md.clone(); + let code_span_background_color = style.document_highlight_read_background; + let view_id = cx.view_id(); + let mut region_id = 0; + Text::new(md.text, style.text.clone()) + .with_highlights(md.highlights) + .with_custom_runs(md.region_ranges, move |ix, bounds, scene, _| { + region_id += 1; + let region = md.regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) +} + #[derive(Clone, Deserialize, PartialEq, Default)] pub struct SelectNext { #[serde(default)] @@ -938,11 +978,8 @@ impl CompletionsMenu { project: Option<&ModelHandle>, cx: &mut ViewContext, ) { - println!("attempt_resolve_selected_completion"); - let index = self.matches[dbg!(self.selected_item)].candidate_id; - dbg!(index); + let index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { - println!("no project"); return; }; @@ -950,7 +987,6 @@ impl CompletionsMenu { let completions_guard = completions.read(); let completion = &completions_guard[index]; if completion.lsp_completion.documentation.is_some() { - println!("has existing documentation"); return; } @@ -959,7 +995,6 @@ impl CompletionsMenu { drop(completions_guard); let Some(server) = project.read(cx).language_server_for_id(server_id) else { - println!("no server"); return; }; @@ -969,26 +1004,21 @@ impl CompletionsMenu { .as_ref() .and_then(|options| options.resolve_provider) .unwrap_or(false); - if !dbg!(can_resolve) { + if !can_resolve { return; } cx.spawn(|this, mut cx| async move { - println!("in spawn"); let request = server.request::(completion); let Some(completion_item) = request.await.log_err() else { - println!("errored"); return; }; if completion_item.documentation.is_some() { - println!("got new documentation"); let mut completions = completions.write(); completions[index].lsp_completion.documentation = completion_item.documentation; - println!("notifying"); + drop(completions); _ = this.update(&mut cx, |_, cx| cx.notify()); - } else { - println!("did not get anything"); } }) .detach(); @@ -1169,12 +1199,16 @@ impl CompletionsMenu { Some( Flex::column() .scrollable::(0, None, cx) - // .with_child(language::markdown::render_markdown( - // &content.value, - // ®istry, - // &language, - // &style, - // )) + .with_child(render_rendered_markdown( + &language::markdown::render_markdown( + &content.value, + ®istry, + &language, + &style.theme, + ), + &style, + cx, + )) .constrained() .with_width(alongside_docs_width) .contained() diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index e033820a215f105657a299a538793b2d20740e4f..4ccd2955b626b621271e20bcdbaf3d792780d634 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -3,57 +3,17 @@ use std::sync::Arc; use crate::{Language, LanguageRegistry}; use futures::FutureExt; -use gpui::{ - elements::Text, - fonts::{HighlightStyle, Underline, Weight}, - platform::{CursorStyle, MouseButton}, - CursorRegion, MouseRegion, ViewContext, -}; +use gpui::fonts::{HighlightStyle, Underline, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] pub struct RenderedMarkdown { - text: String, - highlights: Vec<(Range, HighlightStyle)>, - region_ranges: Vec>, - regions: Vec, + pub text: String, + pub highlights: Vec<(Range, HighlightStyle)>, + pub region_ranges: Vec>, + pub regions: Vec, } -// impl RenderedMarkdown { -// pub fn render(&self, style: &theme::Editor, cx: &mut ViewContext) -> Text { -// let code_span_background_color = style.document_highlight_read_background; -// let view_id = cx.view_id(); -// let mut region_id = 0; -// Text::new(text, style.text.clone()) -// .with_highlights(highlights) -// .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { -// region_id += 1; -// let region = regions[ix].clone(); -// if let Some(url) = region.link_url { -// scene.push_cursor_region(CursorRegion { -// bounds, -// style: CursorStyle::PointingHand, -// }); -// scene.push_mouse_region( -// MouseRegion::new::(view_id, region_id, bounds) -// .on_click::(MouseButton::Left, move |_, _, cx| { -// cx.platform().open_url(&url) -// }), -// ); -// } -// if region.code { -// scene.push_quad(gpui::Quad { -// bounds, -// background: Some(code_span_background_color), -// border: Default::default(), -// corner_radii: (2.0).into(), -// }); -// } -// }) -// .with_soft_wrap(true) -// } -// } - #[derive(Debug, Clone)] pub struct RenderedRegion { pub code: bool, From b8876f2b17307917bf24b20ffcdd090f9dc5126e Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 3 Oct 2023 10:58:08 -0400 Subject: [PATCH 028/180] Preparse documentation markdown when resolving completion --- crates/editor/src/editor.rs | 133 ++++++++++++++++------------- crates/editor/src/hover_popover.rs | 26 +++--- crates/language/src/buffer.rs | 44 +++++++++- crates/language/src/markdown.rs | 30 +++---- crates/language/src/proto.rs | 2 +- crates/project/src/lsp_command.rs | 2 +- 6 files changed, 145 insertions(+), 92 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d35a9dd30e51b9294c5020f90e6ab2f0c8cde6af..0f117cde1b83312f53a756afe64506c88135cc71 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -119,12 +119,12 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); -pub fn render_rendered_markdown( - md: &language::RenderedMarkdown, +pub fn render_parsed_markdown( + md: &language::ParsedMarkdown, style: &EditorStyle, cx: &mut ViewContext, ) -> Text { - enum RenderedRenderedMarkdown {} + enum RenderedMarkdown {} let md = md.clone(); let code_span_background_color = style.document_highlight_read_background; @@ -141,7 +141,7 @@ pub fn render_rendered_markdown( style: CursorStyle::PointingHand, }); scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) + MouseRegion::new::(view_id, region_id, bounds) .on_click::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) }), @@ -831,11 +831,12 @@ impl ContextMenu { fn select_first( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_first(project, cx), + ContextMenu::Completions(menu) => menu.select_first(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_first(cx), } true @@ -847,11 +848,12 @@ impl ContextMenu { fn select_prev( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_prev(project, cx), + ContextMenu::Completions(menu) => menu.select_prev(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_prev(cx), } true @@ -863,11 +865,12 @@ impl ContextMenu { fn select_next( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_next(project, cx), + ContextMenu::Completions(menu) => menu.select_next(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_next(cx), } true @@ -879,11 +882,12 @@ impl ContextMenu { fn select_last( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_last(project, cx), + ContextMenu::Completions(menu) => menu.select_last(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_last(cx), } true @@ -928,60 +932,66 @@ impl CompletionsMenu { fn select_first( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn select_prev( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item > 0 { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn select_next( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item + 1 < self.matches.len() { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn select_last( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn attempt_resolve_selected_completion( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { let index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { return; }; + let language_registry = project.read(cx).languages().clone(); let completions = self.completions.clone(); let completions_guard = completions.read(); @@ -1008,16 +1018,27 @@ impl CompletionsMenu { return; } + // TODO: Do on background cx.spawn(|this, mut cx| async move { let request = server.request::(completion); let Some(completion_item) = request.await.log_err() else { return; }; - if completion_item.documentation.is_some() { + if let Some(lsp_documentation) = completion_item.documentation { + let documentation = language::prepare_completion_documentation( + &lsp_documentation, + &language_registry, + None, // TODO: Try to reasonably work out which language the completion is for + &style, + ); + let mut completions = completions.write(); - completions[index].lsp_completion.documentation = completion_item.documentation; + let completion = &mut completions[index]; + completion.documentation = documentation; + completion.lsp_completion.documentation = Some(lsp_documentation); drop(completions); + _ = this.update(&mut cx, |_, cx| cx.notify()); } }) @@ -1069,7 +1090,7 @@ impl CompletionsMenu { let completions = completions.read(); for (ix, mat) in matches[range].iter().enumerate() { let completion = &completions[mat.candidate_id]; - let documentation = &completion.lsp_completion.documentation; + let documentation = &completion.documentation; let item_ix = start_ix + ix; items.push( @@ -1100,7 +1121,9 @@ impl CompletionsMenu { ), ); - if let Some(lsp::Documentation::String(text)) = documentation { + if let Some(language::Documentation::SingleLine(text)) = + documentation + { Flex::row() .with_child(completion_label) .with_children((|| { @@ -1183,39 +1206,18 @@ impl CompletionsMenu { let mat = &self.matches[selected_item]; let completions = self.completions.read(); let completion = &completions[mat.candidate_id]; - let documentation = &completion.lsp_completion.documentation; + let documentation = &completion.documentation; - if let Some(lsp::Documentation::MarkupContent(content)) = documentation { - let registry = editor - .project - .as_ref() - .unwrap() - .read(cx) - .languages() - .clone(); - let language = self.buffer.read(cx).language().map(Arc::clone); - - enum CompletionDocsMarkdown {} - Some( - Flex::column() - .scrollable::(0, None, cx) - .with_child(render_rendered_markdown( - &language::markdown::render_markdown( - &content.value, - ®istry, - &language, - &style.theme, - ), - &style, - cx, - )) - .constrained() - .with_width(alongside_docs_width) - .contained() - .with_style(alongside_docs_container_style), - ) - } else { - None + match documentation { + Some(language::Documentation::MultiLinePlainText(text)) => { + Some(Text::new(text.clone(), style.text.clone())) + } + + Some(language::Documentation::MultiLineMarkdown(parsed)) => { + Some(render_parsed_markdown(parsed, &style, cx)) + } + + _ => None, } }) .contained() @@ -3333,7 +3335,11 @@ impl Editor { None } else { _ = this.update(&mut cx, |editor, cx| { - menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); + menu.attempt_resolve_selected_completion( + editor.project.as_ref(), + editor.style(cx).theme, + cx, + ); }); Some(menu) } @@ -5509,13 +5515,16 @@ impl Editor { return; } - if self - .context_menu - .as_mut() - .map(|menu| menu.select_last(self.project.as_ref(), cx)) - .unwrap_or(false) - { - return; + if self.context_menu.is_some() { + let style = self.style(cx).theme; + if self + .context_menu + .as_mut() + .map(|menu| menu.select_last(self.project.as_ref(), style, cx)) + .unwrap_or(false) + { + return; + } } if matches!(self.mode, EditorMode::SingleLine) { @@ -5555,26 +5564,30 @@ impl Editor { } pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_first(self.project.as_ref(), cx); + context_menu.select_first(self.project.as_ref(), style, cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_prev(self.project.as_ref(), cx); + context_menu.select_prev(self.project.as_ref(), style, cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_next(self.project.as_ref(), cx); + context_menu.select_next(self.project.as_ref(), style, cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_last(self.project.as_ref(), cx); + context_menu.select_last(self.project.as_ref(), style, cx); } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index ea6eac3a66072b548d67949f0d223da299152cfb..9341fa2da352d9848eda219eadb8922039439f50 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -13,7 +13,7 @@ use gpui::{ AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; use language::{ - markdown::{self, RenderedRegion}, + markdown::{self, ParsedRegion}, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, }; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; @@ -367,9 +367,9 @@ fn render_blocks( theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, - language: &Option>, + language: Option>, style: &EditorStyle, -) -> RenderedInfo { +) -> ParsedInfo { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); @@ -382,10 +382,10 @@ fn render_blocks( text.push_str(&block.text); } - HoverBlockKind::Markdown => markdown::render_markdown_block( + HoverBlockKind::Markdown => markdown::parse_markdown_block( &block.text, language_registry, - language, + language.clone(), style, &mut text, &mut highlights, @@ -399,7 +399,7 @@ fn render_blocks( .now_or_never() .and_then(Result::ok) { - markdown::render_code( + markdown::highlight_code( &mut text, &mut highlights, &block.text, @@ -413,7 +413,7 @@ fn render_blocks( } } - RenderedInfo { + ParsedInfo { theme_id, text: text.trim().to_string(), highlights, @@ -482,16 +482,16 @@ pub struct InfoPopover { symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - rendered_content: Option, + rendered_content: Option, } #[derive(Debug, Clone)] -struct RenderedInfo { +struct ParsedInfo { theme_id: usize, text: String, highlights: Vec<(Range, HighlightStyle)>, region_ranges: Vec>, - regions: Vec, + regions: Vec, } impl InfoPopover { @@ -511,7 +511,7 @@ impl InfoPopover { style.theme_id, &self.blocks, self.project.read(cx).languages(), - &self.language, + self.language.clone(), style, ) }); @@ -877,7 +877,7 @@ mod tests { ); let style = editor.style(cx); - let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); assert_eq!( rendered.text, code_str.trim(), @@ -1069,7 +1069,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 10585633ae09928001666f68c83df1d89485d397..344b470aa99740410bce3a3d9e4f2e575b6f2258 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,12 +1,13 @@ pub use crate::{ diagnostic_set::DiagnosticSet, highlight_map::{HighlightId, HighlightMap}, - markdown::RenderedMarkdown, + markdown::ParsedMarkdown, proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT, }; use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, language_settings::{language_settings, LanguageSettings}, + markdown, outline::OutlineItem, syntax_map::{ SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, @@ -144,12 +145,51 @@ pub struct Diagnostic { pub is_unnecessary: bool, } +pub fn prepare_completion_documentation( + documentation: &lsp::Documentation, + language_registry: &Arc, + language: Option>, + style: &theme::Editor, +) -> Option { + match documentation { + lsp::Documentation::String(text) => { + if text.lines().count() <= 1 { + Some(Documentation::SingleLine(text.clone())) + } else { + Some(Documentation::MultiLinePlainText(text.clone())) + } + } + + lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind { + lsp::MarkupKind::PlainText => { + if value.lines().count() <= 1 { + Some(Documentation::SingleLine(value.clone())) + } else { + Some(Documentation::MultiLinePlainText(value.clone())) + } + } + + lsp::MarkupKind::Markdown => { + let parsed = markdown::parse_markdown(value, language_registry, language, style); + Some(Documentation::MultiLineMarkdown(parsed)) + } + }, + } +} + +#[derive(Clone, Debug)] +pub enum Documentation { + SingleLine(String), + MultiLinePlainText(String), + MultiLineMarkdown(ParsedMarkdown), +} + #[derive(Clone, Debug)] pub struct Completion { pub old_range: Range, pub new_text: String, pub label: CodeLabel, - pub alongside_documentation: Option, + pub documentation: Option, pub server_id: LanguageServerId, pub lsp_completion: lsp::CompletionItem, } diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index 4ccd2955b626b621271e20bcdbaf3d792780d634..c56a6763781efe0a1dc9e5945e43d7366321c378 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -7,31 +7,31 @@ use gpui::fonts::{HighlightStyle, Underline, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] -pub struct RenderedMarkdown { +pub struct ParsedMarkdown { pub text: String, pub highlights: Vec<(Range, HighlightStyle)>, pub region_ranges: Vec>, - pub regions: Vec, + pub regions: Vec, } #[derive(Debug, Clone)] -pub struct RenderedRegion { +pub struct ParsedRegion { pub code: bool, pub link_url: Option, } -pub fn render_markdown( +pub fn parse_markdown( markdown: &str, language_registry: &Arc, - language: &Option>, + language: Option>, style: &theme::Editor, -) -> RenderedMarkdown { +) -> ParsedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); let mut regions = Vec::new(); - render_markdown_block( + parse_markdown_block( markdown, language_registry, language, @@ -42,7 +42,7 @@ pub fn render_markdown( &mut regions, ); - RenderedMarkdown { + ParsedMarkdown { text, highlights, region_ranges, @@ -50,15 +50,15 @@ pub fn render_markdown( } } -pub fn render_markdown_block( +pub fn parse_markdown_block( markdown: &str, language_registry: &Arc, - language: &Option>, + language: Option>, style: &theme::Editor, text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, region_ranges: &mut Vec>, - regions: &mut Vec, + regions: &mut Vec, ) { let mut bold_depth = 0; let mut italic_depth = 0; @@ -71,7 +71,7 @@ pub fn render_markdown_block( match event { Event::Text(t) => { if let Some(language) = ¤t_language { - render_code(text, highlights, t.as_ref(), language, style); + highlight_code(text, highlights, t.as_ref(), language, style); } else { text.push_str(t.as_ref()); @@ -84,7 +84,7 @@ pub fn render_markdown_block( } if let Some(link_url) = link_url.clone() { region_ranges.push(prev_len..text.len()); - regions.push(RenderedRegion { + regions.push(ParsedRegion { link_url: Some(link_url), code: false, }); @@ -124,7 +124,7 @@ pub fn render_markdown_block( }, )); } - regions.push(RenderedRegion { + regions.push(ParsedRegion { code: true, link_url: link_url.clone(), }); @@ -202,7 +202,7 @@ pub fn render_markdown_block( } } -pub fn render_code( +pub fn highlight_code( text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, content: &str, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 49b332b4fb6d8626bcb99ee3b60bfbc021882cc3..957f4ee7fbc4da251698fd640d33d37d5cf4a06b 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -482,7 +482,7 @@ pub async fn deserialize_completion( lsp_completion.filter_text.as_deref(), ) }), - alongside_documentation: None, + documentation: None, server_id: LanguageServerId(completion.server_id as usize), lsp_completion, }) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 16ebb7467bc774fdea0461787d41321d3b968ea6..400dbe2abf16252b6d63594a795a6205c4c909c8 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1470,7 +1470,7 @@ impl LspCommand for GetCompletions { lsp_completion.filter_text.as_deref(), ) }), - alongside_documentation: None, + documentation: None, server_id, lsp_completion, } From ea6f366d2348135897fdb4a803097d4ffdfdab24 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 3 Oct 2023 11:58:49 -0400 Subject: [PATCH 029/180] If documentation exists and hasn't been parsed, do so at render and keep --- crates/editor/src/editor.rs | 46 ++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0f117cde1b83312f53a756afe64506c88135cc71..cabf73b58139b0df7d79528fdbebe7bb6f51a27d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,10 +60,10 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, - point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, - CursorShape, Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, - LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, - TransactionId, + point_from_lsp, prepare_completion_documentation, AutoindentMode, BracketPair, Buffer, + CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, + File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, + Selection, SelectionGoal, TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -1075,21 +1075,37 @@ impl CompletionsMenu { }) .map(|(ix, _)| ix); + let project = editor.project.clone(); let completions = self.completions.clone(); let matches = self.matches.clone(); let selected_item = self.selected_item; - let alongside_docs_width = style.autocomplete.alongside_docs_width; - let alongside_docs_container_style = style.autocomplete.alongside_docs_container; - let outer_container_style = style.autocomplete.container; - let list = UniformList::new(self.list.clone(), matches.len(), cx, { let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; - let completions = completions.read(); + let mut completions = completions.write(); + for (ix, mat) in matches[range].iter().enumerate() { - let completion = &completions[mat.candidate_id]; + let completion = &mut completions[mat.candidate_id]; + + if completion.documentation.is_none() { + if let Some(lsp_docs) = &completion.lsp_completion.documentation { + let project = project + .as_ref() + .expect("It is impossible have LSP servers without a project"); + + let language_registry = project.read(cx).languages(); + + completion.documentation = prepare_completion_documentation( + lsp_docs, + language_registry, + None, + &style.theme, + ); + } + } + let documentation = &completion.documentation; let item_ix = start_ix + ix; @@ -1121,9 +1137,7 @@ impl CompletionsMenu { ), ); - if let Some(language::Documentation::SingleLine(text)) = - documentation - { + if let Some(Documentation::SingleLine(text)) = documentation { Flex::row() .with_child(completion_label) .with_children((|| { @@ -1209,11 +1223,11 @@ impl CompletionsMenu { let documentation = &completion.documentation; match documentation { - Some(language::Documentation::MultiLinePlainText(text)) => { + Some(Documentation::MultiLinePlainText(text)) => { Some(Text::new(text.clone(), style.text.clone())) } - Some(language::Documentation::MultiLineMarkdown(parsed)) => { + Some(Documentation::MultiLineMarkdown(parsed)) => { Some(render_parsed_markdown(parsed, &style, cx)) } @@ -1221,7 +1235,7 @@ impl CompletionsMenu { } }) .contained() - .with_style(outer_container_style) + .with_style(style.autocomplete.container) .into_any() } From a881b1f5fb4b2c28a48581a90e16dba1201afe7e Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 4 Oct 2023 17:36:51 -0400 Subject: [PATCH 030/180] Wait for language to load when parsing markdown --- crates/editor/src/editor.rs | 42 +++++--- crates/editor/src/hover_popover.rs | 167 +++++++++++++++++------------ crates/language/src/buffer.rs | 6 +- crates/language/src/markdown.rs | 12 +-- 4 files changed, 136 insertions(+), 91 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cabf73b58139b0df7d79528fdbebe7bb6f51a27d..257abad41ba0d5b3bfceab1ff676a9e960690040 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1018,7 +1018,6 @@ impl CompletionsMenu { return; } - // TODO: Do on background cx.spawn(|this, mut cx| async move { let request = server.request::(completion); let Some(completion_item) = request.await.log_err() else { @@ -1031,7 +1030,8 @@ impl CompletionsMenu { &language_registry, None, // TODO: Try to reasonably work out which language the completion is for &style, - ); + ) + .await; let mut completions = completions.write(); let completion = &mut completions[index]; @@ -1084,30 +1084,46 @@ impl CompletionsMenu { let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; - let mut completions = completions.write(); + let completions_guard = completions.read(); for (ix, mat) in matches[range].iter().enumerate() { - let completion = &mut completions[mat.candidate_id]; + let item_ix = start_ix + ix; + let candidate_id = mat.candidate_id; + let completion = &completions_guard[candidate_id]; - if completion.documentation.is_none() { + if item_ix == selected_item && completion.documentation.is_none() { if let Some(lsp_docs) = &completion.lsp_completion.documentation { let project = project .as_ref() .expect("It is impossible have LSP servers without a project"); - let language_registry = project.read(cx).languages(); + let lsp_docs = lsp_docs.clone(); + let lsp_docs = lsp_docs.clone(); + let language_registry = project.read(cx).languages().clone(); + let style = style.theme.clone(); + let completions = completions.clone(); + + cx.spawn(|this, mut cx| async move { + let documentation = prepare_completion_documentation( + &lsp_docs, + &language_registry, + None, + &style, + ) + .await; - completion.documentation = prepare_completion_documentation( - lsp_docs, - language_registry, - None, - &style.theme, - ); + this.update(&mut cx, |_, cx| { + let mut completions = completions.write(); + completions[candidate_id].documentation = documentation; + drop(completions); + cx.notify(); + }) + }) + .detach(); } } let documentation = &completion.documentation; - let item_ix = start_ix + ix; items.push( MouseEventHandler::new::( diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 9341fa2da352d9848eda219eadb8922039439f50..585a335bd6848b051470d40800edfa6ef8d78ef0 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -7,7 +7,7 @@ use crate::{ use futures::FutureExt; use gpui::{ actions, - elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, + elements::{Empty, Flex, MouseEventHandler, Padding, ParentElement, Text}, fonts::HighlightStyle, platform::{CursorStyle, MouseButton}, AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, @@ -128,7 +128,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie symbol_range: DocumentRange::Inlay(inlay_hover.range), blocks: vec![inlay_hover.tooltip], language: None, - rendered_content: None, + parsed_content: None, }; this.update(&mut cx, |this, cx| { @@ -332,7 +332,7 @@ fn show_hover( symbol_range: DocumentRange::Text(range), blocks: hover_result.contents, language: hover_result.language, - rendered_content: None, + parsed_content: None, }) }); @@ -363,12 +363,12 @@ fn show_hover( editor.hover_state.info_task = Some(task); } -fn render_blocks( +async fn render_blocks( theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, language: Option>, - style: &EditorStyle, + style: &theme::Editor, ) -> ParsedInfo { let mut text = String::new(); let mut highlights = Vec::new(); @@ -382,16 +382,19 @@ fn render_blocks( text.push_str(&block.text); } - HoverBlockKind::Markdown => markdown::parse_markdown_block( - &block.text, - language_registry, - language.clone(), - style, - &mut text, - &mut highlights, - &mut region_ranges, - &mut regions, - ), + HoverBlockKind::Markdown => { + markdown::parse_markdown_block( + &block.text, + language_registry, + language.clone(), + style, + &mut text, + &mut highlights, + &mut region_ranges, + &mut regions, + ) + .await + } HoverBlockKind::Code { language } => { if let Some(language) = language_registry @@ -482,7 +485,7 @@ pub struct InfoPopover { symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - rendered_content: Option, + parsed_content: Option, } #[derive(Debug, Clone)] @@ -500,63 +503,87 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { - if let Some(rendered) = &self.rendered_content { - if rendered.theme_id != style.theme_id { - self.rendered_content = None; + if let Some(parsed) = &self.parsed_content { + if parsed.theme_id != style.theme_id { + self.parsed_content = None; } } - let rendered_content = self.rendered_content.get_or_insert_with(|| { - render_blocks( - style.theme_id, - &self.blocks, - self.project.read(cx).languages(), - self.language.clone(), - style, - ) - }); + let rendered = if let Some(parsed) = &self.parsed_content { + let view_id = cx.view_id(); + let regions = parsed.regions.clone(); + let code_span_background_color = style.document_highlight_read_background; - MouseEventHandler::new::(0, cx, |_, cx| { let mut region_id = 0; - let view_id = cx.view_id(); - let code_span_background_color = style.document_highlight_read_background; - let regions = rendered_content.regions.clone(); + Text::new(parsed.text.clone(), style.text.clone()) + .with_highlights(parsed.highlights.clone()) + .with_custom_runs(parsed.region_ranges.clone(), move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) + .into_any() + } else { + let theme_id = style.theme_id; + let language_registry = self.project.read(cx).languages().clone(); + let blocks = self.blocks.clone(); + let language = self.language.clone(); + let style = style.theme.clone(); + cx.spawn(|this, mut cx| async move { + let blocks = + render_blocks(theme_id, &blocks, &language_registry, language, &style).await; + _ = this.update(&mut cx, |_, cx| cx.notify()); + blocks + }) + .detach(); + + Empty::new().into_any() + }; + + // let rendered_content = self.parsed_content.get_or_insert_with(|| { + // let language_registry = self.project.read(cx).languages().clone(); + // cx.spawn(|this, mut cx| async move { + // let blocks = render_blocks( + // style.theme_id, + // &self.blocks, + // &language_registry, + // self.language.clone(), + // style, + // ) + // .await; + // this.update(&mut cx, |_, cx| cx.notify()); + // blocks + // }) + // .shared() + // }); + + MouseEventHandler::new::(0, cx, |_, cx| { Flex::column() .scrollable::(1, None, cx) - .with_child( - Text::new(rendered_content.text.clone(), style.text.clone()) - .with_highlights(rendered_content.highlights.clone()) - .with_custom_runs( - rendered_content.region_ranges.clone(), - move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::( - MouseButton::Left, - move |_, _, cx| cx.platform().open_url(&url), - ), - ); - } - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }, - ) - .with_soft_wrap(true), - ) + .with_child(rendered) .contained() .with_style(style.hover_popover.container) }) @@ -877,7 +904,8 @@ mod tests { ); let style = editor.style(cx); - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = + smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); assert_eq!( rendered.text, code_str.trim(), @@ -1069,7 +1097,8 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = + smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges @@ -1339,7 +1368,7 @@ mod tests { ); assert_eq!( popover - .rendered_content + .parsed_content .as_ref() .expect("should have label text for new type hint") .text, @@ -1403,7 +1432,7 @@ mod tests { ); assert_eq!( popover - .rendered_content + .parsed_content .as_ref() .expect("should have label text for struct hint") .text, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 344b470aa99740410bce3a3d9e4f2e575b6f2258..971494ea4fdc6b740045db7f79e358ccb34217c1 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -7,7 +7,7 @@ pub use crate::{ use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, language_settings::{language_settings, LanguageSettings}, - markdown, + markdown::parse_markdown, outline::OutlineItem, syntax_map::{ SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, @@ -145,7 +145,7 @@ pub struct Diagnostic { pub is_unnecessary: bool, } -pub fn prepare_completion_documentation( +pub async fn prepare_completion_documentation( documentation: &lsp::Documentation, language_registry: &Arc, language: Option>, @@ -170,7 +170,7 @@ pub fn prepare_completion_documentation( } lsp::MarkupKind::Markdown => { - let parsed = markdown::parse_markdown(value, language_registry, language, style); + let parsed = parse_markdown(value, language_registry, language, style).await; Some(Documentation::MultiLineMarkdown(parsed)) } }, diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index c56a6763781efe0a1dc9e5945e43d7366321c378..de5c7e8b098b591d83a311b376e90789a08605ff 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -2,7 +2,6 @@ use std::ops::Range; use std::sync::Arc; use crate::{Language, LanguageRegistry}; -use futures::FutureExt; use gpui::fonts::{HighlightStyle, Underline, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; @@ -20,7 +19,7 @@ pub struct ParsedRegion { pub link_url: Option, } -pub fn parse_markdown( +pub async fn parse_markdown( markdown: &str, language_registry: &Arc, language: Option>, @@ -40,7 +39,8 @@ pub fn parse_markdown( &mut highlights, &mut region_ranges, &mut regions, - ); + ) + .await; ParsedMarkdown { text, @@ -50,7 +50,7 @@ pub fn parse_markdown( } } -pub fn parse_markdown_block( +pub async fn parse_markdown_block( markdown: &str, language_registry: &Arc, language: Option>, @@ -143,8 +143,8 @@ pub fn parse_markdown_block( current_language = if let CodeBlockKind::Fenced(language) = kind { language_registry .language_for_name(language.as_ref()) - .now_or_never() - .and_then(Result::ok) + .await + .ok() } else { language.clone() } From 8dca4c3f9ac9b923fd9bef415fb0c6de19f5becb Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 5 Oct 2023 14:40:41 -0400 Subject: [PATCH 031/180] Don't need editor style to parse markdown --- crates/editor/src/editor.rs | 118 ++++++++++++---------- crates/editor/src/hover_popover.rs | 154 ++++++----------------------- crates/language/src/buffer.rs | 3 +- crates/language/src/markdown.rs | 63 ++++++------ 4 files changed, 133 insertions(+), 205 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 257abad41ba0d5b3bfceab1ff676a9e960690040..6b2be7c719e3c072dfb39b91a4cb1f3e042e0446 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,6 +60,7 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, + markdown::MarkdownHighlight, point_from_lsp, prepare_completion_documentation, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, @@ -120,21 +121,57 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); pub fn render_parsed_markdown( - md: &language::ParsedMarkdown, - style: &EditorStyle, + parsed: &language::ParsedMarkdown, + editor_style: &EditorStyle, cx: &mut ViewContext, ) -> Text { enum RenderedMarkdown {} - let md = md.clone(); - let code_span_background_color = style.document_highlight_read_background; + let parsed = parsed.clone(); let view_id = cx.view_id(); + let code_span_background_color = editor_style.document_highlight_read_background; + let mut region_id = 0; - Text::new(md.text, style.text.clone()) - .with_highlights(md.highlights) - .with_custom_runs(md.region_ranges, move |ix, bounds, scene, _| { + + Text::new(parsed.text, editor_style.text.clone()) + .with_highlights( + parsed + .highlights + .iter() + .filter_map(|(range, highlight)| { + let highlight = match highlight { + MarkdownHighlight::Style(style) => { + let mut highlight = HighlightStyle::default(); + + if style.italic { + highlight.italic = Some(true); + } + + if style.underline { + highlight.underline = Some(fonts::Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style.weight != fonts::Weight::default() { + highlight.weight = Some(style.weight); + } + + highlight + } + + MarkdownHighlight::Code(id) => id.style(&editor_style.syntax)?, + }; + + Some((range.clone(), highlight)) + }) + .collect::>(), + ) + .with_custom_runs(parsed.region_ranges, move |ix, bounds, scene, _| { region_id += 1; - let region = md.regions[ix].clone(); + let region = parsed.regions[ix].clone(); + if let Some(url) = region.link_url { scene.push_cursor_region(CursorRegion { bounds, @@ -147,6 +184,7 @@ pub fn render_parsed_markdown( }), ); } + if region.code { scene.push_quad(gpui::Quad { bounds, @@ -831,12 +869,11 @@ impl ContextMenu { fn select_first( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_first(project, style, cx), + ContextMenu::Completions(menu) => menu.select_first(project, cx), ContextMenu::CodeActions(menu) => menu.select_first(cx), } true @@ -848,12 +885,11 @@ impl ContextMenu { fn select_prev( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_prev(project, style, cx), + ContextMenu::Completions(menu) => menu.select_prev(project, cx), ContextMenu::CodeActions(menu) => menu.select_prev(cx), } true @@ -865,12 +901,11 @@ impl ContextMenu { fn select_next( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_next(project, style, cx), + ContextMenu::Completions(menu) => menu.select_next(project, cx), ContextMenu::CodeActions(menu) => menu.select_next(cx), } true @@ -882,12 +917,11 @@ impl ContextMenu { fn select_last( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_last(project, style, cx), + ContextMenu::Completions(menu) => menu.select_last(project, cx), ContextMenu::CodeActions(menu) => menu.select_last(cx), } true @@ -932,59 +966,54 @@ impl CompletionsMenu { fn select_first( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn select_prev( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item > 0 { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn select_next( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item + 1 < self.matches.len() { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn select_last( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn attempt_resolve_selected_completion( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { let index = self.matches[self.selected_item].candidate_id; @@ -1029,7 +1058,6 @@ impl CompletionsMenu { &lsp_documentation, &language_registry, None, // TODO: Try to reasonably work out which language the completion is for - &style, ) .await; @@ -1097,10 +1125,8 @@ impl CompletionsMenu { .as_ref() .expect("It is impossible have LSP servers without a project"); - let lsp_docs = lsp_docs.clone(); let lsp_docs = lsp_docs.clone(); let language_registry = project.read(cx).languages().clone(); - let style = style.theme.clone(); let completions = completions.clone(); cx.spawn(|this, mut cx| async move { @@ -1108,7 +1134,6 @@ impl CompletionsMenu { &lsp_docs, &language_registry, None, - &style, ) .await; @@ -3365,11 +3390,7 @@ impl Editor { None } else { _ = this.update(&mut cx, |editor, cx| { - menu.attempt_resolve_selected_completion( - editor.project.as_ref(), - editor.style(cx).theme, - cx, - ); + menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); }); Some(menu) } @@ -5545,16 +5566,13 @@ impl Editor { return; } - if self.context_menu.is_some() { - let style = self.style(cx).theme; - if self - .context_menu - .as_mut() - .map(|menu| menu.select_last(self.project.as_ref(), style, cx)) - .unwrap_or(false) - { - return; - } + if self + .context_menu + .as_mut() + .map(|menu| menu.select_last(self.project.as_ref(), cx)) + .unwrap_or(false) + { + return; } if matches!(self.mode, EditorMode::SingleLine) { @@ -5594,30 +5612,26 @@ impl Editor { } pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_first(self.project.as_ref(), style, cx); + context_menu.select_first(self.project.as_ref(), cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_prev(self.project.as_ref(), style, cx); + context_menu.select_prev(self.project.as_ref(), cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_next(self.project.as_ref(), style, cx); + context_menu.select_next(self.project.as_ref(), cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_last(self.project.as_ref(), style, cx); + context_menu.select_last(self.project.as_ref(), cx); } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 585a335bd6848b051470d40800edfa6ef8d78ef0..51fe27e58a33ce78ae80649d83be1aa112320699 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -8,13 +8,11 @@ use futures::FutureExt; use gpui::{ actions, elements::{Empty, Flex, MouseEventHandler, Padding, ParentElement, Text}, - fonts::HighlightStyle, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, + AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, }; use language::{ - markdown::{self, ParsedRegion}, - Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, + markdown, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, ParsedMarkdown, }; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use std::{ops::Range, sync::Arc, time::Duration}; @@ -363,13 +361,11 @@ fn show_hover( editor.hover_state.info_task = Some(task); } -async fn render_blocks( - theme_id: usize, +async fn parse_blocks( blocks: &[HoverBlock], language_registry: &Arc, language: Option>, - style: &theme::Editor, -) -> ParsedInfo { +) -> markdown::ParsedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); @@ -387,7 +383,6 @@ async fn render_blocks( &block.text, language_registry, language.clone(), - style, &mut text, &mut highlights, &mut region_ranges, @@ -402,13 +397,7 @@ async fn render_blocks( .now_or_never() .and_then(Result::ok) { - markdown::highlight_code( - &mut text, - &mut highlights, - &block.text, - &language, - style, - ); + markdown::highlight_code(&mut text, &mut highlights, &block.text, &language); } else { text.push_str(&block.text); } @@ -416,8 +405,7 @@ async fn render_blocks( } } - ParsedInfo { - theme_id, + ParsedMarkdown { text: text.trim().to_string(), highlights, region_ranges, @@ -485,16 +473,7 @@ pub struct InfoPopover { symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - parsed_content: Option, -} - -#[derive(Debug, Clone)] -struct ParsedInfo { - theme_id: usize, - text: String, - highlights: Vec<(Range, HighlightStyle)>, - region_ranges: Vec>, - regions: Vec, + parsed_content: Option, } impl InfoPopover { @@ -503,58 +482,14 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { - if let Some(parsed) = &self.parsed_content { - if parsed.theme_id != style.theme_id { - self.parsed_content = None; - } - } - let rendered = if let Some(parsed) = &self.parsed_content { - let view_id = cx.view_id(); - let regions = parsed.regions.clone(); - let code_span_background_color = style.document_highlight_read_background; - - let mut region_id = 0; - - Text::new(parsed.text.clone(), style.text.clone()) - .with_highlights(parsed.highlights.clone()) - .with_custom_runs(parsed.region_ranges.clone(), move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) - }), - ); - } - - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }) - .with_soft_wrap(true) - .into_any() + crate::render_parsed_markdown(parsed, style, cx).into_any() } else { - let theme_id = style.theme_id; let language_registry = self.project.read(cx).languages().clone(); let blocks = self.blocks.clone(); let language = self.language.clone(); - let style = style.theme.clone(); cx.spawn(|this, mut cx| async move { - let blocks = - render_blocks(theme_id, &blocks, &language_registry, language, &style).await; + let blocks = parse_blocks(&blocks, &language_registry, language).await; _ = this.update(&mut cx, |_, cx| cx.notify()); blocks }) @@ -563,23 +498,6 @@ impl InfoPopover { Empty::new().into_any() }; - // let rendered_content = self.parsed_content.get_or_insert_with(|| { - // let language_registry = self.project.read(cx).languages().clone(); - // cx.spawn(|this, mut cx| async move { - // let blocks = render_blocks( - // style.theme_id, - // &self.blocks, - // &language_registry, - // self.language.clone(), - // style, - // ) - // .await; - // this.update(&mut cx, |_, cx| cx.notify()); - // blocks - // }) - // .shared() - // }); - MouseEventHandler::new::(0, cx, |_, cx| { Flex::column() .scrollable::(1, None, cx) @@ -678,9 +596,12 @@ mod tests { test::editor_lsp_test_context::EditorLspTestContext, }; use collections::BTreeSet; - use gpui::fonts::{Underline, Weight}; + use gpui::fonts::Weight; use indoc::indoc; - use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; + use language::{ + language_settings::InlayHintSettings, markdown::MarkdownHighlightStyle, Diagnostic, + DiagnosticSet, + }; use lsp::LanguageServerId; use project::{HoverBlock, HoverBlockKind}; use smol::stream::StreamExt; @@ -893,7 +814,7 @@ mod tests { .await; cx.condition(|editor, _| editor.hover_state.visible()).await; - cx.editor(|editor, cx| { + cx.editor(|editor, _| { let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; assert_eq!( blocks, @@ -903,9 +824,7 @@ mod tests { }], ); - let style = editor.style(cx); - let rendered = - smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); + let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); assert_eq!( rendered.text, code_str.trim(), @@ -984,16 +903,17 @@ mod tests { #[gpui::test] fn test_render_blocks(cx: &mut gpui::TestAppContext) { + use markdown::MarkdownHighlight; + init_test(cx, |_| {}); cx.add_window(|cx| { let editor = Editor::single_line(None, cx); - let style = editor.style(cx); struct Row { blocks: Vec, expected_marked_text: String, - expected_styles: Vec, + expected_styles: Vec, } let rows = &[ @@ -1004,10 +924,10 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![HighlightStyle { - weight: Some(Weight::BOLD), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + weight: Weight::BOLD, ..Default::default() - }], + })], }, // Links Row { @@ -1016,13 +936,10 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }], + })], }, // Lists Row { @@ -1047,13 +964,10 @@ mod tests { - «c» - d" .unindent(), - expected_styles: vec![HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }], + })], }, // Multi-paragraph list items Row { @@ -1081,13 +995,10 @@ mod tests { - ten - six" .unindent(), - expected_styles: vec![HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }], + })], }, ]; @@ -1097,8 +1008,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = - smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); + let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 971494ea4fdc6b740045db7f79e358ccb34217c1..d318a87b40850f2799a0db7d95bbaa5a67556f5e 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -149,7 +149,6 @@ pub async fn prepare_completion_documentation( documentation: &lsp::Documentation, language_registry: &Arc, language: Option>, - style: &theme::Editor, ) -> Option { match documentation { lsp::Documentation::String(text) => { @@ -170,7 +169,7 @@ pub async fn prepare_completion_documentation( } lsp::MarkupKind::Markdown => { - let parsed = parse_markdown(value, language_registry, language, style).await; + let parsed = parse_markdown(value, language_registry, language).await; Some(Documentation::MultiLineMarkdown(parsed)) } }, diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index de5c7e8b098b591d83a311b376e90789a08605ff..9f29e7cb885765ae6d04e74b065e0619323e0051 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -1,18 +1,31 @@ use std::ops::Range; use std::sync::Arc; -use crate::{Language, LanguageRegistry}; -use gpui::fonts::{HighlightStyle, Underline, Weight}; +use crate::{HighlightId, Language, LanguageRegistry}; +use gpui::fonts::Weight; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] pub struct ParsedMarkdown { pub text: String, - pub highlights: Vec<(Range, HighlightStyle)>, + pub highlights: Vec<(Range, MarkdownHighlight)>, pub region_ranges: Vec>, pub regions: Vec, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MarkdownHighlight { + Style(MarkdownHighlightStyle), + Code(HighlightId), +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct MarkdownHighlightStyle { + pub italic: bool, + pub underline: bool, + pub weight: Weight, +} + #[derive(Debug, Clone)] pub struct ParsedRegion { pub code: bool, @@ -23,7 +36,6 @@ pub async fn parse_markdown( markdown: &str, language_registry: &Arc, language: Option>, - style: &theme::Editor, ) -> ParsedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); @@ -34,7 +46,6 @@ pub async fn parse_markdown( markdown, language_registry, language, - style, &mut text, &mut highlights, &mut region_ranges, @@ -54,9 +65,8 @@ pub async fn parse_markdown_block( markdown: &str, language_registry: &Arc, language: Option>, - style: &theme::Editor, text: &mut String, - highlights: &mut Vec<(Range, HighlightStyle)>, + highlights: &mut Vec<(Range, MarkdownHighlight)>, region_ranges: &mut Vec>, regions: &mut Vec, ) { @@ -71,16 +81,16 @@ pub async fn parse_markdown_block( match event { Event::Text(t) => { if let Some(language) = ¤t_language { - highlight_code(text, highlights, t.as_ref(), language, style); + highlight_code(text, highlights, t.as_ref(), language); } else { text.push_str(t.as_ref()); - let mut style = HighlightStyle::default(); + let mut style = MarkdownHighlightStyle::default(); if bold_depth > 0 { - style.weight = Some(Weight::BOLD); + style.weight = Weight::BOLD; } if italic_depth > 0 { - style.italic = Some(true); + style.italic = true; } if let Some(link_url) = link_url.clone() { region_ranges.push(prev_len..text.len()); @@ -88,22 +98,22 @@ pub async fn parse_markdown_block( link_url: Some(link_url), code: false, }); - style.underline = Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }); + style.underline = true; } - if style != HighlightStyle::default() { + if style != MarkdownHighlightStyle::default() { let mut new_highlight = true; - if let Some((last_range, last_style)) = highlights.last_mut() { + if let Some((last_range, MarkdownHighlight::Style(last_style))) = + highlights.last_mut() + { if last_range.end == prev_len && last_style == &style { last_range.end = text.len(); new_highlight = false; } } if new_highlight { - highlights.push((prev_len..text.len(), style)); + let range = prev_len..text.len(); + highlights.push((range, MarkdownHighlight::Style(style))); } } } @@ -115,13 +125,10 @@ pub async fn parse_markdown_block( if link_url.is_some() { highlights.push(( prev_len..text.len(), - HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }, + }), )); } regions.push(ParsedRegion { @@ -204,17 +211,15 @@ pub async fn parse_markdown_block( pub fn highlight_code( text: &mut String, - highlights: &mut Vec<(Range, HighlightStyle)>, + highlights: &mut Vec<(Range, MarkdownHighlight)>, content: &str, language: &Arc, - style: &theme::Editor, ) { let prev_len = text.len(); text.push_str(content); for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { - if let Some(style) = highlight_id.style(&style.syntax) { - highlights.push((prev_len + range.start..prev_len + range.end, style)); - } + let highlight = MarkdownHighlight::Code(highlight_id); + highlights.push((prev_len + range.start..prev_len + range.end, highlight)); } } From 32a29cd4d32d447c00af9157c99f45d57d219cda Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 5 Oct 2023 23:57:01 -0400 Subject: [PATCH 032/180] Unbork info popover parsing/rendering and make better --- crates/editor/src/hover_popover.rs | 105 +++++++++++++---------------- 1 file changed, 45 insertions(+), 60 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 51fe27e58a33ce78ae80649d83be1aa112320699..7917d5786574201831ce1ff83ab0398841a994f7 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -7,7 +7,7 @@ use crate::{ use futures::FutureExt; use gpui::{ actions, - elements::{Empty, Flex, MouseEventHandler, Padding, ParentElement, Text}, + elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, platform::{CursorStyle, MouseButton}, AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, }; @@ -121,12 +121,15 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie this.hover_state.diagnostic_popover = None; })?; + let language_registry = project.update(&mut cx, |p, _| p.languages().clone()); + let blocks = vec![inlay_hover.tooltip]; + let parsed_content = parse_blocks(&blocks, &language_registry, None).await; + let hover_popover = InfoPopover { project: project.clone(), symbol_range: DocumentRange::Inlay(inlay_hover.range), - blocks: vec![inlay_hover.tooltip], - language: None, - parsed_content: None, + blocks, + parsed_content, }; this.update(&mut cx, |this, cx| { @@ -304,35 +307,38 @@ fn show_hover( }); })?; - // Construct new hover popover from hover request - let hover_popover = hover_request.await.ok().flatten().and_then(|hover_result| { - if hover_result.is_empty() { - return None; + let hover_result = hover_request.await.ok().flatten(); + let hover_popover = match hover_result { + Some(hover_result) if !hover_result.is_empty() => { + // Create symbol range of anchors for highlighting and filtering of future requests. + let range = if let Some(range) = hover_result.range { + let start = snapshot + .buffer_snapshot + .anchor_in_excerpt(excerpt_id.clone(), range.start); + let end = snapshot + .buffer_snapshot + .anchor_in_excerpt(excerpt_id.clone(), range.end); + + start..end + } else { + anchor..anchor + }; + + let language_registry = project.update(&mut cx, |p, _| p.languages().clone()); + let blocks = hover_result.contents; + let language = hover_result.language; + let parsed_content = parse_blocks(&blocks, &language_registry, language).await; + + Some(InfoPopover { + project: project.clone(), + symbol_range: DocumentRange::Text(range), + blocks, + parsed_content, + }) } - // Create symbol range of anchors for highlighting and filtering - // of future requests. - let range = if let Some(range) = hover_result.range { - let start = snapshot - .buffer_snapshot - .anchor_in_excerpt(excerpt_id.clone(), range.start); - let end = snapshot - .buffer_snapshot - .anchor_in_excerpt(excerpt_id.clone(), range.end); - - start..end - } else { - anchor..anchor - }; - - Some(InfoPopover { - project: project.clone(), - symbol_range: DocumentRange::Text(range), - blocks: hover_result.contents, - language: hover_result.language, - parsed_content: None, - }) - }); + _ => None, + }; this.update(&mut cx, |this, cx| { if let Some(symbol_range) = hover_popover @@ -472,8 +478,7 @@ pub struct InfoPopover { pub project: ModelHandle, symbol_range: DocumentRange, pub blocks: Vec, - language: Option>, - parsed_content: Option, + parsed_content: ParsedMarkdown, } impl InfoPopover { @@ -482,26 +487,14 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { - let rendered = if let Some(parsed) = &self.parsed_content { - crate::render_parsed_markdown(parsed, style, cx).into_any() - } else { - let language_registry = self.project.read(cx).languages().clone(); - let blocks = self.blocks.clone(); - let language = self.language.clone(); - cx.spawn(|this, mut cx| async move { - let blocks = parse_blocks(&blocks, &language_registry, language).await; - _ = this.update(&mut cx, |_, cx| cx.notify()); - blocks - }) - .detach(); - - Empty::new().into_any() - }; - MouseEventHandler::new::(0, cx, |_, cx| { Flex::column() .scrollable::(1, None, cx) - .with_child(rendered) + .with_child(crate::render_parsed_markdown( + &self.parsed_content, + style, + cx, + )) .contained() .with_style(style.hover_popover.container) }) @@ -1277,11 +1270,7 @@ mod tests { "Popover range should match the new type label part" ); assert_eq!( - popover - .parsed_content - .as_ref() - .expect("should have label text for new type hint") - .text, + popover.parsed_content.text, format!("A tooltip for `{new_type_label}`"), "Rendered text should not anyhow alter backticks" ); @@ -1341,11 +1330,7 @@ mod tests { "Popover range should match the struct label part" ); assert_eq!( - popover - .parsed_content - .as_ref() - .expect("should have label text for struct hint") - .text, + popover.parsed_content.text, format!("A tooltip for {struct_label}"), "Rendered markdown element should remove backticks from text" ); From 9d8cff1275e8bfa6a6f08d48c286cdc9a8a5ab6e Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 6 Oct 2023 00:17:36 -0400 Subject: [PATCH 033/180] If documentation included in original completion then parse up front --- crates/editor/src/editor.rs | 51 +++++-------------------------- crates/project/src/lsp_command.rs | 27 ++++++++++++---- 2 files changed, 28 insertions(+), 50 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6b2be7c719e3c072dfb39b91a4cb1f3e042e0446..6585611040f3b93666fd6341531892a40b99b15b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -61,10 +61,10 @@ pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, markdown::MarkdownHighlight, - point_from_lsp, prepare_completion_documentation, AutoindentMode, BracketPair, Buffer, - CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, - File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, - Selection, SelectionGoal, TransactionId, + point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, + CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, + Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, + TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -940,12 +940,11 @@ impl ContextMenu { fn render( &self, cursor_position: DisplayPoint, - editor: &Editor, style: EditorStyle, cx: &mut ViewContext, ) -> (DisplayPoint, AnyElement) { match self { - ContextMenu::Completions(menu) => (cursor_position, menu.render(editor, style, cx)), + ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), } } @@ -1077,12 +1076,7 @@ impl CompletionsMenu { !self.matches.is_empty() } - fn render( - &self, - editor: &Editor, - style: EditorStyle, - cx: &mut ViewContext, - ) -> AnyElement { + fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { enum CompletionTag {} let widest_completion_ix = self @@ -1103,7 +1097,6 @@ impl CompletionsMenu { }) .map(|(ix, _)| ix); - let project = editor.project.clone(); let completions = self.completions.clone(); let matches = self.matches.clone(); let selected_item = self.selected_item; @@ -1118,36 +1111,6 @@ impl CompletionsMenu { let item_ix = start_ix + ix; let candidate_id = mat.candidate_id; let completion = &completions_guard[candidate_id]; - - if item_ix == selected_item && completion.documentation.is_none() { - if let Some(lsp_docs) = &completion.lsp_completion.documentation { - let project = project - .as_ref() - .expect("It is impossible have LSP servers without a project"); - - let lsp_docs = lsp_docs.clone(); - let language_registry = project.read(cx).languages().clone(); - let completions = completions.clone(); - - cx.spawn(|this, mut cx| async move { - let documentation = prepare_completion_documentation( - &lsp_docs, - &language_registry, - None, - ) - .await; - - this.update(&mut cx, |_, cx| { - let mut completions = completions.write(); - completions[candidate_id].documentation = documentation; - drop(completions); - cx.notify(); - }) - }) - .detach(); - } - } - let documentation = &completion.documentation; items.push( @@ -4201,7 +4164,7 @@ impl Editor { ) -> Option<(DisplayPoint, AnyElement)> { self.context_menu .as_ref() - .map(|menu| menu.render(cursor_position, self, style, cx)) + .map(|menu| menu.render(cursor_position, style, cx)) } fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 400dbe2abf16252b6d63594a795a6205c4c909c8..c71b378da685cd429a11a392ae242c71da7f8158 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -10,7 +10,7 @@ use futures::future; use gpui::{AppContext, AsyncAppContext, ModelHandle}; use language::{ language_settings::{language_settings, InlayHintKind}, - point_from_lsp, point_to_lsp, + point_from_lsp, point_to_lsp, prepare_completion_documentation, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, @@ -1341,7 +1341,7 @@ impl LspCommand for GetCompletions { async fn response_from_lsp( self, completions: Option, - _: ModelHandle, + project: ModelHandle, buffer: ModelHandle, server_id: LanguageServerId, cx: AsyncAppContext, @@ -1361,7 +1361,8 @@ impl LspCommand for GetCompletions { Vec::new() }; - let completions = buffer.read_with(&cx, |buffer, _| { + let completions = buffer.read_with(&cx, |buffer, cx| { + let language_registry = project.read(cx).languages().clone(); let language = buffer.language().cloned(); let snapshot = buffer.snapshot(); let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left); @@ -1453,14 +1454,28 @@ impl LspCommand for GetCompletions { } }; - let language = language.clone(); LineEnding::normalize(&mut new_text); + let language_registry = language_registry.clone(); + let language = language.clone(); + Some(async move { let mut label = None; - if let Some(language) = language { + if let Some(language) = language.as_ref() { language.process_completion(&mut lsp_completion).await; label = language.label_for_completion(&lsp_completion).await; } + + let documentation = if let Some(lsp_docs) = &lsp_completion.documentation { + prepare_completion_documentation( + lsp_docs, + &language_registry, + language.clone(), + ) + .await + } else { + None + }; + Completion { old_range, new_text, @@ -1470,7 +1485,7 @@ impl LspCommand for GetCompletions { lsp_completion.filter_text.as_deref(), ) }), - documentation: None, + documentation, server_id, lsp_completion, } From f18f870206bf274a0e9a9dfd92efd8d4ed4b5c82 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 6 Oct 2023 11:56:55 -0400 Subject: [PATCH 034/180] Re-enable language servers --- crates/zed/src/languages.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 3b65255a3de2cd3fcdb6322405302ad78f647214..04e5292a7df6cf4fa9c58d1999aed246f963c352 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -128,8 +128,8 @@ pub fn init( "tsx", tree_sitter_typescript::language_tsx(), vec![ - // Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), - // Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), + Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), ], ); From b58c42cd536e2317a009ed48464a8f691199b731 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 13:47:35 -0600 Subject: [PATCH 035/180] TEMP --- .cargo/config.toml | 2 +- crates/util/src/channel.rs | 49 ++++++---- crates/workspace/src/workspace.rs | 149 +++++++++++++++++++++--------- crates/zed/src/main.rs | 107 ++++++++++----------- crates/zed/src/open_url.rs | 7 +- 5 files changed, 191 insertions(+), 123 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 9da6b3be080072d89d16a199e2d60d527eeacd07..e22bdb0f2c70a1ffda714674253cc533e9e7c1d1 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0"] +rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 761b17e6af0d74f67f603808a7f515bf4ffdf9b7..2364dcaad48d79272dddf9bbabbf67c0d590270a 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -1,7 +1,6 @@ use std::env; use lazy_static::lazy_static; -use url::Url; lazy_static! { pub static ref RELEASE_CHANNEL_NAME: String = if cfg!(debug_assertions) { @@ -16,22 +15,6 @@ lazy_static! { "stable" => ReleaseChannel::Stable, _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME), }; - - pub static ref URL_SCHEME_PREFIX: String = match RELEASE_CHANNEL_NAME.as_str() { - "dev" => "zed-dev:/", - "preview" => "zed-preview:/", - "stable" => "zed:/", - // NOTE: this must be kept in sync with osx_url_schemes in Cargo.toml and with https://zed.dev. - _ => unreachable!(), - }.to_string(); - pub static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { - "dev" => "http://localhost:3000/dev/", - "preview" => "https://zed.dev/preview/", - "stable" => "https://zed.dev/", - // NOTE: this must be kept in sync with https://zed.dev. - _ => unreachable!(), - }) - .unwrap(); } #[derive(Copy, Clone, PartialEq, Eq, Default)] @@ -58,4 +41,36 @@ impl ReleaseChannel { ReleaseChannel::Stable => "stable", } } + + pub fn url_scheme(&self) -> &'static str { + match self { + ReleaseChannel::Dev => "zed-dev:/", + ReleaseChannel::Preview => "zed-preview:/", + ReleaseChannel::Stable => "zed:/", + } + } + + pub fn link_prefix(&self) -> &'static str { + match self { + ReleaseChannel::Dev => "https://zed.dev/dev/", + ReleaseChannel::Preview => "https://zed.dev/preview/", + ReleaseChannel::Stable => "https://zed.dev/", + } + } +} + +pub fn parse_zed_link(link: &str) -> Option<&str> { + for release in [ + ReleaseChannel::Dev, + ReleaseChannel::Preview, + ReleaseChannel::Stable, + ] { + if let Some(stripped) = link.strip_prefix(release.link_prefix()) { + return Some(stripped); + } + if let Some(stripped) = link.strip_prefix(release.url_scheme()) { + return Some(stripped); + } + } + None } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5ec847b28b337ca52879511c8531a9a4e0e380b5..1002ae29dc2059335f79d0d67e6631dad1eecf6e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -15,14 +15,14 @@ use call::ActiveCall; use channel::ChannelStore; use client::{ proto::{self, PeerId}, - Client, TypedEnvelope, UserStore, + Client, Status, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; use drag_and_drop::DragAndDrop; use futures::{ channel::{mpsc, oneshot}, future::try_join_all, - FutureExt, StreamExt, + select_biased, FutureExt, StreamExt, }; use gpui::{ actions, @@ -4154,57 +4154,119 @@ pub async fn last_opened_workspace_paths() -> Option { DB.last_workspace().await.log_err().flatten() } -pub fn join_channel( +async fn join_channel_internal( channel_id: u64, app_state: Arc, requesting_window: Option>, - cx: &mut AppContext, -) -> Task> { - let active_call = ActiveCall::global(cx); - cx.spawn(|mut cx| async move { - let should_prompt = active_call.read_with(&mut cx, |active_call, cx| { - let Some(room) = active_call.room().map( |room| room.read(cx) ) else { - return false - }; + active_call: &ModelHandle, + cx: &mut AsyncAppContext, +) -> Result { + let should_prompt = active_call.read_with(cx, |active_call, cx| { + let Some(room) = active_call.room().map(|room| room.read(cx)) else { + return false; + }; - room.is_sharing_project() && room.remote_participants().len() > 0 && - room.channel_id() != Some(channel_id) - }); + room.is_sharing_project() + && room.remote_participants().len() > 0 + && room.channel_id() != Some(channel_id) + }); - if should_prompt { - if let Some(workspace) = requesting_window { - if let Some(window) = workspace.update(&mut cx, |cx| { - cx.window() - }) { - let answer = window.prompt( - PromptLevel::Warning, - "Leaving this call will unshare your current project.\nDo you want to switch channels?", - &["Yes, Join Channel", "Cancel"], - &mut cx, - ); + if should_prompt { + if let Some(workspace) = requesting_window { + if let Some(window) = workspace.update(cx, |cx| cx.window()) { + let answer = window.prompt( + PromptLevel::Warning, + "Leaving this call will unshare your current project.\nDo you want to switch channels?", + &["Yes, Join Channel", "Cancel"], + cx, + ); - if let Some(mut answer) = answer { - if answer.next().await == Some(1) { - return Ok(()); - } + if let Some(mut answer) = answer { + if answer.next().await == Some(1) { + return Ok(false); } } + } else { + return Ok(false); // unreachable!() hopefully } + } else { + return Ok(false); // unreachable!() hopefully } + } - let room = active_call.update(&mut cx, |active_call, cx| { - active_call.join_channel(channel_id, cx) - }).await?; + let client = cx.read(|cx| active_call.read(cx).client()); - let task = room.update(&mut cx, |room, cx| { - if let Some((project, host)) = room.most_active_project() { - return Some(join_remote_project(project, host, app_state.clone(), cx)) + let mut timer = cx.background().timer(Duration::from_secs(5)).fuse(); + let mut client_status = client.status(); + + 'outer: loop { + select_biased! { + _ = timer => { + return Err(anyhow!("connecting timed out")) + }, + status = client_status.recv().fuse() => { + let Some(status) = status else { + return Err(anyhow!("unexpected error reading connection status")) + }; + + match status { + Status::Connecting | Status::Authenticating | Status::Reconnecting | Status::Reauthenticating => continue, + Status::Connected { .. } => break 'outer, + Status::SignedOut => { + if client.has_keychain_credentials(&cx) { + client.authenticate_and_connect(true, &cx).await?; + timer = cx.background().timer(Duration::from_secs(5)).fuse(); + } else { + return Err(anyhow!("not signed in")) + } + }, + Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), + | Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => return Err(anyhow!("zed is offline")) + } } + } + } - None - }); - if let Some(task) = task { - task.await?; + let room = active_call + .update(cx, |active_call, cx| { + active_call.join_channel(channel_id, cx) + }) + .await?; + + let task = room.update(cx, |room, cx| { + if let Some((project, host)) = room.most_active_project() { + return Some(join_remote_project(project, host, app_state.clone(), cx)); + } + + None + }); + if let Some(task) = task { + task.await?; + return anyhow::Ok(true); + } + + anyhow::Ok(false) +} + +pub fn join_channel( + channel_id: u64, + app_state: Arc, + requesting_window: Option>, + cx: &mut AppContext, +) -> Task> { + let active_call = ActiveCall::global(cx); + cx.spawn(|mut cx| { + let result = join_channel_internal( + channel_id, + app_state, + requesting_window, + &active_call, + &mut cx, + ) + .await; + + // join channel succeeded, and opened a window + if Some(true) = result { return anyhow::Ok(()); } @@ -4223,16 +4285,15 @@ pub fn join_channel( }); if found.unwrap_or(false) { - return anyhow::Ok(()) + return anyhow::Ok(()); } } // no open workspaces - cx.update(|cx| { - Workspace::new_local(vec![], app_state.clone(), requesting_window, cx) - }).await; + cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) + .await; - return anyhow::Ok(()); + return connected.map(|_| ()); }) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9d0451ecfa97cf7da8001ccbcee03fe56a274d5f..861121a1cf20c4037a4110cf3246982cb059477f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -43,7 +43,7 @@ use std::{ }; use sum_tree::Bias; use util::{ - channel::ReleaseChannel, + channel::{parse_zed_link, ReleaseChannel}, http::{self, HttpClient}, paths::PathLikeWithPosition, }; @@ -206,12 +206,9 @@ fn main() { if stdout_is_a_pty() { cx.platform().activate(true); - let paths = collect_path_args(); - if paths.is_empty() { - cx.spawn(|cx| async move { restore_or_create_workspace(&app_state, cx).await }) - .detach() - } else { - workspace::open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx); + let urls = collect_url_args(); + if !urls.is_empty() { + listener.open_urls(urls) } } else { upload_previous_panics(http.clone(), cx); @@ -223,53 +220,51 @@ fn main() { { listener.open_urls(collect_url_args()) } + } - match open_rx.try_next() { - Ok(Some(OpenRequest::Paths { paths })) => { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); - } - Ok(Some(OpenRequest::CliConnection { connection })) => { - cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) - .detach(); - } - Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx - .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) - .detach(), - Ok(None) | Err(_) => cx - .spawn({ - let app_state = app_state.clone(); - |cx| async move { restore_or_create_workspace(&app_state, cx).await } - }) - .detach(), + match open_rx.try_next() { + Ok(Some(OpenRequest::Paths { paths })) => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .detach(); + } + Ok(Some(OpenRequest::CliConnection { connection })) => { + cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) + .detach(); } + Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx + .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) + .detach_and_log_err(cx), + Ok(None) | Err(_) => cx + .spawn({ + let app_state = app_state.clone(); + |cx| async move { restore_or_create_workspace(&app_state, cx).await } + }) + .detach(), + } - cx.spawn(|mut cx| { - let app_state = app_state.clone(); - async move { - while let Some(request) = open_rx.next().await { - match request { - OpenRequest::Paths { paths } => { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); - } - OpenRequest::CliConnection { connection } => { - cx.spawn(|cx| { - handle_cli_connection(connection, app_state.clone(), cx) - }) + cx.spawn(|mut cx| { + let app_state = app_state.clone(); + async move { + while let Some(request) = open_rx.next().await { + match request { + OpenRequest::Paths { paths } => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .detach(); + } + OpenRequest::CliConnection { connection } => { + cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) .detach(); - } - OpenRequest::JoinChannel { channel_id } => cx - .update(|cx| { - workspace::join_channel(channel_id, app_state.clone(), None, cx) - }) - .detach(), } + OpenRequest::JoinChannel { channel_id } => cx + .update(|cx| { + workspace::join_channel(channel_id, app_state.clone(), None, cx) + }) + .detach(), } } - }) - .detach(); - } + } + }) + .detach(); cx.spawn(|cx| async move { if stdout_is_a_pty() { @@ -608,23 +603,23 @@ fn stdout_is_a_pty() -> bool { std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && std::io::stdout().is_terminal() } -fn collect_path_args() -> Vec { +fn collect_url_args() -> Vec { env::args() .skip(1) - .filter_map(|arg| match std::fs::canonicalize(arg) { - Ok(path) => Some(path), + .filter_map(|arg| match std::fs::canonicalize(Path::new(&arg)) { + Ok(path) => Some(format!("file://{}", path.to_string_lossy())), Err(error) => { - log::error!("error parsing path argument: {}", error); - None + if let Some(_) = parse_zed_link(&arg) { + Some(arg) + } else { + log::error!("error parsing path argument: {}", error); + None + } } }) .collect() } -fn collect_url_args() -> Vec { - env::args().skip(1).collect() -} - fn load_embedded_fonts(app: &App) { let font_paths = Assets.list("fonts"); let embedded_fonts = Mutex::new(Vec::new()); diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index 1c741a02c89d3ec20d88d866c2d2c4b469385ab2..6f90953de2740617017556f803cd87a670e99c77 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -6,7 +6,7 @@ use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; use std::sync::atomic::Ordering; use std::{path::PathBuf, sync::atomic::AtomicBool}; -use util::channel::URL_SCHEME_PREFIX; +use util::channel::parse_zed_link; use util::ResultExt; use crate::connect_to_cli; @@ -47,10 +47,7 @@ impl OpenListener { urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { self.handle_cli_connection(server_name) - } else if let Some(request_path) = urls - .first() - .and_then(|url| url.strip_prefix(URL_SCHEME_PREFIX.as_str())) - { + } else if let Some(request_path) = urls.first().and_then(|url| parse_zed_link(url)) { self.handle_zed_url_scheme(request_path) } else { self.handle_file_urls(urls) From 4128e2ffcbb5317ffc668c0c851e3a51474649e2 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 15:18:25 -0600 Subject: [PATCH 036/180] Fix panic if the host is not there. --- crates/call/src/room.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 72db174d7256b0a5686bee0210bb5c37464bc97d..2543697bc0f86e1d69cb0cb0b51dc2ff63cf53ad 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -602,6 +602,7 @@ impl Room { pub fn most_active_project(&self) -> Option<(u64, u64)> { let mut projects = HashMap::default(); let mut hosts = HashMap::default(); + for participant in self.remote_participants.values() { match participant.location { ParticipantLocation::SharedProject { project_id } => { @@ -619,8 +620,8 @@ impl Room { pairs.sort_by_key(|(_, count)| *count as i32); pairs - .first() - .map(|(project_id, _)| (*project_id, hosts[&project_id])) + .iter() + .find_map(|(project_id, _)| hosts.get(project_id).map(|host| (*project_id, *host))) } async fn handle_room_updated( From 63a230f92e04e839f52855419dfb7e3477910866 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 16:11:45 -0600 Subject: [PATCH 037/180] Make joining on boot work --- crates/call/src/room.rs | 20 +++++- crates/client/src/client.rs | 3 +- crates/workspace/src/workspace.rs | 110 +++++++++++++++++------------- crates/zed/src/main.rs | 49 ++++++++----- crates/zed/src/open_url.rs | 1 - 5 files changed, 118 insertions(+), 65 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 2543697bc0f86e1d69cb0cb0b51dc2ff63cf53ad..5419e00f02e093419862d0c7cbaf4dab70c7b0f0 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -18,7 +18,7 @@ use live_kit_client::{ LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, }; -use postage::stream::Stream; +use postage::{sink::Sink, stream::Stream, watch}; use project::Project; use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration}; use util::{post_inc, ResultExt, TryFutureExt}; @@ -70,6 +70,8 @@ pub struct Room { user_store: ModelHandle, follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec>, subscriptions: Vec, + room_update_completed_tx: watch::Sender>, + room_update_completed_rx: watch::Receiver>, pending_room_update: Option>, maintain_connection: Option>>, } @@ -211,6 +213,8 @@ impl Room { Audio::play_sound(Sound::Joined, cx); + let (room_update_completed_tx, room_update_completed_rx) = watch::channel(); + Self { id, channel_id, @@ -230,6 +234,8 @@ impl Room { user_store, follows_by_leader_id_project_id: Default::default(), maintain_connection: Some(maintain_connection), + room_update_completed_tx, + room_update_completed_rx, } } @@ -856,6 +862,7 @@ impl Room { }); this.check_invariants(); + this.room_update_completed_tx.try_send(Some(())).ok(); cx.notify(); }); })); @@ -864,6 +871,17 @@ impl Room { Ok(()) } + pub fn next_room_update(&mut self) -> impl Future { + let mut done_rx = self.room_update_completed_rx.clone(); + async move { + while let Some(result) = done_rx.next().await { + if result.is_some() { + break; + } + } + } + } + fn remote_video_track_updated( &mut self, change: RemoteVideoTrackUpdate, diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 5767ac54b7893f7425dfd56202b7512d17314f0f..62cd60c55fd339965232448d0a306cc3dd457ff4 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -798,7 +798,8 @@ impl Client { } } } - _ = status_rx.next().fuse() => { + status = status_rx.next().fuse() => { + dbg!(status); return Err(anyhow!("authentication canceled")); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1002ae29dc2059335f79d0d67e6631dad1eecf6e..a1249173090a8af506d16efe1b8c25657b358e34 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -22,7 +22,7 @@ use drag_and_drop::DragAndDrop; use futures::{ channel::{mpsc, oneshot}, future::try_join_all, - select_biased, FutureExt, StreamExt, + FutureExt, StreamExt, }; use gpui::{ actions, @@ -36,9 +36,9 @@ use gpui::{ CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds, WindowOptions, }, - AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, - ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, WindowHandle, + AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AnyWindowHandle, AppContext, AsyncAppContext, + Entity, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, WindowHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use itertools::Itertools; @@ -4156,7 +4156,7 @@ pub async fn last_opened_workspace_paths() -> Option { async fn join_channel_internal( channel_id: u64, - app_state: Arc, + app_state: &Arc, requesting_window: Option>, active_call: &ModelHandle, cx: &mut AsyncAppContext, @@ -4196,33 +4196,24 @@ async fn join_channel_internal( let client = cx.read(|cx| active_call.read(cx).client()); - let mut timer = cx.background().timer(Duration::from_secs(5)).fuse(); let mut client_status = client.status(); + // this loop will terminate within client::CONNECTION_TIMEOUT seconds. 'outer: loop { - select_biased! { - _ = timer => { - return Err(anyhow!("connecting timed out")) - }, - status = client_status.recv().fuse() => { - let Some(status) = status else { - return Err(anyhow!("unexpected error reading connection status")) - }; + let Some(status) = client_status.recv().await else { + return Err(anyhow!("error connecting")); + }; - match status { - Status::Connecting | Status::Authenticating | Status::Reconnecting | Status::Reauthenticating => continue, - Status::Connected { .. } => break 'outer, - Status::SignedOut => { - if client.has_keychain_credentials(&cx) { - client.authenticate_and_connect(true, &cx).await?; - timer = cx.background().timer(Duration::from_secs(5)).fuse(); - } else { - return Err(anyhow!("not signed in")) - } - }, - Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), - | Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => return Err(anyhow!("zed is offline")) - } + match status { + Status::Connecting + | Status::Authenticating + | Status::Reconnecting + | Status::Reauthenticating => continue, + Status::Connected { .. } => break 'outer, + Status::SignedOut => return Err(anyhow!("not signed in")), + Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), + Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => { + return Err(anyhow!("zed is offline")) } } } @@ -4233,6 +4224,8 @@ async fn join_channel_internal( }) .await?; + room.update(cx, |room, cx| room.next_room_update()).await; + let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project() { return Some(join_remote_project(project, host, app_state.clone(), cx)); @@ -4255,10 +4248,10 @@ pub fn join_channel( cx: &mut AppContext, ) -> Task> { let active_call = ActiveCall::global(cx); - cx.spawn(|mut cx| { + cx.spawn(|mut cx| async move { let result = join_channel_internal( channel_id, - app_state, + &app_state, requesting_window, &active_call, &mut cx, @@ -4266,7 +4259,7 @@ pub fn join_channel( .await; // join channel succeeded, and opened a window - if Some(true) = result { + if matches!(result, Ok(true)) { return anyhow::Ok(()); } @@ -4275,28 +4268,53 @@ pub fn join_channel( } // find an existing workspace to focus and show call controls - for window in cx.windows() { - let found = window.update(&mut cx, |cx| { - let is_workspace = cx.root_view().clone().downcast::().is_some(); - if is_workspace { - cx.activate_window(); - } - is_workspace - }); + let mut active_window = activate_any_workspace_window(&mut cx); + if active_window.is_none() { + // no open workspaces, make one to show the error in (blergh) + cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) + .await; + } - if found.unwrap_or(false) { - return anyhow::Ok(()); - } + active_window = activate_any_workspace_window(&mut cx); + if active_window.is_none() { + return result.map(|_| ()); // unreachable!() assuming new_local always opens a window } - // no open workspaces - cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) - .await; + if let Err(err) = result { + let prompt = active_window.unwrap().prompt( + PromptLevel::Critical, + &format!("Failed to join channel: {}", err), + &["Ok"], + &mut cx, + ); + if let Some(mut prompt) = prompt { + prompt.next().await; + } else { + return Err(err); + } + } - return connected.map(|_| ()); + // return ok, we showed the error to the user. + return anyhow::Ok(()); }) } +pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option { + for window in cx.windows() { + let found = window.update(cx, |cx| { + let is_workspace = cx.root_view().clone().downcast::().is_some(); + if is_workspace { + cx.activate_window(); + } + is_workspace + }); + if found == Some(true) { + return Some(window); + } + } + None +} + #[allow(clippy::type_complexity)] pub fn open_paths( abs_paths: &[PathBuf], diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 861121a1cf20c4037a4110cf3246982cb059477f..52aaf639ea4c875bfcecf9aea3ffc6fb8bfe2dc2 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -8,7 +8,9 @@ use cli::{ ipc::{self, IpcSender}, CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME, }; -use client::{self, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; +use client::{ + self, Client, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN, +}; use db::kvp::KEY_VALUE_STORE; use editor::{scroll::autoscroll::Autoscroll, Editor}; use futures::{ @@ -33,7 +35,7 @@ use std::{ fs::OpenOptions, io::{IsTerminal, Write as _}, panic, - path::{Path, PathBuf}, + path::Path, sync::{ atomic::{AtomicU32, Ordering}, Arc, Weak, @@ -222,6 +224,8 @@ fn main() { } } + let mut triggered_authentication = false; + match open_rx.try_next() { Ok(Some(OpenRequest::Paths { paths })) => { cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) @@ -231,9 +235,18 @@ fn main() { cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) .detach(); } - Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx - .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) - .detach_and_log_err(cx), + Ok(Some(OpenRequest::JoinChannel { channel_id })) => { + triggered_authentication = true; + let app_state = app_state.clone(); + let client = client.clone(); + cx.spawn(|mut cx| async move { + // ignore errors here, we'll show a generic "not signed in" + let _ = authenticate(client, &cx).await; + cx.update(|cx| workspace::join_channel(channel_id, app_state, None, cx)) + .await + }) + .detach_and_log_err(cx) + } Ok(None) | Err(_) => cx .spawn({ let app_state = app_state.clone(); @@ -266,20 +279,24 @@ fn main() { }) .detach(); - cx.spawn(|cx| async move { - if stdout_is_a_pty() { - if client::IMPERSONATE_LOGIN.is_some() { - client.authenticate_and_connect(false, &cx).await?; - } - } else if client.has_keychain_credentials(&cx) { - client.authenticate_and_connect(true, &cx).await?; - } - Ok::<_, anyhow::Error>(()) - }) - .detach_and_log_err(cx); + if !triggered_authentication { + cx.spawn(|cx| async move { authenticate(client, &cx).await }) + .detach_and_log_err(cx); + } }); } +async fn authenticate(client: Arc, cx: &AsyncAppContext) -> Result<()> { + if stdout_is_a_pty() { + if client::IMPERSONATE_LOGIN.is_some() { + client.authenticate_and_connect(false, &cx).await?; + } + } else if client.has_keychain_credentials(&cx) { + client.authenticate_and_connect(true, &cx).await?; + } + Ok::<_, anyhow::Error>(()) +} + async fn installation_id() -> Result { let legacy_key_name = "device_id"; diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index 6f90953de2740617017556f803cd87a670e99c77..3e4902b9785cd08d4e38bbc14c6287936106bf8f 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -42,7 +42,6 @@ impl OpenListener { pub fn open_urls(&self, urls: Vec) { self.triggered.store(true, Ordering::Release); - dbg!(&urls); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { From f6bc229d1d4f4d46139ec268241fb69d925fceed Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 16:48:29 -0600 Subject: [PATCH 038/180] More progress and some debug logs to remove --- assets/settings/default.json | 2 +- crates/util/src/channel.rs | 6 +++--- crates/workspace/src/workspace.rs | 21 +++++++++++++++++++-- crates/zed/src/main.rs | 10 ++++++++++ crates/zed/src/open_url.rs | 13 +++++++++++++ 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 8fb73a2ecb0b8143f7e42981a71966327edd0f54..cc724657c0c20411b17d0ad0ea293b0a08ddc6f5 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -76,7 +76,7 @@ // Settings related to calls in Zed "calls": { // Join calls with the microphone muted by default - "mute_on_join": true + "mute_on_join": false }, // Scrollbar related settings "scrollbar": { diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 2364dcaad48d79272dddf9bbabbf67c0d590270a..47c6a570a1f84c4f0fb16c24df650082d5aee60e 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -44,9 +44,9 @@ impl ReleaseChannel { pub fn url_scheme(&self) -> &'static str { match self { - ReleaseChannel::Dev => "zed-dev:/", - ReleaseChannel::Preview => "zed-preview:/", - ReleaseChannel::Stable => "zed:/", + ReleaseChannel::Dev => "zed-dev://", + ReleaseChannel::Preview => "zed-preview://", + ReleaseChannel::Stable => "zed://", } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a1249173090a8af506d16efe1b8c25657b358e34..1644f86b61c25a0ebede73a133e4f805585f659f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -47,6 +47,7 @@ use std::{ any::TypeId, borrow::Cow, cmp, env, + fs::OpenOptions, future::Future, path::{Path, PathBuf}, rc::Rc, @@ -4161,6 +4162,7 @@ async fn join_channel_internal( active_call: &ModelHandle, cx: &mut AsyncAppContext, ) -> Result { + dbg!("join channel internal"); let should_prompt = active_call.read_with(cx, |active_call, cx| { let Some(room) = active_call.room().map(|room| room.read(cx)) else { return false; @@ -4193,6 +4195,7 @@ async fn join_channel_internal( return Ok(false); // unreachable!() hopefully } } + dbg!("asdajdkjkasd"); let client = cx.read(|cx| active_call.read(cx).client()); @@ -4218,13 +4221,17 @@ async fn join_channel_internal( } } + dbg!("past here"); + let room = active_call .update(cx, |active_call, cx| { active_call.join_channel(channel_id, cx) }) .await?; - room.update(cx, |room, cx| room.next_room_update()).await; + room.update(cx, |room, _| room.next_room_update()).await; + + dbg!("wow"); let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project() { @@ -4237,7 +4244,16 @@ async fn join_channel_internal( task.await?; return anyhow::Ok(true); } - + use std::io::Write; + writeln!( + OpenOptions::new() + .write(true) + .append(true) + .open("/Users/conrad/dbg") + .unwrap(), + "no jokes" + ) + .unwrap(); anyhow::Ok(false) } @@ -4257,6 +4273,7 @@ pub fn join_channel( &mut cx, ) .await; + dbg!("joined!"); // join channel succeeded, and opened a window if matches!(result, Ok(true)) { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 52aaf639ea4c875bfcecf9aea3ffc6fb8bfe2dc2..f25a1e14be0bfb6e790b7179a2ca5e0c96bd6cf3 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -66,6 +66,15 @@ use crate::open_url::{OpenListener, OpenRequest}; mod open_url; fn main() { + writeln!( + OpenOptions::new() + .write(true) + .append(true) + .open("/Users/conrad/dbg") + .unwrap(), + "HELLO" + ) + .unwrap(); let http = http::client(); init_paths(); init_logger(); @@ -270,6 +279,7 @@ fn main() { } OpenRequest::JoinChannel { channel_id } => cx .update(|cx| { + dbg!("joining channel"); workspace::join_channel(channel_id, app_state.clone(), None, cx) }) .detach(), diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index 3e4902b9785cd08d4e38bbc14c6287936106bf8f..8cad217a8c952358d9aa613e6cdc2f934fd361de 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -3,6 +3,8 @@ use cli::{ipc::IpcSender, CliRequest, CliResponse}; use futures::channel::mpsc; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; use std::ffi::OsStr; +use std::fs::OpenOptions; +use std::io::Write; use std::os::unix::prelude::OsStrExt; use std::sync::atomic::Ordering; use std::{path::PathBuf, sync::atomic::AtomicBool}; @@ -41,6 +43,16 @@ impl OpenListener { } pub fn open_urls(&self, urls: Vec) { + writeln!( + OpenOptions::new() + .write(true) + .append(true) + .open("/Users/conrad/dbg") + .unwrap(), + "{:?}", + &urls, + ) + .unwrap(); self.triggered.store(true, Ordering::Release); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) @@ -79,6 +91,7 @@ impl OpenListener { } } } + log::error!("invalid zed url: {}", request_path); None } From 6de69de868fcee8214edca76b8a09d2210885646 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 6 Oct 2023 16:04:45 -0700 Subject: [PATCH 039/180] Remove change to linker args --- .cargo/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index e22bdb0f2c70a1ffda714674253cc533e9e7c1d1..9da6b3be080072d89d16a199e2d60d527eeacd07 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] +rustflags = ["-C", "symbol-mangling-version=v0"] From 66120fb97a3dca9292a3c978152246f675198adb Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 22:24:41 -0600 Subject: [PATCH 040/180] Try universal link entitlement too --- crates/zed/resources/zed.entitlements | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/zed/resources/zed.entitlements b/crates/zed/resources/zed.entitlements index dca0ff676633b9bac7b69114c416536f99262599..9f5414243de13b87e419849ab63c68eb45ab1ce0 100644 --- a/crates/zed/resources/zed.entitlements +++ b/crates/zed/resources/zed.entitlements @@ -2,6 +2,8 @@ + com.apple.developer.associated-domains + applinks:cirw.in com.apple.security.automation.apple-events com.apple.security.cs.allow-jit From 34b75379485f82f531cba5325997a5e42600a379 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 23:15:37 -0600 Subject: [PATCH 041/180] Add universal links support to mac platform --- crates/gpui/src/platform/mac/platform.rs | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 656f2e44750641d752fce2ff4f639cce59601c25..47ad72eeb233f7813af1067fccb616ca002d72c7 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -140,6 +140,10 @@ unsafe fn build_classes() { sel!(application:openURLs:), open_urls as extern "C" fn(&mut Object, Sel, id, id), ); + decl.add_method( + sel!(application:continueUserActivity:restorationHandler:), + continue_user_activity as extern "C" fn(&mut Object, Sel, id, id, id), + ); decl.register() } } @@ -1009,6 +1013,28 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { } } +extern "C" fn continue_user_activity(this: &mut Object, _: Sel, _: id, user_activity: id, _: id) { + dbg!("yay!"); + let url = unsafe { + let url: id = msg_send!(user_activity, webpageURL); + if url == nil { + log::error!("got unexpected user activity"); + None + } else { + Some( + CStr::from_ptr(url.absoluteString().UTF8String()) + .to_string_lossy() + .to_string(), + ) + } + }; + dbg!(&url); + let platform = unsafe { get_foreground_platform(this) }; + if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() { + callback(url.into_iter().collect()); + } +} + extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { unsafe { let platform = get_foreground_platform(this); From 5dbda7023517b60720d250e4109c8bb08f7c6da7 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 08:59:25 -0600 Subject: [PATCH 042/180] Fix ./script/bundle to allow passing key --- crates/client/src/client.rs | 1 - crates/gpui/src/platform/mac/platform.rs | 2 -- crates/workspace/src/workspace.rs | 7 ------- crates/zed/src/main.rs | 1 - script/bundle | 10 ++++++++-- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 62cd60c55fd339965232448d0a306cc3dd457ff4..22e8bc06d4e430ae922d44ca4a3dc87948ebf6cd 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -799,7 +799,6 @@ impl Client { } } status = status_rx.next().fuse() => { - dbg!(status); return Err(anyhow!("authentication canceled")); } } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 47ad72eeb233f7813af1067fccb616ca002d72c7..24ad7747590d9c218ec9b86e99e248f3ff6d3cb7 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -1014,7 +1014,6 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { } extern "C" fn continue_user_activity(this: &mut Object, _: Sel, _: id, user_activity: id, _: id) { - dbg!("yay!"); let url = unsafe { let url: id = msg_send!(user_activity, webpageURL); if url == nil { @@ -1028,7 +1027,6 @@ extern "C" fn continue_user_activity(this: &mut Object, _: Sel, _: id, user_acti ) } }; - dbg!(&url); let platform = unsafe { get_foreground_platform(this) }; if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() { callback(url.into_iter().collect()); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1644f86b61c25a0ebede73a133e4f805585f659f..7773abc19cc379402cd875acd12bc7e6f79098db 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4162,7 +4162,6 @@ async fn join_channel_internal( active_call: &ModelHandle, cx: &mut AsyncAppContext, ) -> Result { - dbg!("join channel internal"); let should_prompt = active_call.read_with(cx, |active_call, cx| { let Some(room) = active_call.room().map(|room| room.read(cx)) else { return false; @@ -4195,7 +4194,6 @@ async fn join_channel_internal( return Ok(false); // unreachable!() hopefully } } - dbg!("asdajdkjkasd"); let client = cx.read(|cx| active_call.read(cx).client()); @@ -4221,8 +4219,6 @@ async fn join_channel_internal( } } - dbg!("past here"); - let room = active_call .update(cx, |active_call, cx| { active_call.join_channel(channel_id, cx) @@ -4231,8 +4227,6 @@ async fn join_channel_internal( room.update(cx, |room, _| room.next_room_update()).await; - dbg!("wow"); - let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project() { return Some(join_remote_project(project, host, app_state.clone(), cx)); @@ -4273,7 +4267,6 @@ pub fn join_channel( &mut cx, ) .await; - dbg!("joined!"); // join channel succeeded, and opened a window if matches!(result, Ok(true)) { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f25a1e14be0bfb6e790b7179a2ca5e0c96bd6cf3..004e5769d57666d1df0fb8e972aa445ddecf7980 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -279,7 +279,6 @@ fn main() { } OpenRequest::JoinChannel { channel_id } => cx .update(|cx| { - dbg!("joining channel"); workspace::join_channel(channel_id, app_state.clone(), None, cx) }) .detach(), diff --git a/script/bundle b/script/bundle index 94741d290fc95839362de6c256b41431f216d7e3..a1d0b305c8fc3b4732c3dac7a4722f3ae3838ae9 100755 --- a/script/bundle +++ b/script/bundle @@ -128,7 +128,11 @@ fi echo "Copying WebRTC.framework into the frameworks folder" mkdir "${app_path}/Contents/Frameworks" -cp -R target/${local_target_triple}/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" +if [ "$local_arch" = false ]; then + cp -R target/${local_target_triple}/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" +else + cp -R target/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" +fi if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then echo "Signing bundle with Apple-issued certificate" @@ -144,7 +148,9 @@ if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTAR else echo "One or more of the following variables are missing: MACOS_CERTIFICATE, MACOS_CERTIFICATE_PASSWORD, APPLE_NOTARIZATION_USERNAME, APPLE_NOTARIZATION_PASSWORD" echo "Performing an ad-hoc signature, but this bundle should not be distributed" - codesign --force --deep --entitlements crates/zed/resources/zed.entitlements --sign - "${app_path}" -v + echo "If you see 'The application cannot be opened for an unexpected reason,' you likely don't have the necessary entitlements to run the application in your signing keychain" + echo "You will need to download a new signing key from developer.apple.com, add it to keychain, and export MACOS_SIGNING_KEY=" + codesign --force --deep --entitlements crates/zed/resources/zed.entitlements --sign ${MACOS_SIGNING_KEY:- -} "${app_path}" -v fi if [[ "$target_dir" = "debug" && "$local_only" = false ]]; then From 8f4d81903c6b0322c7026c04b0df2830fbc02f10 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 09:28:49 -0600 Subject: [PATCH 043/180] Add "Copy Link" to channel right click menu --- crates/channel/src/channel_store.rs | 21 +++++++++++++++++++++ crates/collab_ui/src/collab_panel.rs | 27 +++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index bd72c92c7db768c558f7bc1b39a371f01f5dfd6c..cd2153ad5c56658a1d80eb6543200069ef63314c 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -4,6 +4,7 @@ use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat}; use anyhow::{anyhow, Result}; use client::{Client, Subscription, User, UserId, UserStore}; use collections::{hash_map, HashMap, HashSet}; +use db::RELEASE_CHANNEL; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle}; use rpc::{ @@ -47,6 +48,26 @@ pub struct Channel { pub unseen_message_id: Option, } +impl Channel { + pub fn link(&self) -> String { + RELEASE_CHANNEL.link_prefix().to_owned() + + "channel/" + + &self.slug() + + "-" + + &self.id.to_string() + } + + pub fn slug(&self) -> String { + let slug: String = self + .name + .chars() + .map(|c| if c.is_alphanumeric() { c } else { '-' }) + .collect(); + + slug.trim_matches(|c| c == '-').to_string() + } +} + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)] pub struct ChannelPath(Arc<[ChannelId]>); diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 3d66e8450ae0435bdc8d69908b43402992575458..9ec05e07c81fe0b48e19cf567f4ff137e62d527c 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -34,8 +34,8 @@ use gpui::{ }, impl_actions, platform::{CursorStyle, MouseButton, PromptLevel}, - serde_json, AnyElement, AppContext, AsyncAppContext, Element, Entity, FontCache, ModelHandle, - Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, + serde_json, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, FontCache, + ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Fs, Project}; @@ -100,6 +100,11 @@ pub struct JoinChannelChat { pub channel_id: u64, } +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct CopyChannelLink { + pub channel_id: u64, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] struct StartMoveChannelFor { channel_id: ChannelId, @@ -157,6 +162,7 @@ impl_actions!( OpenChannelNotes, JoinChannelCall, JoinChannelChat, + CopyChannelLink, LinkChannel, StartMoveChannelFor, StartLinkChannelFor, @@ -205,6 +211,7 @@ pub fn init(cx: &mut AppContext) { cx.add_action(CollabPanel::expand_selected_channel); cx.add_action(CollabPanel::open_channel_notes); cx.add_action(CollabPanel::join_channel_chat); + cx.add_action(CollabPanel::copy_channel_link); cx.add_action( |panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext| { @@ -2571,6 +2578,13 @@ impl CollabPanel { }, )); + items.push(ContextMenuItem::action( + "Copy Channel Link", + CopyChannelLink { + channel_id: path.channel_id(), + }, + )); + if self.channel_store.read(cx).is_user_admin(path.channel_id()) { let parent_id = path.parent_id(); @@ -3219,6 +3233,15 @@ impl CollabPanel { }); } } + + fn copy_channel_link(&mut self, action: &CopyChannelLink, cx: &mut ViewContext) { + let channel_store = self.channel_store.read(cx); + let Some(channel) = channel_store.channel_for_id(action.channel_id) else { + return; + }; + let item = ClipboardItem::new(channel.link()); + cx.write_to_clipboard(item) + } } fn render_tree_branch( From 6084486dcdb98685cdc589cff385c64a5a63cb7e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 09:34:18 -0600 Subject: [PATCH 044/180] Code quality --- crates/client/src/client.rs | 2 +- crates/collab_ui/src/collab_panel.rs | 27 ++++++++++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 22e8bc06d4e430ae922d44ca4a3dc87948ebf6cd..5767ac54b7893f7425dfd56202b7512d17314f0f 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -798,7 +798,7 @@ impl Client { } } } - status = status_rx.next().fuse() => { + _ = status_rx.next().fuse() => { return Err(anyhow!("authentication canceled")); } } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 9ec05e07c81fe0b48e19cf567f4ff137e62d527c..8a8fde88eeee92985a13b37291b0b428542ebb01 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -1976,21 +1976,18 @@ impl CollabPanel { let style = collab_theme.channel_name.inactive_state(); Flex::row() .with_child( - Label::new( - channel.name.clone().to_owned() + channel_id.to_string().as_str(), - style.text.clone(), - ) - .contained() - .with_style(style.container) - .aligned() - .left() - .with_tooltip::( - ix, - "Join channel", - None, - theme.tooltip.clone(), - cx, - ), + Label::new(channel.name.clone(), style.text.clone()) + .contained() + .with_style(style.container) + .aligned() + .left() + .with_tooltip::( + ix, + "Join channel", + None, + theme.tooltip.clone(), + cx, + ), ) .with_children({ let participants = From abfb4490d590f33ffefce2d945b540b60cad4d99 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 12:05:26 -0600 Subject: [PATCH 045/180] Focus the currently active project if there is one (also consider your own projects in "most_active_projects") --- crates/call/src/room.rs | 9 ++++++++- crates/workspace/src/workspace.rs | 31 ++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 5419e00f02e093419862d0c7cbaf4dab70c7b0f0..b7aeee90e20300c916db50a12ccd5dcc6d06b334 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -605,7 +605,7 @@ impl Room { } /// Returns the most 'active' projects, defined as most people in the project - pub fn most_active_project(&self) -> Option<(u64, u64)> { + pub fn most_active_project(&self, cx: &AppContext) -> Option<(u64, u64)> { let mut projects = HashMap::default(); let mut hosts = HashMap::default(); @@ -622,6 +622,13 @@ impl Room { } } + if let Some(user) = self.user_store.read(cx).current_user() { + for project in &self.local_participant.projects { + *projects.entry(project.id).or_insert(0) += 1; + hosts.insert(project.id, user.id); + } + } + let mut pairs: Vec<(u64, usize)> = projects.into_iter().collect(); pairs.sort_by_key(|(_, count)| *count as i32); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7773abc19cc379402cd875acd12bc7e6f79098db..e690757af71748d66d688510c5fd515c9d8a1343 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4162,16 +4162,37 @@ async fn join_channel_internal( active_call: &ModelHandle, cx: &mut AsyncAppContext, ) -> Result { - let should_prompt = active_call.read_with(cx, |active_call, cx| { + let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| { let Some(room) = active_call.room().map(|room| room.read(cx)) else { - return false; + return (false, None); }; - room.is_sharing_project() + let already_in_channel = room.channel_id() == Some(channel_id); + let should_prompt = room.is_sharing_project() && room.remote_participants().len() > 0 - && room.channel_id() != Some(channel_id) + && !already_in_channel; + let open_room = if already_in_channel { + active_call.room().cloned() + } else { + None + }; + (should_prompt, open_room) }); + if let Some(room) = open_room { + let task = room.update(cx, |room, cx| { + if let Some((project, host)) = room.most_active_project(cx) { + return Some(join_remote_project(project, host, app_state.clone(), cx)); + } + + None + }); + if let Some(task) = task { + task.await?; + } + return anyhow::Ok(true); + } + if should_prompt { if let Some(workspace) = requesting_window { if let Some(window) = workspace.update(cx, |cx| cx.window()) { @@ -4228,7 +4249,7 @@ async fn join_channel_internal( room.update(cx, |room, _| room.next_room_update()).await; let task = room.update(cx, |room, cx| { - if let Some((project, host)) = room.most_active_project() { + if let Some((project, host)) = room.most_active_project(cx) { return Some(join_remote_project(project, host, app_state.clone(), cx)); } From 7020050b069474ab9145cef6578a95d68f217f43 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 9 Oct 2023 14:28:53 -0400 Subject: [PATCH 046/180] Fix `hover_popover.rs` after bad rebase --- crates/editor/src/editor.rs | 35 ++------ crates/editor/src/hover_popover.rs | 128 +++++++++++++---------------- crates/language/src/markdown.rs | 31 ++++++- 3 files changed, 92 insertions(+), 102 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6585611040f3b93666fd6341531892a40b99b15b..b8c9690b905d9b09e6aa783d13dc73a130bf6473 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,7 +60,6 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, - markdown::MarkdownHighlight, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, @@ -139,45 +138,21 @@ pub fn render_parsed_markdown( .highlights .iter() .filter_map(|(range, highlight)| { - let highlight = match highlight { - MarkdownHighlight::Style(style) => { - let mut highlight = HighlightStyle::default(); - - if style.italic { - highlight.italic = Some(true); - } - - if style.underline { - highlight.underline = Some(fonts::Underline { - thickness: 1.0.into(), - ..Default::default() - }); - } - - if style.weight != fonts::Weight::default() { - highlight.weight = Some(style.weight); - } - - highlight - } - - MarkdownHighlight::Code(id) => id.style(&editor_style.syntax)?, - }; - + let highlight = highlight.to_highlight_style(&editor_style.syntax)?; Some((range.clone(), highlight)) }) .collect::>(), ) - .with_custom_runs(parsed.region_ranges, move |ix, bounds, scene, _| { + .with_custom_runs(parsed.region_ranges, move |ix, bounds, cx| { region_id += 1; let region = parsed.regions[ix].clone(); if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { + cx.scene().push_cursor_region(CursorRegion { bounds, style: CursorStyle::PointingHand, }); - scene.push_mouse_region( + cx.scene().push_mouse_region( MouseRegion::new::(view_id, region_id, bounds) .on_click::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) @@ -186,7 +161,7 @@ pub fn render_parsed_markdown( } if region.code { - scene.push_quad(gpui::Quad { + cx.scene().push_quad(gpui::Quad { bounds, background: Some(code_span_background_color), border: Default::default(), diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 7917d5786574201831ce1ff83ab0398841a994f7..d5ccb481b2f9a21ffb3aca34a728f908f3ab8ce3 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,6 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, - link_go_to_definition::{DocumentRange, InlayRange}, + link_go_to_definition::{InlayHighlight, RangeInEditor}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -51,19 +51,18 @@ pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewC pub struct InlayHover { pub excerpt: ExcerptId, - pub triggered_from: InlayOffset, - pub range: InlayRange, + pub range: InlayHighlight, pub tooltip: HoverBlock, } pub fn find_hovered_hint_part( label_parts: Vec, - hint_range: Range, + hint_start: InlayOffset, hovered_offset: InlayOffset, ) -> Option<(InlayHintLabelPart, Range)> { - if hovered_offset >= hint_range.start && hovered_offset <= hint_range.end { - let mut hovered_character = (hovered_offset - hint_range.start).0; - let mut part_start = hint_range.start; + if hovered_offset >= hint_start { + let mut hovered_character = (hovered_offset - hint_start).0; + let mut part_start = hint_start; for part in label_parts { let part_len = part.value.chars().count(); if hovered_character > part_len { @@ -89,10 +88,8 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie }; if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { - if let DocumentRange::Inlay(range) = symbol_range { - if (range.highlight_start..range.highlight_end) - .contains(&inlay_hover.triggered_from) - { + if let RangeInEditor::Inlay(range) = symbol_range { + if range == &inlay_hover.range { // Hover triggered from same location as last time. Don't show again. return; } @@ -100,18 +97,6 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie hide_hover(editor, cx); } - let snapshot = editor.snapshot(cx); - // Don't request again if the location is the same as the previous request - if let Some(triggered_from) = editor.hover_state.triggered_from { - if inlay_hover.triggered_from - == snapshot - .display_snapshot - .anchor_to_inlay_offset(triggered_from) - { - return; - } - } - let task = cx.spawn(|this, mut cx| { async move { cx.background() @@ -127,7 +112,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie let hover_popover = InfoPopover { project: project.clone(), - symbol_range: DocumentRange::Inlay(inlay_hover.range), + symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), blocks, parsed_content, }; @@ -331,7 +316,7 @@ fn show_hover( Some(InfoPopover { project: project.clone(), - symbol_range: DocumentRange::Text(range), + symbol_range: RangeInEditor::Text(range), blocks, parsed_content, }) @@ -449,8 +434,8 @@ impl HoverState { self.info_popover .as_ref() .map(|info_popover| match &info_popover.symbol_range { - DocumentRange::Text(range) => &range.start, - DocumentRange::Inlay(range) => &range.inlay_position, + RangeInEditor::Text(range) => &range.start, + RangeInEditor::Inlay(range) => &range.inlay_position, }) })?; let point = anchor.to_display_point(&snapshot.display_snapshot); @@ -476,7 +461,7 @@ impl HoverState { #[derive(Debug, Clone)] pub struct InfoPopover { pub project: ModelHandle, - symbol_range: DocumentRange, + symbol_range: RangeInEditor, pub blocks: Vec, parsed_content: ParsedMarkdown, } @@ -587,14 +572,12 @@ mod tests { inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, link_go_to_definition::update_inlay_link_and_hover_points, test::editor_lsp_test_context::EditorLspTestContext, + InlayId, }; use collections::BTreeSet; - use gpui::fonts::Weight; + use gpui::fonts::{HighlightStyle, Underline, Weight}; use indoc::indoc; - use language::{ - language_settings::InlayHintSettings, markdown::MarkdownHighlightStyle, Diagnostic, - DiagnosticSet, - }; + use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; use lsp::LanguageServerId; use project::{HoverBlock, HoverBlockKind}; use smol::stream::StreamExt; @@ -896,17 +879,16 @@ mod tests { #[gpui::test] fn test_render_blocks(cx: &mut gpui::TestAppContext) { - use markdown::MarkdownHighlight; - init_test(cx, |_| {}); cx.add_window(|cx| { let editor = Editor::single_line(None, cx); + let style = editor.style(cx); struct Row { blocks: Vec, expected_marked_text: String, - expected_styles: Vec, + expected_styles: Vec, } let rows = &[ @@ -917,10 +899,10 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - weight: Weight::BOLD, + expected_styles: vec![HighlightStyle { + weight: Some(Weight::BOLD), ..Default::default() - })], + }], }, // Links Row { @@ -929,10 +911,13 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - underline: true, + expected_styles: vec![HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), ..Default::default() - })], + }], }, // Lists Row { @@ -957,10 +942,13 @@ mod tests { - «c» - d" .unindent(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - underline: true, + expected_styles: vec![HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), ..Default::default() - })], + }], }, // Multi-paragraph list items Row { @@ -988,10 +976,13 @@ mod tests { - ten - six" .unindent(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - underline: true, + expected_styles: vec![HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), ..Default::default() - })], + }], }, ]; @@ -1012,8 +1003,18 @@ mod tests { rendered.text, expected_text, "wrong text for input {blocks:?}" ); + + let rendered_highlights: Vec<_> = rendered + .highlights + .iter() + .filter_map(|(range, highlight)| { + let highlight = highlight.to_highlight_style(&style.syntax)?; + Some((range.clone(), highlight)) + }) + .collect(); + assert_eq!( - rendered.highlights, expected_highlights, + rendered_highlights, expected_highlights, "wrong highlights for input {blocks:?}" ); } @@ -1247,25 +1248,16 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); - let entire_inlay_start = snapshot.display_point_to_inlay_offset( - inlay_range.start.to_display_point(&snapshot), - Bias::Left, - ); - - let expected_new_type_label_start = InlayOffset(entire_inlay_start.0 + ": ".len()); assert_eq!( popover.symbol_range, - DocumentRange::Inlay(InlayRange { + RangeInEditor::Inlay(InlayHighlight { + inlay: InlayId::Hint(0), inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - highlight_start: expected_new_type_label_start, - highlight_end: InlayOffset( - expected_new_type_label_start.0 + new_type_label.len() - ), + range: ": ".len()..": ".len() + new_type_label.len(), }), "Popover range should match the new type label part" ); @@ -1309,23 +1301,17 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); - let entire_inlay_start = snapshot.display_point_to_inlay_offset( - inlay_range.start.to_display_point(&snapshot), - Bias::Left, - ); - let expected_struct_label_start = - InlayOffset(entire_inlay_start.0 + ": ".len() + new_type_label.len() + "<".len()); assert_eq!( popover.symbol_range, - DocumentRange::Inlay(InlayRange { + RangeInEditor::Inlay(InlayHighlight { + inlay: InlayId::Hint(0), inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - highlight_start: expected_struct_label_start, - highlight_end: InlayOffset(expected_struct_label_start.0 + struct_label.len()), + range: ": ".len() + new_type_label.len() + "<".len() + ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(), }), "Popover range should match the struct label part" ); diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index 9f29e7cb885765ae6d04e74b065e0619323e0051..8be15e81f614d699eaefd88e10c75e6e270cb53f 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -2,7 +2,7 @@ use std::ops::Range; use std::sync::Arc; use crate::{HighlightId, Language, LanguageRegistry}; -use gpui::fonts::Weight; +use gpui::fonts::{self, HighlightStyle, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] @@ -19,6 +19,35 @@ pub enum MarkdownHighlight { Code(HighlightId), } +impl MarkdownHighlight { + pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option { + match self { + MarkdownHighlight::Style(style) => { + let mut highlight = HighlightStyle::default(); + + if style.italic { + highlight.italic = Some(true); + } + + if style.underline { + highlight.underline = Some(fonts::Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style.weight != fonts::Weight::default() { + highlight.weight = Some(style.weight); + } + + Some(highlight) + } + + MarkdownHighlight::Code(id) => id.style(theme), + } + } +} + #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct MarkdownHighlightStyle { pub italic: bool, From 162cb19cff1efaaa127dd7619a5351facc545c1c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 12:59:18 -0600 Subject: [PATCH 047/180] Only allow one release channel in a call --- .../20221109000000_test_schema.sql | 1 + ...009181554_add_release_channel_to_rooms.sql | 1 + crates/collab/src/db/queries/rooms.rs | 32 +++++-- crates/collab/src/db/tables/room.rs | 1 + crates/collab/src/db/tests.rs | 2 + crates/collab/src/db/tests/channel_tests.rs | 20 +++- crates/collab/src/db/tests/db_tests.rs | 92 ++++++++++++++++++- crates/collab/src/rpc.rs | 22 ++++- 8 files changed, 152 insertions(+), 19 deletions(-) create mode 100644 crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 2d963ff15fa4717aa7faee092356f4b06d8a5814..a9b8d9709defb861c598239abbb3aa62b4ff71b7 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -37,6 +37,7 @@ CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b"); CREATE TABLE "rooms" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "live_kit_room" VARCHAR NOT NULL, + "release_channel" VARCHAR, "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE ); diff --git a/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql new file mode 100644 index 0000000000000000000000000000000000000000..95d3c400fcf6fc309fe81a690cf2ed3a16f5017a --- /dev/null +++ b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql @@ -0,0 +1 @@ +ALTER TABLE rooms ADD COLUMN release_channel TEXT; diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index b103ae1c737cfdd977418d528585c6fdd9ebb4b7..6589f23791d792d4144441d92cd25d08d088f231 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -107,10 +107,12 @@ impl Database { user_id: UserId, connection: ConnectionId, live_kit_room: &str, + release_channel: &str, ) -> Result { self.transaction(|tx| async move { let room = room::ActiveModel { live_kit_room: ActiveValue::set(live_kit_room.into()), + release_channel: ActiveValue::set(Some(release_channel.to_string())), ..Default::default() } .insert(&*tx) @@ -270,20 +272,31 @@ impl Database { room_id: RoomId, user_id: UserId, connection: ConnectionId, + collab_release_channel: &str, ) -> Result> { self.room_transaction(room_id, |tx| async move { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] - enum QueryChannelId { + enum QueryChannelIdAndReleaseChannel { ChannelId, + ReleaseChannel, + } + + let (channel_id, release_channel): (Option, Option) = + room::Entity::find() + .select_only() + .column(room::Column::ChannelId) + .column(room::Column::ReleaseChannel) + .filter(room::Column::Id.eq(room_id)) + .into_values::<_, QueryChannelIdAndReleaseChannel>() + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such room"))?; + + if let Some(release_channel) = release_channel { + if &release_channel != collab_release_channel { + Err(anyhow!("must join using the {} release", release_channel))?; + } } - let channel_id: Option = room::Entity::find() - .select_only() - .column(room::Column::ChannelId) - .filter(room::Column::Id.eq(room_id)) - .into_values::<_, QueryChannelId>() - .one(&*tx) - .await? - .ok_or_else(|| anyhow!("no such room"))?; #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] enum QueryParticipantIndices { @@ -300,6 +313,7 @@ impl Database { .into_values::<_, QueryParticipantIndices>() .all(&*tx) .await?; + let mut participant_index = 0; while existing_participant_indices.contains(&participant_index) { participant_index += 1; diff --git a/crates/collab/src/db/tables/room.rs b/crates/collab/src/db/tables/room.rs index f72f7000a783570d2cac4f6aebc3cf68846ab3a3..7f31edcdbd96bf453271895b63636625f7d566db 100644 --- a/crates/collab/src/db/tables/room.rs +++ b/crates/collab/src/db/tables/room.rs @@ -8,6 +8,7 @@ pub struct Model { pub id: RoomId, pub live_kit_room: String, pub channel_id: Option, + pub release_channel: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index 75584ff90b68cf4fea0b4151a835c77e970daae5..6a91fd6ffe145c1a31f9b5264029a04ddb1ef1de 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -12,6 +12,8 @@ use sea_orm::ConnectionTrait; use sqlx::migrate::MigrateDatabase; use std::sync::Arc; +const TEST_RELEASE_CHANNEL: &'static str = "test"; + pub struct TestDb { pub db: Option>, pub connection: Option, diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 429852d12870a232da68d165d75de68d3a7b9be0..2631e0d19184969450de53c001a9f4595e6252dc 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -5,7 +5,11 @@ use rpc::{ }; use crate::{ - db::{queries::channels::ChannelGraph, tests::graph, ChannelId, Database, NewUserParams}, + db::{ + queries::channels::ChannelGraph, + tests::{graph, TEST_RELEASE_CHANNEL}, + ChannelId, Database, NewUserParams, + }, test_both_dbs, }; use std::sync::Arc; @@ -206,7 +210,12 @@ async fn test_joining_channels(db: &Arc) { // can join a room with membership to its channel let joined_room = db - .join_room(room_1, user_1, ConnectionId { owner_id, id: 1 }) + .join_room( + room_1, + user_1, + ConnectionId { owner_id, id: 1 }, + TEST_RELEASE_CHANNEL, + ) .await .unwrap(); assert_eq!(joined_room.room.participants.len(), 1); @@ -214,7 +223,12 @@ async fn test_joining_channels(db: &Arc) { drop(joined_room); // cannot join a room without membership to its channel assert!(db - .join_room(room_1, user_2, ConnectionId { owner_id, id: 1 }) + .join_room( + room_1, + user_2, + ConnectionId { owner_id, id: 1 }, + TEST_RELEASE_CHANNEL + ) .await .is_err()); } diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index 9a617166fead82ca5b538b84ec268329f1f8de22..1520e081c07ead1afc376f84d2e12918fef40db2 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -479,7 +479,7 @@ async fn test_project_count(db: &Arc) { .unwrap(); let room_id = RoomId::from_proto( - db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "") + db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "", "dev") .await .unwrap() .id, @@ -493,9 +493,14 @@ async fn test_project_count(db: &Arc) { ) .await .unwrap(); - db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 }) - .await - .unwrap(); + db.join_room( + room_id, + user2.user_id, + ConnectionId { owner_id, id: 1 }, + "dev", + ) + .await + .unwrap(); assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0); db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[]) @@ -575,6 +580,85 @@ async fn test_fuzzy_search_users() { } } +test_both_dbs!( + test_non_matching_release_channels, + test_non_matching_release_channels_postgres, + test_non_matching_release_channels_sqlite +); + +async fn test_non_matching_release_channels(db: &Arc) { + let owner_id = db.create_server("test").await.unwrap().0 as u32; + + let user1 = db + .create_user( + &format!("admin@example.com"), + true, + NewUserParams { + github_login: "admin".into(), + github_user_id: 0, + invite_count: 0, + }, + ) + .await + .unwrap(); + let user2 = db + .create_user( + &format!("user@example.com"), + false, + NewUserParams { + github_login: "user".into(), + github_user_id: 1, + invite_count: 0, + }, + ) + .await + .unwrap(); + + let room = db + .create_room( + user1.user_id, + ConnectionId { owner_id, id: 0 }, + "", + "stable", + ) + .await + .unwrap(); + + db.call( + RoomId::from_proto(room.id), + user1.user_id, + ConnectionId { owner_id, id: 0 }, + user2.user_id, + None, + ) + .await + .unwrap(); + + // User attempts to join from preview + let result = db + .join_room( + RoomId::from_proto(room.id), + user2.user_id, + ConnectionId { owner_id, id: 1 }, + "preview", + ) + .await; + + assert!(result.is_err()); + + // User switches to stable + let result = db + .join_room( + RoomId::from_proto(room.id), + user2.user_id, + ConnectionId { owner_id, id: 1 }, + "stable", + ) + .await; + + assert!(result.is_ok()) +} + fn build_background_executor() -> Arc { Deterministic::new(0).build_background() } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 6171803341b2e3bb47d890a0882f786a0b28aa6a..70052468d45e967f63ad978d7240eda0f24af777 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -63,6 +63,7 @@ use time::OffsetDateTime; use tokio::sync::{watch, Semaphore}; use tower::ServiceBuilder; use tracing::{info_span, instrument, Instrument}; +use util::channel::RELEASE_CHANNEL_NAME; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); @@ -957,7 +958,12 @@ async fn create_room( let room = session .db() .await - .create_room(session.user_id, session.connection_id, &live_kit_room) + .create_room( + session.user_id, + session.connection_id, + &live_kit_room, + RELEASE_CHANNEL_NAME.as_str(), + ) .await?; response.send(proto::CreateRoomResponse { @@ -979,7 +985,12 @@ async fn join_room( let room = session .db() .await - .join_room(room_id, session.user_id, session.connection_id) + .join_room( + room_id, + session.user_id, + session.connection_id, + RELEASE_CHANNEL_NAME.as_str(), + ) .await?; room_updated(&room.room, &session.peer); room.into_inner() @@ -2616,7 +2627,12 @@ async fn join_channel( let room_id = db.room_id_for_channel(channel_id).await?; let joined_room = db - .join_room(room_id, session.user_id, session.connection_id) + .join_room( + room_id, + session.user_id, + session.connection_id, + RELEASE_CHANNEL_NAME.as_str(), + ) .await?; let live_kit_connection_info = session.live_kit_client.as_ref().and_then(|live_kit| { From 8922437fcd0ae13ab6e3ba23cdac071ff20e7f2a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 18:13:52 -0600 Subject: [PATCH 049/180] code review --- crates/call/src/room.rs | 2 +- crates/workspace/src/workspace.rs | 14 ++------------ crates/zed/Cargo.toml | 2 +- crates/zed/resources/zed.entitlements | 12 ++++++++---- crates/zed/src/main.rs | 13 ++----------- crates/zed/src/{open_url.rs => open_listener.rs} | 10 ---------- 6 files changed, 14 insertions(+), 39 deletions(-) rename crates/zed/src/{open_url.rs => open_listener.rs} (92%) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index b7aeee90e20300c916db50a12ccd5dcc6d06b334..32b8232b4f76f73eb35d5f45d7d5a902f2d50a18 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -878,7 +878,7 @@ impl Room { Ok(()) } - pub fn next_room_update(&mut self) -> impl Future { + pub fn room_update_completed(&mut self) -> impl Future { let mut done_rx = self.room_update_completed_rx.clone(); async move { while let Some(result) = done_rx.next().await { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e690757af71748d66d688510c5fd515c9d8a1343..c021384d91067fd52e649211760eb732079e3b8c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -47,7 +47,6 @@ use std::{ any::TypeId, borrow::Cow, cmp, env, - fs::OpenOptions, future::Future, path::{Path, PathBuf}, rc::Rc, @@ -4246,7 +4245,8 @@ async fn join_channel_internal( }) .await?; - room.update(cx, |room, _| room.next_room_update()).await; + room.update(cx, |room, _| room.room_update_completed()) + .await; let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project(cx) { @@ -4259,16 +4259,6 @@ async fn join_channel_internal( task.await?; return anyhow::Ok(true); } - use std::io::Write; - writeln!( - OpenOptions::new() - .write(true) - .append(true) - .open("/Users/conrad/dbg") - .unwrap(), - "no jokes" - ) - .unwrap(); anyhow::Ok(false) } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 3c93462d4b9182fb362d623455d52c9212d8cc9d..7eb14559be8ab64e44eabf55eaf8837338473d06 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -156,7 +156,7 @@ workspace = { path = "../workspace", features = ["test-support"] } unindent.workspace = true -[package.metadata.bundle] +[package.metadata.bundle-dev] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] identifier = "dev.zed.Zed-Dev" name = "Zed Dev" diff --git a/crates/zed/resources/zed.entitlements b/crates/zed/resources/zed.entitlements index 9f5414243de13b87e419849ab63c68eb45ab1ce0..f40a8a253ad082bc08e48ed8684b2d553d5065e8 100644 --- a/crates/zed/resources/zed.entitlements +++ b/crates/zed/resources/zed.entitlements @@ -2,8 +2,6 @@ - com.apple.developer.associated-domains - applinks:cirw.in com.apple.security.automation.apple-events com.apple.security.cs.allow-jit @@ -12,8 +10,14 @@ com.apple.security.device.camera - com.apple.security.keychain-access-groups - MQ55VZLNZQ.dev.zed.Shared + com.apple.security.personal-information.addressbook + + com.apple.security.personal-information.calendars + + com.apple.security.personal-information.location + + com.apple.security.personal-information.photos-library + diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 004e5769d57666d1df0fb8e972aa445ddecf7980..a75caa54f6eb3a8240da736cb376e7e653da9158 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -61,20 +61,11 @@ use zed::{ only_instance::{ensure_only_instance, IsOnlyInstance}, }; -use crate::open_url::{OpenListener, OpenRequest}; +use crate::open_listener::{OpenListener, OpenRequest}; -mod open_url; +mod open_listener; fn main() { - writeln!( - OpenOptions::new() - .write(true) - .append(true) - .open("/Users/conrad/dbg") - .unwrap(), - "HELLO" - ) - .unwrap(); let http = http::client(); init_paths(); init_logger(); diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_listener.rs similarity index 92% rename from crates/zed/src/open_url.rs rename to crates/zed/src/open_listener.rs index 8cad217a8c952358d9aa613e6cdc2f934fd361de..e3c08ff2c8e6aebdcf96af4b18d7751faf0ffc44 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_listener.rs @@ -43,16 +43,6 @@ impl OpenListener { } pub fn open_urls(&self, urls: Vec) { - writeln!( - OpenOptions::new() - .write(true) - .append(true) - .open("/Users/conrad/dbg") - .unwrap(), - "{:?}", - &urls, - ) - .unwrap(); self.triggered.store(true, Ordering::Release); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) From dcdd74dff43b421acb2526a6c9592cde7ad78cf0 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 10 Oct 2023 00:00:57 -0400 Subject: [PATCH 050/180] Truncate Discord release note text --- .github/workflows/release_actions.yml | 29 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index f767324e4f6e2bc2edc825af512bbb5561ac6b62..54e5d07787faf6e849b0852ec01a7b404215502e 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -10,19 +10,30 @@ jobs: id: get-appropriate-url run: | if [ "${{ github.event.release.prerelease }}" == "true" ]; then - URL="https://zed.dev/releases/preview/latest" + url="https://zed.dev/releases/preview/latest" else - URL="https://zed.dev/releases/stable/latest" + url="https://zed.dev/releases/stable/latest" fi - echo "::set-output name=URL::$URL" + echo "::set-output name=url::$url" + + - name: Prepare release content + id: prepare-content + run: | + set -eu + + text="📣 Zed ${{ github.event.release.tag_name }} was just released!\n\nRestart your Zed or head to ${{ steps.get-appropriate-url.outputs.URL }} to grab it.\n\n${{ github.event.release.body }}" + + maxTextLength=2000 + truncationIndicator="..." + + if (( ${#text} > maxTextLength )); then + text=${text:0:maxTextLength - ${#truncationIndicator}}$truncationIndicator + fi + + echo "::set-output name=content::$text" - name: Discord Webhook Action uses: tsickert/discord-webhook@v5.3.0 with: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} - content: | - 📣 Zed ${{ github.event.release.tag_name }} was just released! - - Restart your Zed or head to ${{ steps.get-appropriate-url.outputs.URL }} to grab it. - - ${{ github.event.release.body }} + content: ${{ steps.prepare-content.outputs.content }} From 354882f2c00d877defaf050ae8a34451ef5fa851 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 10 Oct 2023 00:16:15 -0400 Subject: [PATCH 051/180] Enable completion menu to resolve documentation when guest --- crates/collab/src/rpc.rs | 1 + crates/editor/src/editor.rs | 74 +++++++++++-- crates/language/src/buffer.rs | 13 ++- crates/project/src/lsp_command.rs | 12 +- crates/project/src/project.rs | 35 ++++++ crates/rpc/proto/zed.proto | 177 ++++++++++++++++-------------- crates/rpc/src/proto.rs | 7 ++ crates/rpc/src/rpc.rs | 2 +- 8 files changed, 217 insertions(+), 104 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5eb434e167cc115c7ec9f08dd24bc7b12f04e30a..ed09cde0610d36b87e1c9309f6c96fd6c4c04f01 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -224,6 +224,7 @@ impl Server { .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) + .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b8c9690b905d9b09e6aa783d13dc73a130bf6473..3143b19629e9ee6d19b532bfdc3e6f5636e79976 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,10 +60,10 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, - point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, - CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, - Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, - TransactionId, + markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, + Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, + IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, + SelectionGoal, TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -80,7 +80,7 @@ use ordered_float::OrderedFloat; use parking_lot::RwLock; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use rand::{seq::SliceRandom, thread_rng}; -use rpc::proto::PeerId; +use rpc::proto::{self, PeerId}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -999,7 +999,7 @@ impl CompletionsMenu { let completions = self.completions.clone(); let completions_guard = completions.read(); let completion = &completions_guard[index]; - if completion.lsp_completion.documentation.is_some() { + if completion.documentation.is_some() { return; } @@ -1007,6 +1007,57 @@ impl CompletionsMenu { let completion = completion.lsp_completion.clone(); drop(completions_guard); + if project.read(cx).is_remote() { + let Some(project_id) = project.read(cx).remote_id() else { + log::error!("Remote project without remote_id"); + return; + }; + + let client = project.read(cx).client(); + let request = proto::ResolveCompletionDocumentation { + project_id, + language_server_id: server_id.0 as u64, + lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), + }; + + cx.spawn(|this, mut cx| async move { + let Some(response) = client + .request(request) + .await + .context("completion documentation resolve proto request") + .log_err() + else { + return; + }; + + if response.text.is_empty() { + let mut completions = completions.write(); + let completion = &mut completions[index]; + completion.documentation = Some(Documentation::Undocumented); + } + + let documentation = if response.is_markdown { + Documentation::MultiLineMarkdown( + markdown::parse_markdown(&response.text, &language_registry, None).await, + ) + } else if response.text.lines().count() <= 1 { + Documentation::SingleLine(response.text) + } else { + Documentation::MultiLinePlainText(response.text) + }; + + let mut completions = completions.write(); + let completion = &mut completions[index]; + completion.documentation = Some(documentation); + drop(completions); + + _ = this.update(&mut cx, |_, cx| cx.notify()); + }) + .detach(); + + return; + } + let Some(server) = project.read(cx).language_server_for_id(server_id) else { return; }; @@ -1037,11 +1088,14 @@ impl CompletionsMenu { let mut completions = completions.write(); let completion = &mut completions[index]; - completion.documentation = documentation; - completion.lsp_completion.documentation = Some(lsp_documentation); + completion.documentation = Some(documentation); drop(completions); _ = this.update(&mut cx, |_, cx| cx.notify()); + } else { + let mut completions = completions.write(); + let completion = &mut completions[index]; + completion.documentation = Some(Documentation::Undocumented); } }) .detach(); @@ -1061,10 +1115,10 @@ impl CompletionsMenu { .max_by_key(|(_, mat)| { let completions = self.completions.read(); let completion = &completions[mat.candidate_id]; - let documentation = &completion.lsp_completion.documentation; + let documentation = &completion.documentation; let mut len = completion.label.text.chars().count(); - if let Some(lsp::Documentation::String(text)) = documentation { + if let Some(Documentation::SingleLine(text)) = documentation { len += text.chars().count(); } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d318a87b40850f2799a0db7d95bbaa5a67556f5e..d8ebc1d445d8ae358dd40537e5e81c0eb3b1b039 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -149,28 +149,28 @@ pub async fn prepare_completion_documentation( documentation: &lsp::Documentation, language_registry: &Arc, language: Option>, -) -> Option { +) -> Documentation { match documentation { lsp::Documentation::String(text) => { if text.lines().count() <= 1 { - Some(Documentation::SingleLine(text.clone())) + Documentation::SingleLine(text.clone()) } else { - Some(Documentation::MultiLinePlainText(text.clone())) + Documentation::MultiLinePlainText(text.clone()) } } lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind { lsp::MarkupKind::PlainText => { if value.lines().count() <= 1 { - Some(Documentation::SingleLine(value.clone())) + Documentation::SingleLine(value.clone()) } else { - Some(Documentation::MultiLinePlainText(value.clone())) + Documentation::MultiLinePlainText(value.clone()) } } lsp::MarkupKind::Markdown => { let parsed = parse_markdown(value, language_registry, language).await; - Some(Documentation::MultiLineMarkdown(parsed)) + Documentation::MultiLineMarkdown(parsed) } }, } @@ -178,6 +178,7 @@ pub async fn prepare_completion_documentation( #[derive(Clone, Debug)] pub enum Documentation { + Undocumented, SingleLine(String), MultiLinePlainText(String), MultiLineMarkdown(ParsedMarkdown), diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index c71b378da685cd429a11a392ae242c71da7f8158..72d79ca97992c47384f004c6d1f25bb7f59f22a1 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1466,12 +1466,14 @@ impl LspCommand for GetCompletions { } let documentation = if let Some(lsp_docs) = &lsp_completion.documentation { - prepare_completion_documentation( - lsp_docs, - &language_registry, - language.clone(), + Some( + prepare_completion_documentation( + lsp_docs, + &language_registry, + language.clone(), + ) + .await, ) - .await } else { None }; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a50e02a631ccda81f1d40c49cc3361c1626a383d..f25309b9c6a8f0dbe50eb78f21f65ba6907766b6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -580,6 +580,7 @@ impl Project { client.add_model_request_handler(Self::handle_apply_code_action); client.add_model_request_handler(Self::handle_on_type_formatting); client.add_model_request_handler(Self::handle_inlay_hints); + client.add_model_request_handler(Self::handle_resolve_completion_documentation); client.add_model_request_handler(Self::handle_resolve_inlay_hint); client.add_model_request_handler(Self::handle_refresh_inlay_hints); client.add_model_request_handler(Self::handle_reload_buffers); @@ -7155,6 +7156,40 @@ impl Project { }) } + async fn handle_resolve_completion_documentation( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + let lsp_completion = serde_json::from_slice(&envelope.payload.lsp_completion)?; + + let completion = this + .read_with(&mut cx, |this, _| { + let id = LanguageServerId(envelope.payload.language_server_id as usize); + let Some(server) = this.language_server_for_id(id) else { + return Err(anyhow!("No language server {id}")); + }; + + Ok(server.request::(lsp_completion)) + })? + .await?; + + let mut is_markdown = false; + let text = match completion.documentation { + Some(lsp::Documentation::String(text)) => text, + + Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value })) => { + is_markdown = kind == lsp::MarkupKind::Markdown; + value + } + + _ => String::new(), + }; + + Ok(proto::ResolveCompletionDocumentationResponse { text, is_markdown }) + } + async fn handle_apply_code_action( this: ModelHandle, envelope: TypedEnvelope, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 3501e70e6ac4191c5fe3141620e18cd8f7b8c32d..e97ede3feea851db0ba2f6b95681892453d3c144 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -89,88 +89,90 @@ message Envelope { FormatBuffersResponse format_buffers_response = 70; GetCompletions get_completions = 71; GetCompletionsResponse get_completions_response = 72; - ApplyCompletionAdditionalEdits apply_completion_additional_edits = 73; - ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 74; - GetCodeActions get_code_actions = 75; - GetCodeActionsResponse get_code_actions_response = 76; - GetHover get_hover = 77; - GetHoverResponse get_hover_response = 78; - ApplyCodeAction apply_code_action = 79; - ApplyCodeActionResponse apply_code_action_response = 80; - PrepareRename prepare_rename = 81; - PrepareRenameResponse prepare_rename_response = 82; - PerformRename perform_rename = 83; - PerformRenameResponse perform_rename_response = 84; - SearchProject search_project = 85; - SearchProjectResponse search_project_response = 86; - - UpdateContacts update_contacts = 87; - UpdateInviteInfo update_invite_info = 88; - ShowContacts show_contacts = 89; - - GetUsers get_users = 90; - FuzzySearchUsers fuzzy_search_users = 91; - UsersResponse users_response = 92; - RequestContact request_contact = 93; - RespondToContactRequest respond_to_contact_request = 94; - RemoveContact remove_contact = 95; - - Follow follow = 96; - FollowResponse follow_response = 97; - UpdateFollowers update_followers = 98; - Unfollow unfollow = 99; - GetPrivateUserInfo get_private_user_info = 100; - GetPrivateUserInfoResponse get_private_user_info_response = 101; - UpdateDiffBase update_diff_base = 102; - - OnTypeFormatting on_type_formatting = 103; - OnTypeFormattingResponse on_type_formatting_response = 104; - - UpdateWorktreeSettings update_worktree_settings = 105; - - InlayHints inlay_hints = 106; - InlayHintsResponse inlay_hints_response = 107; - ResolveInlayHint resolve_inlay_hint = 108; - ResolveInlayHintResponse resolve_inlay_hint_response = 109; - RefreshInlayHints refresh_inlay_hints = 110; - - CreateChannel create_channel = 111; - CreateChannelResponse create_channel_response = 112; - InviteChannelMember invite_channel_member = 113; - RemoveChannelMember remove_channel_member = 114; - RespondToChannelInvite respond_to_channel_invite = 115; - UpdateChannels update_channels = 116; - JoinChannel join_channel = 117; - DeleteChannel delete_channel = 118; - GetChannelMembers get_channel_members = 119; - GetChannelMembersResponse get_channel_members_response = 120; - SetChannelMemberAdmin set_channel_member_admin = 121; - RenameChannel rename_channel = 122; - RenameChannelResponse rename_channel_response = 123; - - JoinChannelBuffer join_channel_buffer = 124; - JoinChannelBufferResponse join_channel_buffer_response = 125; - UpdateChannelBuffer update_channel_buffer = 126; - LeaveChannelBuffer leave_channel_buffer = 127; - UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 128; - RejoinChannelBuffers rejoin_channel_buffers = 129; - RejoinChannelBuffersResponse rejoin_channel_buffers_response = 130; - AckBufferOperation ack_buffer_operation = 143; - - JoinChannelChat join_channel_chat = 131; - JoinChannelChatResponse join_channel_chat_response = 132; - LeaveChannelChat leave_channel_chat = 133; - SendChannelMessage send_channel_message = 134; - SendChannelMessageResponse send_channel_message_response = 135; - ChannelMessageSent channel_message_sent = 136; - GetChannelMessages get_channel_messages = 137; - GetChannelMessagesResponse get_channel_messages_response = 138; - RemoveChannelMessage remove_channel_message = 139; - AckChannelMessage ack_channel_message = 144; - - LinkChannel link_channel = 140; - UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; // current max: 144 + ResolveCompletionDocumentation resolve_completion_documentation = 73; + ResolveCompletionDocumentationResponse resolve_completion_documentation_response = 74; + ApplyCompletionAdditionalEdits apply_completion_additional_edits = 75; + ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 76; + GetCodeActions get_code_actions = 77; + GetCodeActionsResponse get_code_actions_response = 78; + GetHover get_hover = 79; + GetHoverResponse get_hover_response = 80; + ApplyCodeAction apply_code_action = 81; + ApplyCodeActionResponse apply_code_action_response = 82; + PrepareRename prepare_rename = 83; + PrepareRenameResponse prepare_rename_response = 84; + PerformRename perform_rename = 85; + PerformRenameResponse perform_rename_response = 86; + SearchProject search_project = 87; + SearchProjectResponse search_project_response = 88; + + UpdateContacts update_contacts = 89; + UpdateInviteInfo update_invite_info = 90; + ShowContacts show_contacts = 91; + + GetUsers get_users = 92; + FuzzySearchUsers fuzzy_search_users = 93; + UsersResponse users_response = 94; + RequestContact request_contact = 95; + RespondToContactRequest respond_to_contact_request = 96; + RemoveContact remove_contact = 97; + + Follow follow = 98; + FollowResponse follow_response = 99; + UpdateFollowers update_followers = 100; + Unfollow unfollow = 101; + GetPrivateUserInfo get_private_user_info = 102; + GetPrivateUserInfoResponse get_private_user_info_response = 103; + UpdateDiffBase update_diff_base = 104; + + OnTypeFormatting on_type_formatting = 105; + OnTypeFormattingResponse on_type_formatting_response = 106; + + UpdateWorktreeSettings update_worktree_settings = 107; + + InlayHints inlay_hints = 108; + InlayHintsResponse inlay_hints_response = 109; + ResolveInlayHint resolve_inlay_hint = 110; + ResolveInlayHintResponse resolve_inlay_hint_response = 111; + RefreshInlayHints refresh_inlay_hints = 112; + + CreateChannel create_channel = 113; + CreateChannelResponse create_channel_response = 114; + InviteChannelMember invite_channel_member = 115; + RemoveChannelMember remove_channel_member = 116; + RespondToChannelInvite respond_to_channel_invite = 117; + UpdateChannels update_channels = 118; + JoinChannel join_channel = 119; + DeleteChannel delete_channel = 120; + GetChannelMembers get_channel_members = 121; + GetChannelMembersResponse get_channel_members_response = 122; + SetChannelMemberAdmin set_channel_member_admin = 123; + RenameChannel rename_channel = 124; + RenameChannelResponse rename_channel_response = 125; + + JoinChannelBuffer join_channel_buffer = 126; + JoinChannelBufferResponse join_channel_buffer_response = 127; + UpdateChannelBuffer update_channel_buffer = 128; + LeaveChannelBuffer leave_channel_buffer = 129; + UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 130; + RejoinChannelBuffers rejoin_channel_buffers = 131; + RejoinChannelBuffersResponse rejoin_channel_buffers_response = 132; + AckBufferOperation ack_buffer_operation = 145; + + JoinChannelChat join_channel_chat = 133; + JoinChannelChatResponse join_channel_chat_response = 134; + LeaveChannelChat leave_channel_chat = 135; + SendChannelMessage send_channel_message = 136; + SendChannelMessageResponse send_channel_message_response = 137; + ChannelMessageSent channel_message_sent = 138; + GetChannelMessages get_channel_messages = 139; + GetChannelMessagesResponse get_channel_messages_response = 140; + RemoveChannelMessage remove_channel_message = 141; + AckChannelMessage ack_channel_message = 146; + + LinkChannel link_channel = 142; + UnlinkChannel unlink_channel = 143; + MoveChannel move_channel = 144; // current max: 146 } } @@ -832,6 +834,17 @@ message ResolveState { } } +message ResolveCompletionDocumentation { + uint64 project_id = 1; + uint64 language_server_id = 2; + bytes lsp_completion = 3; +} + +message ResolveCompletionDocumentationResponse { + string text = 1; + bool is_markdown = 2; +} + message ResolveInlayHint { uint64 project_id = 1; uint64 buffer_id = 2; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index f0d7937f6f9592b2574bff7b93d3097ceba24d94..abadada32827b3f28cd42f88577f2b66dc93d4b3 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -205,6 +205,8 @@ messages!( (OnTypeFormattingResponse, Background), (InlayHints, Background), (InlayHintsResponse, Background), + (ResolveCompletionDocumentation, Background), + (ResolveCompletionDocumentationResponse, Background), (ResolveInlayHint, Background), (ResolveInlayHintResponse, Background), (RefreshInlayHints, Foreground), @@ -318,6 +320,10 @@ request_messages!( (PrepareRename, PrepareRenameResponse), (OnTypeFormatting, OnTypeFormattingResponse), (InlayHints, InlayHintsResponse), + ( + ResolveCompletionDocumentation, + ResolveCompletionDocumentationResponse + ), (ResolveInlayHint, ResolveInlayHintResponse), (RefreshInlayHints, Ack), (ReloadBuffers, ReloadBuffersResponse), @@ -381,6 +387,7 @@ entity_messages!( PerformRename, OnTypeFormatting, InlayHints, + ResolveCompletionDocumentation, ResolveInlayHint, RefreshInlayHints, PrepareRename, diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 942672b94bab387a95bfe37bb6b73308e73496dc..5ba531a50ed8d62cc857932c3acae31be1c48433 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 64; +pub const PROTOCOL_VERSION: u32 = 65; From 639ae671aed28847af279df03ac77f6825672027 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 10 Oct 2023 12:26:48 +0300 Subject: [PATCH 052/180] Omit history files with path that does not exist on disk anymore --- crates/file_finder/src/file_finder.rs | 122 +++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 222a9c650af4eaa6033a96217c8242da1cd7a307..b7a4a387ab63a8592992c538e606c6e0d915290f 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -222,6 +222,10 @@ fn toggle_or_cycle_file_finder( .as_ref() .and_then(|found_path| found_path.absolute.as_ref()) }) + .filter(|(_, history_abs_path)| match history_abs_path { + Some(abs_path) => history_file_exists(abs_path), + None => true, + }) .map(|(history_path, abs_path)| FoundPath::new(history_path, abs_path)), ) .collect(); @@ -246,6 +250,16 @@ fn toggle_or_cycle_file_finder( } } +#[cfg(not(test))] +fn history_file_exists(abs_path: &PathBuf) -> bool { + abs_path.exists() +} + +#[cfg(test)] +fn history_file_exists(abs_path: &PathBuf) -> bool { + !abs_path.ends_with("nonexistent.rs") +} + pub enum Event { Selected(ProjectPath), Dismissed, @@ -515,12 +529,7 @@ impl PickerDelegate for FileFinderDelegate { project .worktree_for_id(history_item.project.worktree_id, cx) .is_some() - || (project.is_local() - && history_item - .absolute - .as_ref() - .filter(|abs_path| abs_path.exists()) - .is_some()) + || (project.is_local() && history_item.absolute.is_some()) }) .cloned() .map(|p| (p, None)) @@ -1900,13 +1909,8 @@ mod tests { .matches .search .iter() - .map(|e| e.path.to_path_buf()) + .map(|path_match| path_match.path.to_path_buf()) .collect::>(); - assert_eq!( - search_entries.len(), - 4, - "All history and the new file should be found after query {query} as search results" - ); assert_eq!( search_entries, vec![ @@ -1920,6 +1924,100 @@ mod tests { }); } + #[gpui::test] + async fn test_nonexistent_history_items_not_shown( + deterministic: Arc, + cx: &mut gpui::TestAppContext, + ) { + let app_state = init_test(cx); + + app_state + .fs + .as_fake() + .insert_tree( + "/src", + json!({ + "test": { + "first.rs": "// First Rust file", + "nonexistent.rs": "// Second Rust file", + "third.rs": "// Third Rust file", + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + // generate some history to select from + open_close_queried_buffer( + "fir", + 1, + "first.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + open_close_queried_buffer( + "non", + 1, + "nonexistent.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + open_close_queried_buffer( + "thi", + 1, + "third.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + open_close_queried_buffer( + "fir", + 1, + "first.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + + cx.dispatch_action(window.into(), Toggle); + let query = "rs"; + let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); + finder + .update(cx, |finder, cx| { + finder.delegate_mut().update_matches(query.to_string(), cx) + }) + .await; + finder.read_with(cx, |finder, _| { + let delegate = finder.delegate(); + let history_entries = delegate + .matches + .history + .iter() + .map(|(_, path_match)| path_match.as_ref().expect("should have a path match").path.to_path_buf()) + .collect::>(); + assert_eq!( + history_entries, + vec![ + PathBuf::from("test/first.rs"), + PathBuf::from("test/third.rs"), + ], + "Should have all opened files in the history, except the ones that do not exist on disk" + ); + }); + } + async fn open_close_queried_buffer( input: &str, expected_matches: usize, From f5af5f7334a7b523090c0741029dde913cd00c2c Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 10 Oct 2023 09:27:18 -0400 Subject: [PATCH 053/180] Avoid leaving selected item index past end of matches list Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3143b19629e9ee6d19b532bfdc3e6f5636e79976..9b276f002c010cb4fc366864e0b74a4827ab0cf1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1332,6 +1332,7 @@ impl CompletionsMenu { } self.matches = matches.into(); + self.selected_item = 0; } } From 801af95a13e0332af3f9a4d1a1d62c47bea07529 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 10 Oct 2023 10:08:29 -0400 Subject: [PATCH 054/180] Make completion documentation scroll & fix accompanying panic from tag Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 26 +++++++---- crates/editor/src/hover_popover.rs | 4 +- crates/gpui/src/elements/flex.rs | 75 ++++++++++++++++++------------ 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9b276f002c010cb4fc366864e0b74a4827ab0cf1..06482dbbc6ff6d0bb8339d1fcffbfa6b0ea29496 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -119,7 +119,7 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); -pub fn render_parsed_markdown( +pub fn render_parsed_markdown( parsed: &language::ParsedMarkdown, editor_style: &EditorStyle, cx: &mut ViewContext, @@ -153,7 +153,7 @@ pub fn render_parsed_markdown( style: CursorStyle::PointingHand, }); cx.scene().push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) + MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds) .on_click::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) }), @@ -1247,6 +1247,8 @@ impl CompletionsMenu { }) .with_width_from_item(widest_completion_ix); + enum MultiLineDocumentation {} + Flex::row() .with_child(list) .with_children({ @@ -1256,13 +1258,21 @@ impl CompletionsMenu { let documentation = &completion.documentation; match documentation { - Some(Documentation::MultiLinePlainText(text)) => { - Some(Text::new(text.clone(), style.text.clone())) - } + Some(Documentation::MultiLinePlainText(text)) => Some( + Flex::column() + .scrollable::(0, None, cx) + .with_child( + Text::new(text.clone(), style.text.clone()).with_soft_wrap(true), + ), + ), - Some(Documentation::MultiLineMarkdown(parsed)) => { - Some(render_parsed_markdown(parsed, &style, cx)) - } + Some(Documentation::MultiLineMarkdown(parsed)) => Some( + Flex::column() + .scrollable::(0, None, cx) + .with_child(render_parsed_markdown::( + parsed, &style, cx, + )), + ), _ => None, } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index d5ccb481b2f9a21ffb3aca34a728f908f3ab8ce3..e8901ad6c1678e199a5909992bf4ea9d5fb89809 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -474,8 +474,8 @@ impl InfoPopover { ) -> AnyElement { MouseEventHandler::new::(0, cx, |_, cx| { Flex::column() - .scrollable::(1, None, cx) - .with_child(crate::render_parsed_markdown( + .scrollable::(0, None, cx) + .with_child(crate::render_parsed_markdown::( &self.parsed_content, style, cx, diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index cdce0423fd6d9eef4e0e5be11e584b02d58fec76..ba387c5e48a7068856e4f8bfb31bdf36443d9c68 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -2,7 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc}; use crate::{ json::{self, ToJson, Value}, - AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, Vector2FExt, ViewContext, + AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, TypeTag, Vector2FExt, + ViewContext, }; use pathfinder_geometry::{ rect::RectF, @@ -10,10 +11,10 @@ use pathfinder_geometry::{ }; use serde_json::json; -#[derive(Default)] struct ScrollState { scroll_to: Cell>, scroll_position: Cell, + type_tag: TypeTag, } pub struct Flex { @@ -66,8 +67,14 @@ impl Flex { where Tag: 'static, { - let scroll_state = cx.default_element_state::>(element_id); - scroll_state.read(cx).scroll_to.set(scroll_to); + let scroll_state = cx.element_state::>( + element_id, + Rc::new(ScrollState { + scroll_to: Cell::new(scroll_to), + scroll_position: Default::default(), + type_tag: TypeTag::new::(), + }), + ); self.scroll_state = Some((scroll_state, cx.handle().id())); self } @@ -276,38 +283,44 @@ impl Element for Flex { if let Some((scroll_state, id)) = &self.scroll_state { let scroll_state = scroll_state.read(cx).clone(); cx.scene().push_mouse_region( - crate::MouseRegion::new::(*id, 0, bounds) - .on_scroll({ - let axis = self.axis; - move |e, _: &mut V, cx| { - if remaining_space < 0. { - let scroll_delta = e.delta.raw(); - - let mut delta = match axis { - Axis::Horizontal => { - if scroll_delta.x().abs() >= scroll_delta.y().abs() { - scroll_delta.x() - } else { - scroll_delta.y() - } + crate::MouseRegion::from_handlers( + scroll_state.type_tag, + *id, + 0, + bounds, + Default::default(), + ) + .on_scroll({ + let axis = self.axis; + move |e, _: &mut V, cx| { + if remaining_space < 0. { + let scroll_delta = e.delta.raw(); + + let mut delta = match axis { + Axis::Horizontal => { + if scroll_delta.x().abs() >= scroll_delta.y().abs() { + scroll_delta.x() + } else { + scroll_delta.y() } - Axis::Vertical => scroll_delta.y(), - }; - if !e.delta.precise() { - delta *= 20.; } + Axis::Vertical => scroll_delta.y(), + }; + if !e.delta.precise() { + delta *= 20.; + } - scroll_state - .scroll_position - .set(scroll_state.scroll_position.get() - delta); + scroll_state + .scroll_position + .set(scroll_state.scroll_position.get() - delta); - cx.notify(); - } else { - cx.propagate_event(); - } + cx.notify(); + } else { + cx.propagate_event(); } - }) - .on_move(|_, _: &mut V, _| { /* Capture move events */ }), + } + }) + .on_move(|_, _: &mut V, _| { /* Capture move events */ }), ) } From 5cf92980f0d7a4e6320bb172b7115ad59dfb25e9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 10 Oct 2023 17:51:17 +0200 Subject: [PATCH 055/180] Revert summarizing file content until we can be more intelligent about what we send Co-Authored-By: Nathan Sobo --- crates/assistant/src/assistant_panel.rs | 10 ++++++---- crates/assistant/src/prompts.rs | 21 +++++++++++++++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index b69c12a2a328ed8643315f091be11d764dcdc00d..62e2f61111062afbe4e53b77cae5efbb7fc3134c 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -578,10 +578,7 @@ impl AssistantPanel { let codegen_kind = codegen.read(cx).kind().clone(); let user_prompt = user_prompt.to_string(); - let prompt = cx.background().spawn(async move { - let language_name = language_name.as_deref(); - generate_content_prompt(user_prompt, language_name, &buffer, range, codegen_kind) - }); + let mut messages = Vec::new(); let mut model = settings::get::(cx) .default_open_ai_model @@ -597,6 +594,11 @@ impl AssistantPanel { model = conversation.model.clone(); } + let prompt = cx.background().spawn(async move { + let language_name = language_name.as_deref(); + generate_content_prompt(user_prompt, language_name, &buffer, range, codegen_kind) + }); + cx.spawn(|_, mut cx| async move { let prompt = prompt.await; diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index bf041dff523d57d62cfbc3f312a350ad4766d160..7aca36577630e1ef8a3df5d5382ce56418223eb4 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -121,6 +121,7 @@ pub fn generate_content_prompt( range: Range, kind: CodegenKind, ) -> String { + let range = range.to_offset(buffer); let mut prompt = String::new(); // General Preamble @@ -130,17 +131,29 @@ pub fn generate_content_prompt( writeln!(prompt, "You're an expert engineer.\n").unwrap(); } - let outline = summarize(buffer, range); + let mut content = String::new(); + content.extend(buffer.text_for_range(0..range.start)); + if range.start == range.end { + content.push_str("<|START|>"); + } else { + content.push_str("<|START|"); + } + content.extend(buffer.text_for_range(range.clone())); + if range.start != range.end { + content.push_str("|END|>"); + } + content.extend(buffer.text_for_range(range.end..buffer.len())); + writeln!( prompt, - "The file you are currently working on has the following outline:" + "The file you are currently working on has the following content:" ) .unwrap(); if let Some(language_name) = language_name { let language_name = language_name.to_lowercase(); - writeln!(prompt, "```{language_name}\n{outline}\n```").unwrap(); + writeln!(prompt, "```{language_name}\n{content}\n```").unwrap(); } else { - writeln!(prompt, "```\n{outline}\n```").unwrap(); + writeln!(prompt, "```\n{content}\n```").unwrap(); } match kind { From b366592878d01d3174fdbd2afb901bb354bea03b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 10 Oct 2023 19:00:05 +0200 Subject: [PATCH 056/180] Don't include start of a line when selection ends at start of line --- crates/assistant/src/assistant_panel.rs | 36 +++++++++++++++++-------- crates/assistant/src/codegen.rs | 23 +++------------- crates/assistant/src/prompts.rs | 1 + 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 62e2f61111062afbe4e53b77cae5efbb7fc3134c..b1c6038602b77465cef3f994b02cdc0635ed6776 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -17,7 +17,7 @@ use editor::{ BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, ToDisplayPoint, }, scroll::autoscroll::{Autoscroll, AutoscrollStrategy}, - Anchor, Editor, MoveDown, MoveUp, MultiBufferSnapshot, ToOffset, + Anchor, Editor, MoveDown, MoveUp, MultiBufferSnapshot, ToOffset, ToPoint, }; use fs::Fs; use futures::StreamExt; @@ -278,22 +278,36 @@ impl AssistantPanel { if selection.start.excerpt_id() != selection.end.excerpt_id() { return; } - - let inline_assist_id = post_inc(&mut self.next_inline_assist_id); let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx); - let provider = Arc::new(OpenAICompletionProvider::new( - api_key, - cx.background().clone(), - )); - let codegen_kind = if editor.read(cx).selections.newest::(cx).is_empty() { + + // Extend the selection to the start and the end of the line. + let mut point_selection = selection.map(|selection| selection.to_point(&snapshot)); + if point_selection.end > point_selection.start { + point_selection.start.column = 0; + // If the selection ends at the start of the line, we don't want to include it. + if point_selection.end.column == 0 { + point_selection.end.row -= 1; + } + point_selection.end.column = snapshot.line_len(point_selection.end.row); + } + + let codegen_kind = if point_selection.start == point_selection.end { CodegenKind::Generate { - position: selection.start, + position: snapshot.anchor_after(point_selection.start), } } else { CodegenKind::Transform { - range: selection.start..selection.end, + range: snapshot.anchor_before(point_selection.start) + ..snapshot.anchor_after(point_selection.end), } }; + + let inline_assist_id = post_inc(&mut self.next_inline_assist_id); + let provider = Arc::new(OpenAICompletionProvider::new( + api_key, + cx.background().clone(), + )); + let codegen = cx.add_model(|cx| { Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx) }); @@ -319,7 +333,7 @@ impl AssistantPanel { editor.insert_blocks( [BlockProperties { style: BlockStyle::Flex, - position: selection.head().bias_left(&snapshot), + position: snapshot.anchor_before(point_selection.head()), height: 2, render: Arc::new({ let inline_assistant = inline_assistant.clone(); diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs index e956d722606f6db27c73385d7cf54d58bc82958b..b6ef6b5cfa7fef58936828e0f121946290bc8b48 100644 --- a/crates/assistant/src/codegen.rs +++ b/crates/assistant/src/codegen.rs @@ -1,9 +1,7 @@ use crate::streaming_diff::{Hunk, StreamingDiff}; use ai::completion::{CompletionProvider, OpenAIRequest}; use anyhow::Result; -use editor::{ - multi_buffer, Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, -}; +use editor::{multi_buffer, Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; use futures::{channel::mpsc, SinkExt, Stream, StreamExt}; use gpui::{Entity, ModelContext, ModelHandle, Task}; use language::{Rope, TransactionId}; @@ -40,26 +38,11 @@ impl Entity for Codegen { impl Codegen { pub fn new( buffer: ModelHandle, - mut kind: CodegenKind, + kind: CodegenKind, provider: Arc, cx: &mut ModelContext, ) -> Self { let snapshot = buffer.read(cx).snapshot(cx); - match &mut kind { - CodegenKind::Transform { range } => { - let mut point_range = range.to_point(&snapshot); - point_range.start.column = 0; - if point_range.end.column > 0 || point_range.start.row == point_range.end.row { - point_range.end.column = snapshot.line_len(point_range.end.row); - } - range.start = snapshot.anchor_before(point_range.start); - range.end = snapshot.anchor_after(point_range.end); - } - CodegenKind::Generate { position } => { - *position = position.bias_right(&snapshot); - } - } - Self { provider, buffer: buffer.clone(), @@ -386,7 +369,7 @@ mod tests { let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let range = buffer.read_with(cx, |buffer, cx| { let snapshot = buffer.snapshot(cx); - snapshot.anchor_before(Point::new(1, 4))..snapshot.anchor_after(Point::new(4, 4)) + snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5)) }); let provider = Arc::new(TestCompletionProvider::new()); let codegen = cx.add_model(|cx| { diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 7aca36577630e1ef8a3df5d5382ce56418223eb4..d326a7f44547ee977095484006a2867a2546d525 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -4,6 +4,7 @@ use std::cmp::{self, Reverse}; use std::fmt::Write; use std::ops::Range; +#[allow(dead_code)] fn summarize(buffer: &BufferSnapshot, selected_range: Range) -> String { #[derive(Debug)] struct Match { From 40430cf01ba433a5f5a634b4779273f4c4f5c5da Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 10 Oct 2023 12:38:20 -0700 Subject: [PATCH 057/180] Update channel rooms to be ephemeral Remove redundant live kit initialization code Fix bug in recent channel links changes where channel rooms would have the incorrect release set co-authored-by: Conrad Irwin co-authored-by: Max --- .../20221109000000_test_schema.sql | 1 + ...0_add_unique_index_on_rooms_channel_id.sql | 1 + crates/collab/src/db/queries/channels.rs | 57 +++++++++-------- crates/collab/src/db/queries/rooms.rs | 5 +- crates/collab/src/db/tests/buffer_tests.rs | 6 +- crates/collab/src/db/tests/channel_tests.rs | 63 +++++++------------ crates/collab/src/db/tests/message_tests.rs | 20 ++---- crates/collab/src/rpc.rs | 17 ++--- crates/collab/src/tests/channel_tests.rs | 2 + .../src/tests/random_channel_buffer_tests.rs | 7 +-- crates/live_kit_client/src/test.rs | 5 +- 11 files changed, 73 insertions(+), 111 deletions(-) create mode 100644 crates/collab/migrations/20231010114600_add_unique_index_on_rooms_channel_id.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index a9b8d9709defb861c598239abbb3aa62b4ff71b7..e5104839b8f358b8db617516eb60d32a0cd25d27 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -40,6 +40,7 @@ CREATE TABLE "rooms" ( "release_channel" VARCHAR, "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE ); +CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); CREATE TABLE "projects" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/crates/collab/migrations/20231010114600_add_unique_index_on_rooms_channel_id.sql b/crates/collab/migrations/20231010114600_add_unique_index_on_rooms_channel_id.sql new file mode 100644 index 0000000000000000000000000000000000000000..21ec4cfbb75a574ad3704179a0ae14c8050149d1 --- /dev/null +++ b/crates/collab/migrations/20231010114600_add_unique_index_on_rooms_channel_id.sql @@ -0,0 +1 @@ +CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index ab31f59541887ac0c7a70db7ab60ade2bce79dbf..b2fa2eb9b5b6e7a9d4dd7499c9eb3e29bfb7866a 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -19,21 +19,14 @@ impl Database { .await } - pub async fn create_root_channel( - &self, - name: &str, - live_kit_room: &str, - creator_id: UserId, - ) -> Result { - self.create_channel(name, None, live_kit_room, creator_id) - .await + pub async fn create_root_channel(&self, name: &str, creator_id: UserId) -> Result { + self.create_channel(name, None, creator_id).await } pub async fn create_channel( &self, name: &str, parent: Option, - live_kit_room: &str, creator_id: UserId, ) -> Result { let name = Self::sanitize_channel_name(name)?; @@ -90,14 +83,6 @@ impl Database { .insert(&*tx) .await?; - room::ActiveModel { - channel_id: ActiveValue::Set(Some(channel.id)), - live_kit_room: ActiveValue::Set(live_kit_room.to_string()), - ..Default::default() - } - .insert(&*tx) - .await?; - Ok(channel.id) }) .await @@ -797,18 +782,36 @@ impl Database { .await } - pub async fn room_id_for_channel(&self, channel_id: ChannelId) -> Result { + pub async fn get_or_create_channel_room( + &self, + channel_id: ChannelId, + live_kit_room: &str, + enviroment: &str, + ) -> Result { self.transaction(|tx| async move { let tx = tx; - let room = channel::Model { - id: channel_id, - ..Default::default() - } - .find_related(room::Entity) - .one(&*tx) - .await? - .ok_or_else(|| anyhow!("invalid channel"))?; - Ok(room.id) + + let room = room::Entity::find() + .filter(room::Column::ChannelId.eq(channel_id)) + .one(&*tx) + .await?; + + let room_id = if let Some(room) = room { + room.id + } else { + let result = room::Entity::insert(room::ActiveModel { + channel_id: ActiveValue::Set(Some(channel_id)), + live_kit_room: ActiveValue::Set(live_kit_room.to_string()), + release_channel: ActiveValue::Set(Some(enviroment.to_string())), + ..Default::default() + }) + .exec(&*tx) + .await?; + + result.last_insert_id + }; + + Ok(room_id) }) .await } diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 6589f23791d792d4144441d92cd25d08d088f231..4763b3f5cd4ff1eec6b5d83f36fa9b3c41202cd9 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -832,10 +832,7 @@ impl Database { let (channel_id, room) = self.get_channel_room(room_id, &tx).await?; let deleted = if room.participants.is_empty() { - let result = room::Entity::delete_by_id(room_id) - .filter(room::Column::ChannelId.is_null()) - .exec(&*tx) - .await?; + let result = room::Entity::delete_by_id(room_id).exec(&*tx).await?; result.rows_affected > 0 } else { false diff --git a/crates/collab/src/db/tests/buffer_tests.rs b/crates/collab/src/db/tests/buffer_tests.rs index f6e91b91f011046d6a20431ac10099b21e543774..0ac41a8b0b4267fdd50e8e2c8392319169194888 100644 --- a/crates/collab/src/db/tests/buffer_tests.rs +++ b/crates/collab/src/db/tests/buffer_tests.rs @@ -54,7 +54,7 @@ async fn test_channel_buffers(db: &Arc) { let owner_id = db.create_server("production").await.unwrap().0 as u32; - let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap(); + let zed_id = db.create_root_channel("zed", a_id).await.unwrap(); db.invite_channel_member(zed_id, b_id, a_id, false) .await @@ -141,7 +141,7 @@ async fn test_channel_buffers(db: &Arc) { assert_eq!(left_buffer.connections, &[connection_id_a],); - let cargo_id = db.create_root_channel("cargo", "2", a_id).await.unwrap(); + let cargo_id = db.create_root_channel("cargo", a_id).await.unwrap(); let _ = db .join_channel_buffer(cargo_id, a_id, connection_id_a) .await @@ -207,7 +207,7 @@ async fn test_channel_buffers_last_operations(db: &Database) { let mut text_buffers = Vec::new(); for i in 0..3 { let channel = db - .create_root_channel(&format!("channel-{i}"), &format!("room-{i}"), user_id) + .create_root_channel(&format!("channel-{i}"), user_id) .await .unwrap(); diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 2631e0d19184969450de53c001a9f4595e6252dc..7d2bc04a35aac3adb30ead310705d2ee192ed54a 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -45,7 +45,7 @@ async fn test_channels(db: &Arc) { .unwrap() .user_id; - let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap(); + let zed_id = db.create_root_channel("zed", a_id).await.unwrap(); // Make sure that people cannot read channels they haven't been invited to assert!(db.get_channel(zed_id, b_id).await.unwrap().is_none()); @@ -58,16 +58,13 @@ async fn test_channels(db: &Arc) { .await .unwrap(); - let crdb_id = db - .create_channel("crdb", Some(zed_id), "2", a_id) - .await - .unwrap(); + let crdb_id = db.create_channel("crdb", Some(zed_id), a_id).await.unwrap(); let livestreaming_id = db - .create_channel("livestreaming", Some(zed_id), "3", a_id) + .create_channel("livestreaming", Some(zed_id), a_id) .await .unwrap(); let replace_id = db - .create_channel("replace", Some(zed_id), "4", a_id) + .create_channel("replace", Some(zed_id), a_id) .await .unwrap(); @@ -75,14 +72,14 @@ async fn test_channels(db: &Arc) { members.sort(); assert_eq!(members, &[a_id, b_id]); - let rust_id = db.create_root_channel("rust", "5", a_id).await.unwrap(); + let rust_id = db.create_root_channel("rust", a_id).await.unwrap(); let cargo_id = db - .create_channel("cargo", Some(rust_id), "6", a_id) + .create_channel("cargo", Some(rust_id), a_id) .await .unwrap(); let cargo_ra_id = db - .create_channel("cargo-ra", Some(cargo_id), "7", a_id) + .create_channel("cargo-ra", Some(cargo_id), a_id) .await .unwrap(); @@ -202,11 +199,11 @@ async fn test_joining_channels(db: &Arc) { .unwrap() .user_id; - let channel_1 = db - .create_root_channel("channel_1", "1", user_1) + let channel_1 = db.create_root_channel("channel_1", user_1).await.unwrap(); + let room_1 = db + .get_or_create_channel_room(channel_1, "1", TEST_RELEASE_CHANNEL) .await .unwrap(); - let room_1 = db.room_id_for_channel(channel_1).await.unwrap(); // can join a room with membership to its channel let joined_room = db @@ -283,15 +280,9 @@ async fn test_channel_invites(db: &Arc) { .unwrap() .user_id; - let channel_1_1 = db - .create_root_channel("channel_1", "1", user_1) - .await - .unwrap(); + let channel_1_1 = db.create_root_channel("channel_1", user_1).await.unwrap(); - let channel_1_2 = db - .create_root_channel("channel_2", "2", user_1) - .await - .unwrap(); + let channel_1_2 = db.create_root_channel("channel_2", user_1).await.unwrap(); db.invite_channel_member(channel_1_1, user_2, user_1, false) .await @@ -353,7 +344,7 @@ async fn test_channel_invites(db: &Arc) { .unwrap(); let channel_1_3 = db - .create_channel("channel_3", Some(channel_1_1), "1", user_1) + .create_channel("channel_3", Some(channel_1_1), user_1) .await .unwrap(); @@ -415,7 +406,7 @@ async fn test_channel_renames(db: &Arc) { .unwrap() .user_id; - let zed_id = db.create_root_channel("zed", "1", user_1).await.unwrap(); + let zed_id = db.create_root_channel("zed", user_1).await.unwrap(); db.rename_channel(zed_id, user_1, "#zed-archive") .await @@ -460,25 +451,22 @@ async fn test_db_channel_moving(db: &Arc) { .unwrap() .user_id; - let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap(); + let zed_id = db.create_root_channel("zed", a_id).await.unwrap(); - let crdb_id = db - .create_channel("crdb", Some(zed_id), "2", a_id) - .await - .unwrap(); + let crdb_id = db.create_channel("crdb", Some(zed_id), a_id).await.unwrap(); let gpui2_id = db - .create_channel("gpui2", Some(zed_id), "3", a_id) + .create_channel("gpui2", Some(zed_id), a_id) .await .unwrap(); let livestreaming_id = db - .create_channel("livestreaming", Some(crdb_id), "4", a_id) + .create_channel("livestreaming", Some(crdb_id), a_id) .await .unwrap(); let livestreaming_dag_id = db - .create_channel("livestreaming_dag", Some(livestreaming_id), "5", a_id) + .create_channel("livestreaming_dag", Some(livestreaming_id), a_id) .await .unwrap(); @@ -531,12 +519,7 @@ async fn test_db_channel_moving(db: &Arc) { // ======================================================================== // Create a new channel below a channel with multiple parents let livestreaming_dag_sub_id = db - .create_channel( - "livestreaming_dag_sub", - Some(livestreaming_dag_id), - "6", - a_id, - ) + .create_channel("livestreaming_dag_sub", Some(livestreaming_dag_id), a_id) .await .unwrap(); @@ -826,15 +809,15 @@ async fn test_db_channel_moving_bugs(db: &Arc) { .unwrap() .user_id; - let zed_id = db.create_root_channel("zed", "1", user_id).await.unwrap(); + let zed_id = db.create_root_channel("zed", user_id).await.unwrap(); let projects_id = db - .create_channel("projects", Some(zed_id), "2", user_id) + .create_channel("projects", Some(zed_id), user_id) .await .unwrap(); let livestreaming_id = db - .create_channel("livestreaming", Some(projects_id), "3", user_id) + .create_channel("livestreaming", Some(projects_id), user_id) .await .unwrap(); diff --git a/crates/collab/src/db/tests/message_tests.rs b/crates/collab/src/db/tests/message_tests.rs index 464aaba2073bfd11d22d8565d7eeb6ce006a6290..e758fcfb5d0104a61ad84dd82ce10fad784fdf5a 100644 --- a/crates/collab/src/db/tests/message_tests.rs +++ b/crates/collab/src/db/tests/message_tests.rs @@ -25,10 +25,7 @@ async fn test_channel_message_retrieval(db: &Arc) { .await .unwrap() .user_id; - let channel = db - .create_channel("channel", None, "room", user) - .await - .unwrap(); + let channel = db.create_channel("channel", None, user).await.unwrap(); let owner_id = db.create_server("test").await.unwrap().0 as u32; db.join_channel_chat(channel, rpc::ConnectionId { owner_id, id: 0 }, user) @@ -90,10 +87,7 @@ async fn test_channel_message_nonces(db: &Arc) { .await .unwrap() .user_id; - let channel = db - .create_channel("channel", None, "room", user) - .await - .unwrap(); + let channel = db.create_channel("channel", None, user).await.unwrap(); let owner_id = db.create_server("test").await.unwrap().0 as u32; @@ -157,15 +151,9 @@ async fn test_channel_message_new_notification(db: &Arc) { .unwrap() .user_id; - let channel_1 = db - .create_channel("channel", None, "room", user) - .await - .unwrap(); + let channel_1 = db.create_channel("channel", None, user).await.unwrap(); - let channel_2 = db - .create_channel("channel-2", None, "room", user) - .await - .unwrap(); + let channel_2 = db.create_channel("channel-2", None, user).await.unwrap(); db.invite_channel_member(channel_1, observer, user, false) .await diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 268228077fb45a7080ca4e1754302a81f6fc2600..e5c6d94ce03b8b3b1d64ed58be4da53f9dcca112 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -938,11 +938,6 @@ async fn create_room( util::async_iife!({ let live_kit = live_kit?; - live_kit - .create_room(live_kit_room.clone()) - .await - .trace_err()?; - let token = live_kit .room_token(&live_kit_room, &session.user_id.to_string()) .trace_err()?; @@ -2206,15 +2201,10 @@ async fn create_channel( session: Session, ) -> Result<()> { let db = session.db().await; - let live_kit_room = format!("channel-{}", nanoid::nanoid!(30)); - - if let Some(live_kit) = session.live_kit_client.as_ref() { - live_kit.create_room(live_kit_room.clone()).await?; - } let parent_id = request.parent_id.map(|id| ChannelId::from_proto(id)); let id = db - .create_channel(&request.name, parent_id, &live_kit_room, session.user_id) + .create_channel(&request.name, parent_id, session.user_id) .await?; let channel = proto::Channel { @@ -2619,12 +2609,15 @@ async fn join_channel( session: Session, ) -> Result<()> { let channel_id = ChannelId::from_proto(request.channel_id); + let live_kit_room = format!("channel-{}", nanoid::nanoid!(30)); let joined_room = { leave_room_for_session(&session).await?; let db = session.db().await; - let room_id = db.room_id_for_channel(channel_id).await?; + let room_id = db + .get_or_create_channel_room(channel_id, &live_kit_room, &*RELEASE_CHANNEL_NAME) + .await?; let joined_room = db .join_room( diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 6bdcee6af3eebc5a885f7a60b780b9f13e7ca0a3..7cfcce832b4cd4e05a953156828e517ef85af9fe 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -380,6 +380,8 @@ async fn test_channel_room( // Give everyone a chance to observe user A joining deterministic.run_until_parked(); + let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone()); + room_a.read_with(cx_a, |room, _| assert!(room.is_connected())); client_a.channel_store().read_with(cx_a, |channels, _| { assert_participants_eq( diff --git a/crates/collab/src/tests/random_channel_buffer_tests.rs b/crates/collab/src/tests/random_channel_buffer_tests.rs index ad0181602c9ac3bd5ab25d6029ad84d7ba74ce3e..6e0bef225c9fc2d3bc78faa5ddca6e65e28f5495 100644 --- a/crates/collab/src/tests/random_channel_buffer_tests.rs +++ b/crates/collab/src/tests/random_channel_buffer_tests.rs @@ -46,12 +46,7 @@ impl RandomizedTest for RandomChannelBufferTest { let db = &server.app_state.db; for ix in 0..CHANNEL_COUNT { let id = db - .create_channel( - &format!("channel-{ix}"), - None, - &format!("livekit-room-{ix}"), - users[0].user_id, - ) + .create_channel(&format!("channel-{ix}"), None, users[0].user_id) .await .unwrap(); for user in &users[1..] { diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 704760bab7f42b9e07687fc52bc69a34173e5170..8df8ab4abb6142fea292aec7f3377e86e93cb388 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -91,9 +91,8 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); - let room = server_rooms - .get_mut(&*room_name) - .ok_or_else(|| anyhow!("room {:?} does not exist", room_name))?; + let room = (*server_rooms).entry(room_name.to_string()).or_default(); + if room.client_rooms.contains_key(&identity) { Err(anyhow!( "{:?} attempted to join room {:?} twice", From e6228ca682156f28476338096aab3f5cb9e214d9 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 10 Oct 2023 16:04:31 -0400 Subject: [PATCH 058/180] Slim down pull request template --- .github/pull_request_template.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2cd2050013f7639ff3d3a4ea10379584f0e5f387..147402b2858dce92355d91e9bd11e20cb43293fc 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,11 +2,4 @@ Release Notes: -- N/A - -or - - (Added|Fixed|Improved) ... ([#](https://github.com/zed-industries/community/issues/)). - -If the release notes are only intended for a specific release channel only, add `(-only)` to the end of the release note line. -These will be removed by the person making the release. From d7d027bcf1cd8b87232c94654d1f6c885d84c814 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 10 Oct 2023 13:23:03 -0700 Subject: [PATCH 059/180] Rename release channel to enviroment --- .../20221109000000_test_schema.sql | 2 +- ...20231009181554_add_release_channel_to_rooms.sql | 2 +- crates/collab/src/db/queries/channels.rs | 2 +- crates/collab/src/db/queries/rooms.rs | 14 +++++++------- crates/collab/src/db/tables/room.rs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index e5104839b8f358b8db617516eb60d32a0cd25d27..5a84bfd796a88e09769004cc40d0cc6a06e3118a 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -37,7 +37,7 @@ CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b"); CREATE TABLE "rooms" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "live_kit_room" VARCHAR NOT NULL, - "release_channel" VARCHAR, + "enviroment" VARCHAR, "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE ); CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); diff --git a/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql index 95d3c400fcf6fc309fe81a690cf2ed3a16f5017a..8f3a704adde0c385b26bd553d273eff322a17702 100644 --- a/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql +++ b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql @@ -1 +1 @@ -ALTER TABLE rooms ADD COLUMN release_channel TEXT; +ALTER TABLE rooms ADD COLUMN enviroment TEXT; diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index b2fa2eb9b5b6e7a9d4dd7499c9eb3e29bfb7866a..c576d2406b81279c38561406c3801c02ddaf4377 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -802,7 +802,7 @@ impl Database { let result = room::Entity::insert(room::ActiveModel { channel_id: ActiveValue::Set(Some(channel_id)), live_kit_room: ActiveValue::Set(live_kit_room.to_string()), - release_channel: ActiveValue::Set(Some(enviroment.to_string())), + enviroment: ActiveValue::Set(Some(enviroment.to_string())), ..Default::default() }) .exec(&*tx) diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 4763b3f5cd4ff1eec6b5d83f36fa9b3c41202cd9..a38c77dc0fc67eab6ea18da14dad43dd612bf69d 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -112,7 +112,7 @@ impl Database { self.transaction(|tx| async move { let room = room::ActiveModel { live_kit_room: ActiveValue::set(live_kit_room.into()), - release_channel: ActiveValue::set(Some(release_channel.to_string())), + enviroment: ActiveValue::set(Some(release_channel.to_string())), ..Default::default() } .insert(&*tx) @@ -272,28 +272,28 @@ impl Database { room_id: RoomId, user_id: UserId, connection: ConnectionId, - collab_release_channel: &str, + enviroment: &str, ) -> Result> { self.room_transaction(room_id, |tx| async move { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] - enum QueryChannelIdAndReleaseChannel { + enum QueryChannelIdAndEnviroment { ChannelId, - ReleaseChannel, + Enviroment, } let (channel_id, release_channel): (Option, Option) = room::Entity::find() .select_only() .column(room::Column::ChannelId) - .column(room::Column::ReleaseChannel) + .column(room::Column::Enviroment) .filter(room::Column::Id.eq(room_id)) - .into_values::<_, QueryChannelIdAndReleaseChannel>() + .into_values::<_, QueryChannelIdAndEnviroment>() .one(&*tx) .await? .ok_or_else(|| anyhow!("no such room"))?; if let Some(release_channel) = release_channel { - if &release_channel != collab_release_channel { + if &release_channel != enviroment { Err(anyhow!("must join using the {} release", release_channel))?; } } diff --git a/crates/collab/src/db/tables/room.rs b/crates/collab/src/db/tables/room.rs index 7f31edcdbd96bf453271895b63636625f7d566db..4150c741ac19ef39e09c19116ba3bca819e24a3f 100644 --- a/crates/collab/src/db/tables/room.rs +++ b/crates/collab/src/db/tables/room.rs @@ -8,7 +8,7 @@ pub struct Model { pub id: RoomId, pub live_kit_room: String, pub channel_id: Option, - pub release_channel: Option, + pub enviroment: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] From 96d60eff237ffd357b74ee53a154dd4f6e151b77 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Oct 2023 15:40:40 -0700 Subject: [PATCH 060/180] Fix inclusion of spurious views from other projects in FollowResponse --- crates/workspace/src/workspace.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c3c6f9a4b6fe919a4338d877f29a9ee134101673..8b068fa10cf984a5301cd9e6d7d73dc118218f6f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2878,8 +2878,7 @@ impl Workspace { let cx = &cx; move |item| { let item = item.to_followable_item_handle(cx)?; - if project_id.is_some() - && project_id != follower_project_id + if (project_id.is_none() || project_id != follower_project_id) && item.is_project_item(cx) { return None; From 1de9add304f2d2e787becd3fe49f656d9e45561b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 10 Oct 2023 18:46:49 -0600 Subject: [PATCH 061/180] vim: Add shift-y --- assets/keymaps/vim.json | 1 + crates/vim/src/normal.rs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 07ba8a121f3ee45132c4354c7a072338273b2692..ea025747d8da99128a3d465d46fff5a26e7fb7e3 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -408,6 +408,7 @@ "vim::PushOperator", "Yank" ], + "shift-y": "vim::YankLine", "i": "vim::InsertBefore", "shift-i": "vim::InsertFirstNonWhitespace", "a": "vim::InsertAfter", diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 36eab2c4c0be91eda40868a8b773237ce008ae87..c8b517edd0f7dae65a5109cf1179c67df504da59 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -46,6 +46,7 @@ actions!( ChangeToEndOfLine, DeleteToEndOfLine, Yank, + YankLine, ChangeCase, JoinLines, ] @@ -66,6 +67,7 @@ pub fn init(cx: &mut AppContext) { cx.add_action(insert_line_above); cx.add_action(insert_line_below); cx.add_action(change_case); + cx.add_action(yank_line); cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| { Vim::update(cx, |vim, cx| { @@ -308,6 +310,13 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex }); } +fn yank_line(_: &mut Workspace, _: &YankLine, cx: &mut ViewContext) { + Vim::update(cx, |vim, cx| { + let count = vim.take_count(cx); + yank_motion(vim, motion::Motion::CurrentLine, count, cx) + }) +} + pub(crate) fn normal_replace(text: Arc, cx: &mut WindowContext) { Vim::update(cx, |vim, cx| { vim.stop_recording(); From 821997d3721911cdad9b2ed6c8537f38adc0e347 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 10 Oct 2023 19:59:57 -0600 Subject: [PATCH 062/180] Revert accidental build change --- .cargo/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index e22bdb0f2c70a1ffda714674253cc533e9e7c1d1..9da6b3be080072d89d16a199e2d60d527eeacd07 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] +rustflags = ["-C", "symbol-mangling-version=v0"] From 76191fe47de407754c093053a5c39cda89f1590c Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 11 Oct 2023 01:54:32 -0400 Subject: [PATCH 063/180] Fix Discord text truncation --- .github/workflows/release_actions.yml | 36 +++++++++++---------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 54e5d07787faf6e849b0852ec01a7b404215502e..c1df24a8e5cb98bdbea594fecc0fd4090e222877 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -6,34 +6,28 @@ jobs: discord_release: runs-on: ubuntu-latest steps: - - name: Get appropriate URL - id: get-appropriate-url + - name: Get release URL + id: get-release-url run: | if [ "${{ github.event.release.prerelease }}" == "true" ]; then - url="https://zed.dev/releases/preview/latest" + URL="https://zed.dev/releases/preview/latest" else - url="https://zed.dev/releases/stable/latest" - fi - echo "::set-output name=url::$url" - - - name: Prepare release content - id: prepare-content - run: | - set -eu - - text="📣 Zed ${{ github.event.release.tag_name }} was just released!\n\nRestart your Zed or head to ${{ steps.get-appropriate-url.outputs.URL }} to grab it.\n\n${{ github.event.release.body }}" - - maxTextLength=2000 - truncationIndicator="..." - - if (( ${#text} > maxTextLength )); then - text=${text:0:maxTextLength - ${#truncationIndicator}}$truncationIndicator + URL="https://zed.dev/releases/stable/latest" fi + echo "::set-output name=URL::$URL" + - name: Get content + uses: 2428392/gh-truncate-string-action@v1.2.0 + id: get-content + with: + stringToTruncate: | + 📣 Zed ${{ github.event.release.tag_name }} was just released! - echo "::set-output name=content::$text" + Restart your Zed or head to ${{ steps.get-release-url.outputs.URL }} to grab it. + ${{ github.event.release.body }} + maxLength: 2000 - name: Discord Webhook Action uses: tsickert/discord-webhook@v5.3.0 with: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} - content: ${{ steps.prepare-content.outputs.content }} + content: ${{ steps.get-content.outputs.string }} From eced842dfc281dc24dcb8b3cd9ed4511d7102376 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 22 Aug 2023 16:12:59 -0400 Subject: [PATCH 064/180] Get started with a prettier server package Co-Authored-By: Antonio Scandurra --- Cargo.lock | 4 ++ Cargo.toml | 1 + crates/prettier/Cargo.toml | 9 +++ .../prettier_server/.zed/settings.json | 7 +++ crates/prettier/prettier_server/package.json | 11 ++++ crates/prettier/prettier_server/src/index.js | 56 +++++++++++++++++++ crates/prettier/src/prettier.rs | 14 +++++ 7 files changed, 102 insertions(+) create mode 100644 crates/prettier/Cargo.toml create mode 100644 crates/prettier/prettier_server/.zed/settings.json create mode 100644 crates/prettier/prettier_server/package.json create mode 100644 crates/prettier/prettier_server/src/index.js create mode 100644 crates/prettier/src/prettier.rs diff --git a/Cargo.lock b/Cargo.lock index cd36221de00537800e22e3109c1453089ca45244..8b1278b3dccef59d44ba6671516aeb369edf0c1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5517,6 +5517,10 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettier" +version = "0.1.0" + [[package]] name = "pretty_assertions" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 7dae3bd81f7213ad34f5b51621471678436d6161..25aec39cdd822c90e018cc4504b15075b9eb7ad2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ members = [ "crates/plugin", "crates/plugin_macros", "crates/plugin_runtime", + "crates/prettier", "crates/project", "crates/project_panel", "crates/project_symbols", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..798899df0eb1fd7639ece0b8cffbab1ecdc25a06 --- /dev/null +++ b/crates/prettier/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "prettier" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[lib] +path = "src/prettier.rs" diff --git a/crates/prettier/prettier_server/.zed/settings.json b/crates/prettier/prettier_server/.zed/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..856536c62db360e7ab69b2b356167826f64c47d5 --- /dev/null +++ b/crates/prettier/prettier_server/.zed/settings.json @@ -0,0 +1,7 @@ +{ + "languages": { + "JavaScript": { + "tab_size": 4 + } + } +} diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json new file mode 100644 index 0000000000000000000000000000000000000000..70c834b37e1aab777b4d19126ff179f317842651 --- /dev/null +++ b/crates/prettier/prettier_server/package.json @@ -0,0 +1,11 @@ +{ + "name": "prettier_server", + "version": "1.0.0", + "description": "", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Zed Industries" +} diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js new file mode 100644 index 0000000000000000000000000000000000000000..c8f2f3bb37a47bebb843523c35f8e2eb0202b68f --- /dev/null +++ b/crates/prettier/prettier_server/src/index.js @@ -0,0 +1,56 @@ +const { Buffer } = require('buffer'); + +let buffer = Buffer.alloc(0); +process.stdin.resume(); +process.stdin.on('data', (data) => { + buffer = Buffer.concat([buffer, data]); + handleData(); +}); +process.stdin.on('end', () => { + handleData(); +}); + +function handleData() { + if (buffer.length < 4) { + return; + } + + const length = buffer.readUInt32LE(0); + console.log(length); + if (buffer.length < 4 + length) { + return; + } + + const bytes = buffer.subarray(4, 4 + length); + buffer = buffer.subarray(4 + length); + + try { + const message = JSON.parse(bytes); + handleMessage(message); + } catch (_) { + sendResponse(makeError("Request JSON parse error")); + return; + } +} + +// format +// clear_cache +// shutdown +// error + +function handleMessage(message) { + console.log(message); + sendResponse({ method: "hi", result: null }); +} + +function makeError(message) { + return { method: "error", message }; +} + +function sendResponse(response) { + let message = Buffer.from(JSON.stringify(response)); + let length = Buffer.alloc(4); + length.writeUInt32LE(message.length); + process.stdout.write(length); + process.stdout.write(message); +} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs new file mode 100644 index 0000000000000000000000000000000000000000..7d12d9af8195bf5e19d10c7b592b359ccd014149 --- /dev/null +++ b/crates/prettier/src/prettier.rs @@ -0,0 +1,14 @@ +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From 553abd01beded42f9fdb64feecf8dcff30eac5cb Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 1 Sep 2023 18:31:16 +0300 Subject: [PATCH 065/180] Draft a project part of the prettier --- Cargo.lock | 7 +++ crates/language/src/language_settings.rs | 7 ++- crates/prettier/Cargo.toml | 15 ++++++- crates/prettier/src/prettier.rs | 50 +++++++++++++++++---- crates/project/Cargo.toml | 1 + crates/project/src/project.rs | 56 +++++++++++++++++++++++- 6 files changed, 122 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b1278b3dccef59d44ba6671516aeb369edf0c1a..120669f03704278e13b97fb6510d0d0bc19e053d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5520,6 +5520,12 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettier" version = "0.1.0" +dependencies = [ + "anyhow", + "fs", + "gpui", + "language", +] [[package]] name = "pretty_assertions" @@ -5635,6 +5641,7 @@ dependencies = [ "lsp", "parking_lot 0.11.2", "postage", + "prettier", "pretty_assertions", "rand 0.8.5", "regex", diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index c3f706802a5c15446304e490d70cd14f6c7f2c86..8778ba8d6405cc415381f5153e7afecd03c8efa8 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -149,10 +149,15 @@ pub enum ShowWhitespaceSetting { All, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Formatter { + #[default] + Auto, LanguageServer, + Prettier { + config: (), // Support some of the most important settings in the prettier-vscode extension. + }, External { command: Arc, arguments: Arc<[String]>, diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 798899df0eb1fd7639ece0b8cffbab1ecdc25a06..c2e4b2a3cc51138e0d923c9e7c65aee48ab3e8d0 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -3,7 +3,18 @@ name = "prettier" version = "0.1.0" edition = "2021" -[dependencies] - [lib] path = "src/prettier.rs" + +[dependencies] +language = { path = "../language" } +gpui = { path = "../gpui" } +fs = { path = "../fs" } + +anyhow.workspace = true + + +[dev-dependencies] +language = { path = "../language", features = ["test-support"] } +gpui = { path = "../gpui", features = ["test-support"] } +fs = { path = "../fs", features = ["test-support"] } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 7d12d9af8195bf5e19d10c7b592b359ccd014149..9b1b9c1e9d973a4b5bb99a2a12bf56a449b69cdc 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,14 +1,46 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right +pub use std::path::{Path, PathBuf}; +pub use std::sync::Arc; + +use fs::Fs; +use gpui::ModelHandle; +use language::{Buffer, Diff}; + +pub struct Prettier { + _private: (), } -#[cfg(test)] -mod tests { - use super::*; +type NodeRuntime = (); + +impl Prettier { + // This was taken from the prettier-vscode extension. + pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ + ".prettierrc", + ".prettierrc.json", + ".prettierrc.json5", + ".prettierrc.yaml", + ".prettierrc.yml", + ".prettierrc.toml", + ".prettierrc.js", + ".prettierrc.cjs", + "package.json", + "prettier.config.js", + "prettier.config.cjs", + ".editorconfig", + ]; + + pub async fn locate(starting_path: Option<&Path>, fs: Arc) -> PathBuf { + todo!() + } + + pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { + todo!() + } + + pub async fn format(&self, buffer: &ModelHandle) -> anyhow::Result { + todo!() + } - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + pub async fn clear_cache(&self) -> anyhow::Result<()> { + todo!() } } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index ffea6646e9439a24cd7e09fd9666ee9eabfcbf49..c22193f13090c70e54b8df87b58095d00c30a5f3 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -31,6 +31,7 @@ git = { path = "../git" } gpui = { path = "../gpui" } language = { path = "../language" } lsp = { path = "../lsp" } +prettier = { path = "../prettier" } rpc = { path = "../rpc" } settings = { path = "../settings" } sum_tree = { path = "../sum_tree" } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a50e02a631ccda81f1d40c49cc3361c1626a383d..42b0a13c518c70053f2cb6dbeaa68d9d77a13ebf 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -50,6 +50,7 @@ use lsp::{ }; use lsp_command::*; use postage::watch; +use prettier::Prettier; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -152,6 +153,7 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, + prettier_instances: HashMap<(WorktreeId, PathBuf), Shared>>>>, } struct DelayedDebounced { @@ -660,6 +662,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + prettier_instances: HashMap::default(), } }) } @@ -757,6 +760,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + prettier_instances: HashMap::default(), }; for worktree in worktrees { let _ = this.add_worktree(&worktree, cx); @@ -4027,6 +4031,7 @@ impl Project { enum FormatOperation { Lsp(Vec<(Range, String)>), External(Diff), + Prettier(Diff), } // Apply language-specific formatting using either a language server @@ -4062,8 +4067,8 @@ impl Project { | (_, FormatOnSave::External { command, arguments }) => { if let Some(buffer_abs_path) = buffer_abs_path { format_operation = Self::format_via_external_command( - &buffer, - &buffer_abs_path, + buffer, + buffer_abs_path, &command, &arguments, &mut cx, @@ -4076,6 +4081,45 @@ impl Project { .map(FormatOperation::External); } } + (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => { + if let Some(prettier) = this.update(&mut cx, |project, _| { + project.prettier_instance_for_buffer(buffer) + }) { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("autoformatting via prettier")?, + )); + } else if let Some((language_server, buffer_abs_path)) = + language_server.as_ref().zip(buffer_abs_path.as_ref()) + { + format_operation = Some(FormatOperation::Lsp( + Self::format_via_lsp( + &this, + &buffer, + buffer_abs_path, + &language_server, + tab_size, + &mut cx, + ) + .await + .context("failed to format via language server")?, + )); + } + } + (Formatter::Prettier { .. }, FormatOnSave::On | FormatOnSave::Off) => { + if let Some(prettier) = this.update(&mut cx, |project, _| { + project.prettier_instance_for_buffer(buffer) + }) { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("formatting via prettier")?, + )); + } + } }; buffer.update(&mut cx, |b, cx| { @@ -4100,6 +4144,9 @@ impl Project { FormatOperation::External(diff) => { b.apply_diff(diff, cx); } + FormatOperation::Prettier(diff) => { + b.apply_diff(diff, cx); + } } if let Some(transaction_id) = whitespace_transaction_id { @@ -8109,6 +8156,11 @@ impl Project { Vec::new() } } + + fn prettier_instance_for_buffer(&self, buffer: &ModelHandle) -> Option { + // TODO kb + None + } } fn subscribe_for_copilot_events( From 92f23e626eba18fb4653a653cd600e11ada45c90 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 5 Sep 2023 15:51:46 +0300 Subject: [PATCH 066/180] Properly connect prettier lookup/creation methods --- crates/prettier/src/prettier.rs | 2 +- crates/project/src/project.rs | 115 +++++++++++++++++++++++++------- 2 files changed, 93 insertions(+), 24 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 9b1b9c1e9d973a4b5bb99a2a12bf56a449b69cdc..ecd4376477d6f6d7804a86195261cb9306b25720 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -9,7 +9,7 @@ pub struct Prettier { _private: (), } -type NodeRuntime = (); +pub struct NodeRuntime; impl Prettier { // This was taken from the prettier-vscode extension. diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 42b0a13c518c70053f2cb6dbeaa68d9d77a13ebf..d24fe17380c5a2c9f0f503b53a0200e9767233a0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -50,7 +50,7 @@ use lsp::{ }; use lsp_command::*; use postage::watch; -use prettier::Prettier; +use prettier::{NodeRuntime, Prettier}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -153,7 +153,10 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, - prettier_instances: HashMap<(WorktreeId, PathBuf), Shared>>>>, + prettier_instances: HashMap< + (Option, PathBuf), + Shared, Arc>>>, + >, } struct DelayedDebounced { @@ -3953,7 +3956,7 @@ impl Project { push_to_history: bool, trigger: FormatTrigger, cx: &mut ModelContext, - ) -> Task> { + ) -> Task> { if self.is_local() { let mut buffers_with_paths_and_servers = buffers .into_iter() @@ -4082,15 +4085,26 @@ impl Project { } } (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => { - if let Some(prettier) = this.update(&mut cx, |project, _| { - project.prettier_instance_for_buffer(buffer) - }) { - format_operation = Some(FormatOperation::Prettier( - prettier - .format(buffer) + if let Some(prettier_task) = this + .update(&mut cx, |project, cx| { + project.prettier_instance_for_buffer(buffer, cx) + }) { + match prettier_task .await - .context("autoformatting via prettier")?, - )); + .await + { + Ok(prettier) => { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("formatting via prettier")?, + )); + } + Err(e) => anyhow::bail!( + "Failed to create prettier instance for buffer during autoformatting: {e:#}" + ), + } } else if let Some((language_server, buffer_abs_path)) = language_server.as_ref().zip(buffer_abs_path.as_ref()) { @@ -4109,16 +4123,27 @@ impl Project { } } (Formatter::Prettier { .. }, FormatOnSave::On | FormatOnSave::Off) => { - if let Some(prettier) = this.update(&mut cx, |project, _| { - project.prettier_instance_for_buffer(buffer) - }) { - format_operation = Some(FormatOperation::Prettier( - prettier - .format(buffer) + if let Some(prettier_task) = this + .update(&mut cx, |project, cx| { + project.prettier_instance_for_buffer(buffer, cx) + }) { + match prettier_task .await - .context("formatting via prettier")?, - )); - } + .await + { + Ok(prettier) => { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("formatting via prettier")?, + )); + } + Err(e) => anyhow::bail!( + "Failed to create prettier instance for buffer during formatting: {e:#}" + ), + } + } } }; @@ -8157,9 +8182,53 @@ impl Project { } } - fn prettier_instance_for_buffer(&self, buffer: &ModelHandle) -> Option { - // TODO kb - None + fn prettier_instance_for_buffer( + &mut self, + buffer: &ModelHandle, + cx: &mut ModelContext, + ) -> Option, Arc>>>>> { + let buffer_file = File::from_dyn(buffer.read(cx).file()); + let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); + let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); + + // TODO kb return None if config opted out of prettier + + let task = cx.spawn(|this, mut cx| async move { + let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); + // TODO kb can we have a cache for this instead? + let prettier_path = Prettier::locate(buffer_path.as_deref(), fs).await; + if let Some(existing_prettier) = this.update(&mut cx, |project, _| { + project + .prettier_instances + .get(&(worktree_id, prettier_path.clone())) + .cloned() + }) { + return existing_prettier; + } + + let task_node_runtime = Arc::new(NodeRuntime); + let task_prettier_path = prettier_path.clone(); + let new_prettier_task = cx + .background() + .spawn(async move { + Ok(Arc::new( + Prettier::start(&task_prettier_path, task_node_runtime) + .await + .with_context(|| { + format!("starting new prettier for path {task_prettier_path:?}") + })?, + )) + .map_err(Arc::new) + }) + .shared(); + this.update(&mut cx, |project, _| { + project + .prettier_instances + .insert((worktree_id, prettier_path), new_prettier_task.clone()); + }); + new_prettier_task + }); + Some(task) } } From a8dfa013621833d342902ed887f0e5c49da19bde Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 5 Sep 2023 17:09:22 +0300 Subject: [PATCH 067/180] Prepare prettier file lookup code infra --- Cargo.lock | 1 + assets/settings/default.json | 3 +- crates/fs/src/fs.rs | 5 +- crates/prettier/Cargo.toml | 2 +- crates/prettier/src/prettier.rs | 111 +++++++++++++++++++++++++++++++- crates/project/src/project.rs | 51 ++++++++++++++- 6 files changed, 165 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 120669f03704278e13b97fb6510d0d0bc19e053d..a0daee93055c2f3e04cc6369cfd1829210562f6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5523,6 +5523,7 @@ version = "0.1.0" dependencies = [ "anyhow", "fs", + "futures 0.3.28", "gpui", "language", ] diff --git a/assets/settings/default.json b/assets/settings/default.json index cc724657c0c20411b17d0ad0ea293b0a08ddc6f5..be47ac9c8c1b91e90df800a79367cf4ebcd5ac02 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -199,7 +199,8 @@ // "arguments": ["--stdin-filepath", "{buffer_path}"] // } // } - "formatter": "language_server", + // TODO kb description + "formatter": "auto", // How to soft-wrap long lines of text. This setting can take // three values: // diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 1d95db9b6cf20ddbe1cf87c5c94c73eb0e666d62..bb5d6387e03a69db37c17248494e1edf9d2f4a5b 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -85,7 +85,7 @@ pub struct RemoveOptions { pub ignore_if_not_exists: bool, } -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Debug)] pub struct Metadata { pub inode: u64, pub mtime: SystemTime, @@ -229,11 +229,12 @@ impl Fs for RealFs { } else { symlink_metadata }; + let file_type_metadata = metadata.file_type(); Ok(Some(Metadata { inode: metadata.ino(), mtime: metadata.modified().unwrap(), is_symlink, - is_dir: metadata.file_type().is_dir(), + is_dir: file_type_metadata.is_dir(), })) } diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index c2e4b2a3cc51138e0d923c9e7c65aee48ab3e8d0..821cde7b3a87314218ff1e3934d29e1c60054054 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -12,7 +12,7 @@ gpui = { path = "../gpui" } fs = { path = "../fs" } anyhow.workspace = true - +futures.workspace = true [dev-dependencies] language = { path = "../language", features = ["test-support"] } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index ecd4376477d6f6d7804a86195261cb9306b25720..a38f8f8651bbb90ff07728160c3078160c86ca40 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,6 +1,8 @@ +use std::collections::VecDeque; pub use std::path::{Path, PathBuf}; pub use std::sync::Arc; +use anyhow::Context; use fs::Fs; use gpui::ModelHandle; use language::{Buffer, Diff}; @@ -11,6 +13,12 @@ pub struct Prettier { pub struct NodeRuntime; +#[derive(Debug)] +pub struct LocateStart { + pub worktree_root_path: Arc, + pub starting_path: Arc, +} + impl Prettier { // This was taken from the prettier-vscode extension. pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ @@ -28,8 +36,107 @@ impl Prettier { ".editorconfig", ]; - pub async fn locate(starting_path: Option<&Path>, fs: Arc) -> PathBuf { - todo!() + pub async fn locate( + starting_path: Option, + fs: Arc, + ) -> anyhow::Result { + let paths_to_check = match starting_path { + Some(starting_path) => { + let worktree_root = starting_path + .worktree_root_path + .components() + .into_iter() + .take_while(|path_component| { + path_component.as_os_str().to_str() != Some("node_modules") + }) + .collect::(); + + if worktree_root != starting_path.worktree_root_path.as_ref() { + vec![worktree_root] + } else { + let (worktree_root_metadata, start_path_metadata) = if starting_path + .starting_path + .as_ref() + == Path::new("") + { + let worktree_root_data = + fs.metadata(&worktree_root).await.with_context(|| { + format!( + "FS metadata fetch for worktree root path {worktree_root:?}", + ) + })?; + (worktree_root_data.unwrap_or_else(|| { + panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}") + }), None) + } else { + let full_starting_path = worktree_root.join(&starting_path.starting_path); + let (worktree_root_data, start_path_data) = futures::try_join!( + fs.metadata(&worktree_root), + fs.metadata(&full_starting_path), + ) + .with_context(|| { + format!("FS metadata fetch for starting path {full_starting_path:?}",) + })?; + ( + worktree_root_data.unwrap_or_else(|| { + panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}") + }), + start_path_data, + ) + }; + + match start_path_metadata { + Some(start_path_metadata) => { + anyhow::ensure!(worktree_root_metadata.is_dir, + "For non-empty start path, worktree root {starting_path:?} should be a directory"); + anyhow::ensure!( + !start_path_metadata.is_dir, + "For non-empty start path, it should not be a directory {starting_path:?}" + ); + anyhow::ensure!( + !start_path_metadata.is_symlink, + "For non-empty start path, it should not be a symlink {starting_path:?}" + ); + + let file_to_format = starting_path.starting_path.as_ref(); + let mut paths_to_check = VecDeque::from(vec![worktree_root.clone()]); + let mut current_path = worktree_root; + for path_component in file_to_format.components().into_iter() { + current_path = current_path.join(path_component); + paths_to_check.push_front(current_path.clone()); + if path_component.as_os_str().to_str() == Some("node_modules") { + break; + } + } + paths_to_check.pop_front(); // last one is the file itself or node_modules, skip it + Vec::from(paths_to_check) + } + None => { + anyhow::ensure!( + !worktree_root_metadata.is_dir, + "For empty start path, worktree root should not be a directory {starting_path:?}" + ); + anyhow::ensure!( + !worktree_root_metadata.is_symlink, + "For empty start path, worktree root should not be a symlink {starting_path:?}" + ); + worktree_root + .parent() + .map(|path| vec![path.to_path_buf()]) + .unwrap_or_default() + } + } + } + } + None => Vec::new(), + }; + + if dbg!(paths_to_check).is_empty() { + // TODO kb return the default prettier, how, without state? + } else { + // TODO kb now check all paths to check for prettier + } + Ok(PathBuf::new()) } pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d24fe17380c5a2c9f0f503b53a0200e9767233a0..6538a2654003f4f3371f0afb556d055bc7dbc3fb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -50,7 +50,7 @@ use lsp::{ }; use lsp_command::*; use postage::watch; -use prettier::{NodeRuntime, Prettier}; +use prettier::{LocateStart, NodeRuntime, Prettier}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -8189,14 +8189,61 @@ impl Project { ) -> Option, Arc>>>>> { let buffer_file = File::from_dyn(buffer.read(cx).file()); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); + let worktree_path = buffer_file + .as_ref() + .map(|file| file.worktree.read(cx).abs_path()); let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); // TODO kb return None if config opted out of prettier + if true { + let fs = Arc::clone(&self.fs); + let buffer_path = buffer_path.clone(); + let worktree_path = worktree_path.clone(); + cx.spawn(|_, _| async move { + let prettier_path = Prettier::locate( + worktree_path + .zip(buffer_path) + .map(|(worktree_root_path, starting_path)| { + dbg!(LocateStart { + worktree_root_path, + starting_path, + }) + }), + fs, + ) + .await + .unwrap(); + dbg!(prettier_path); + }) + .detach(); + return None; + } let task = cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); // TODO kb can we have a cache for this instead? - let prettier_path = Prettier::locate(buffer_path.as_deref(), fs).await; + let prettier_path = match cx + .background() + .spawn(Prettier::locate( + worktree_path + .zip(buffer_path) + .map(|(worktree_root_path, starting_path)| LocateStart { + worktree_root_path, + starting_path, + }), + fs, + )) + .await + { + Ok(path) => path, + Err(e) => { + return Task::Ready(Some(Result::Err(Arc::new( + e.context("determining prettier path for worktree {worktree_path:?}"), + )))) + .shared(); + } + }; + if let Some(existing_prettier) = this.update(&mut cx, |project, _| { project .prettier_instances From a420d9cdc73738a38e380671364d05f1c829bb12 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 6 Sep 2023 13:57:50 +0300 Subject: [PATCH 068/180] Add prettier search --- Cargo.lock | 3 ++ crates/prettier/Cargo.toml | 3 ++ crates/prettier/src/prettier.rs | 67 +++++++++++++++++++++++++++++---- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0daee93055c2f3e04cc6369cfd1829210562f6d..9761e040d37926e5ce05d5fd2c519be0858fc69b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5526,6 +5526,9 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "serde", + "serde_derive", + "serde_json", ] [[package]] diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 821cde7b3a87314218ff1e3934d29e1c60054054..77a845b5b380d5e4281ade7a74e529a53d4191fb 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -11,6 +11,9 @@ language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } +serde.workspace = true +serde_derive.workspace = true +serde_json.workspace = true anyhow.workspace = true futures.workspace = true diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index a38f8f8651bbb90ff07728160c3078160c86ca40..ca53bb7a03a8ac2ad2c84fbc865b1a30507ba331 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,4 +1,4 @@ -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; pub use std::path::{Path, PathBuf}; pub use std::sync::Arc; @@ -40,7 +40,7 @@ impl Prettier { starting_path: Option, fs: Arc, ) -> anyhow::Result { - let paths_to_check = match starting_path { + let paths_to_check = match starting_path.as_ref() { Some(starting_path) => { let worktree_root = starting_path .worktree_root_path @@ -131,12 +131,16 @@ impl Prettier { None => Vec::new(), }; - if dbg!(paths_to_check).is_empty() { - // TODO kb return the default prettier, how, without state? - } else { - // TODO kb now check all paths to check for prettier + match find_closest_prettier_path(paths_to_check, fs.as_ref()) + .await + .with_context(|| format!("Finding prettier starting with {starting_path:?}"))? + { + Some(prettier_path) => Ok(prettier_path), + None => { + // TODO kb return the default prettier, how, without state? + Ok(PathBuf::new()) + } } - Ok(PathBuf::new()) } pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { @@ -151,3 +155,52 @@ impl Prettier { todo!() } } + +const PRETTIER_PACKAGE_NAME: &str = "prettier"; +async fn find_closest_prettier_path( + paths_to_check: Vec, + fs: &dyn Fs, +) -> anyhow::Result> { + for path in paths_to_check { + let possible_package_json = path.join("package.json"); + if let Some(package_json_metadata) = fs + .metadata(&path) + .await + .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))? + { + if !package_json_metadata.is_dir && !package_json_metadata.is_symlink { + let package_json_contents = fs + .load(&possible_package_json) + .await + .with_context(|| format!("reading {possible_package_json:?} file contents"))?; + if let Ok(json_contents) = serde_json::from_str::>( + &package_json_contents, + ) { + if let Some(serde_json::Value::Object(o)) = json_contents.get("dependencies") { + if o.contains_key(PRETTIER_PACKAGE_NAME) { + return Ok(Some(path)); + } + } + if let Some(serde_json::Value::Object(o)) = json_contents.get("devDependencies") + { + if o.contains_key(PRETTIER_PACKAGE_NAME) { + return Ok(Some(path)); + } + } + } + } + } + + let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME); + if let Some(node_modules_location_metadata) = fs + .metadata(&path) + .await + .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))? + { + if node_modules_location_metadata.is_dir { + return Ok(Some(path)); + } + } + } + Ok(None) +} From a8387b8b19e76c132e829277badeafbbc0220e22 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 6 Sep 2023 17:23:04 +0300 Subject: [PATCH 069/180] Use proper NodeRuntime in the formatter interface --- Cargo.lock | 2 ++ crates/prettier/Cargo.toml | 1 + crates/prettier/prettier_server/.gitignore | 1 + .../prettier_server/package-lock.json | 29 +++++++++++++++++++ crates/prettier/prettier_server/package.json | 21 ++++++++------ crates/prettier/prettier_server/src/index.js | 1 + crates/prettier/src/prettier.rs | 9 +++--- crates/project/Cargo.toml | 1 + crates/project/src/project.rs | 24 +++++++++++---- crates/zed/src/main.rs | 2 +- 10 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 crates/prettier/prettier_server/.gitignore create mode 100644 crates/prettier/prettier_server/package-lock.json diff --git a/Cargo.lock b/Cargo.lock index 9761e040d37926e5ce05d5fd2c519be0858fc69b..de339e15467e1297d769f145f66f3b4d441e0b3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5526,6 +5526,7 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "node_runtime", "serde", "serde_derive", "serde_json", @@ -5643,6 +5644,7 @@ dependencies = [ "lazy_static", "log", "lsp", + "node_runtime", "parking_lot 0.11.2", "postage", "prettier", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 77a845b5b380d5e4281ade7a74e529a53d4191fb..2b8bf99fc2c453b61b225a3b5e34e637691797bc 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -10,6 +10,7 @@ path = "src/prettier.rs" language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } +node_runtime = { path = "../node_runtime"} serde.workspace = true serde_derive.workspace = true diff --git a/crates/prettier/prettier_server/.gitignore b/crates/prettier/prettier_server/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c2658d7d1b31848c3b71960543cb0368e56cd4c7 --- /dev/null +++ b/crates/prettier/prettier_server/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/crates/prettier/prettier_server/package-lock.json b/crates/prettier/prettier_server/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..6d495d72b472667088ccfb55e93c881c0d46e653 --- /dev/null +++ b/crates/prettier/prettier_server/package-lock.json @@ -0,0 +1,29 @@ +{ + "name": "prettier_server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prettier_server", + "version": "1.0.0", + "dependencies": { + "prettier": "^3.0.3" + } + }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + } + } +} diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json index 70c834b37e1aab777b4d19126ff179f317842651..599f308e9f175a32f4a372800d8629d10aac80a3 100644 --- a/crates/prettier/prettier_server/package.json +++ b/crates/prettier/prettier_server/package.json @@ -1,11 +1,14 @@ { - "name": "prettier_server", - "version": "1.0.0", - "description": "", - "main": "src/index.js", - "scripts": { - "start": "node src/index.js", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Zed Industries" + "name": "prettier_server", + "version": "1.0.0", + "description": "", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Zed Industries", + "dependencies": { + "prettier": "^3.0" + } } diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index c8f2f3bb37a47bebb843523c35f8e2eb0202b68f..5ac35d7ef9c842565377030c2b45f405001093e2 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -17,6 +17,7 @@ function handleData() { const length = buffer.readUInt32LE(0); console.log(length); + console.log(buffer.toString()); if (buffer.length < 4 + length) { return; } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index ca53bb7a03a8ac2ad2c84fbc865b1a30507ba331..efb3a0fcf5e4c58b70c3a1b373bc92865e885777 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,18 +1,17 @@ use std::collections::{HashMap, VecDeque}; -pub use std::path::{Path, PathBuf}; -pub use std::sync::Arc; +use std::path::{Path, PathBuf}; +use std::sync::Arc; use anyhow::Context; use fs::Fs; use gpui::ModelHandle; use language::{Buffer, Diff}; +use node_runtime::NodeRuntime; pub struct Prettier { _private: (), } -pub struct NodeRuntime; - #[derive(Debug)] pub struct LocateStart { pub worktree_root_path: Arc, @@ -143,7 +142,7 @@ impl Prettier { } } - pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { + pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { todo!() } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index c22193f13090c70e54b8df87b58095d00c30a5f3..9f505c3fd2ddf75a4dbdcb7f0d74bfed51e4eca3 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -31,6 +31,7 @@ git = { path = "../git" } gpui = { path = "../gpui" } language = { path = "../language" } lsp = { path = "../lsp" } +node_runtime = { path = "../node_runtime" } prettier = { path = "../prettier" } rpc = { path = "../rpc" } settings = { path = "../settings" } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 6538a2654003f4f3371f0afb556d055bc7dbc3fb..b7fc1b8b34c471ab2149ac2fdb6b9e4b21303ddb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -49,8 +49,9 @@ use lsp::{ DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf, }; use lsp_command::*; +use node_runtime::NodeRuntime; use postage::watch; -use prettier::{LocateStart, NodeRuntime, Prettier}; +use prettier::{LocateStart, Prettier}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -71,7 +72,7 @@ use std::{ str, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, - Arc, + Arc, OnceLock, }, time::{Duration, Instant}, }; @@ -553,14 +554,27 @@ impl SearchMatchCandidate { } } +static NODE_RUNTIME: OnceLock> = OnceLock::new(); + impl Project { pub fn init_settings(cx: &mut AppContext) { settings::register::(cx); } - pub fn init(client: &Arc, cx: &mut AppContext) { + pub fn init( + client: &Arc, + node_runtime: Option>, + cx: &mut AppContext, + ) { Self::init_settings(cx); + // TODO kb move it to Project::local and other constructors? + if let Some(node_runtime) = node_runtime { + NODE_RUNTIME + .set(node_runtime) + .unwrap_or_else(|_| panic!("multiple init calls tried to set node runtime")); + } + client.add_model_message_handler(Self::handle_add_collaborator); client.add_model_message_handler(Self::handle_update_project_collaborator); client.add_model_message_handler(Self::handle_remove_collaborator); @@ -8187,6 +8201,7 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Option, Arc>>>>> { + let node_runtime = Arc::clone(NODE_RUNTIME.get()?); let buffer_file = File::from_dyn(buffer.read(cx).file()); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file @@ -8253,13 +8268,12 @@ impl Project { return existing_prettier; } - let task_node_runtime = Arc::new(NodeRuntime); let task_prettier_path = prettier_path.clone(); let new_prettier_task = cx .background() .spawn(async move { Ok(Arc::new( - Prettier::start(&task_prettier_path, task_node_runtime) + Prettier::start(&task_prettier_path, node_runtime) .await .with_context(|| { format!("starting new prettier for path {task_prettier_path:?}") diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f89a880c715ce645cae4dbd988051b196a7f5c7a..8093632d3a446a745d519feff98a087316141eec 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -138,7 +138,7 @@ fn main() { theme::init(Assets, cx); context_menu::init(cx); - project::Project::init(&client, cx); + project::Project::init(&client, Some(Arc::clone(&node_runtime)), cx); client::init(&client, cx); command_palette::init(cx); language::init(cx); From ce6b31d93897eb8c6059eba11ee5db0c4382c2c5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 6 Sep 2023 18:49:56 +0300 Subject: [PATCH 070/180] Make NodeRuntime non-static for prettier runner --- Cargo.lock | 2 ++ crates/collab/Cargo.toml | 1 + crates/project/src/project.rs | 37 ++++++++++++++++--------------- crates/workspace/Cargo.toml | 1 + crates/workspace/src/workspace.rs | 8 +++++++ crates/zed/src/main.rs | 10 +++++++-- 6 files changed, 39 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de339e15467e1297d769f145f66f3b4d441e0b3f..24e312803b168aefe8e30259767436626d93b38c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,6 +1501,7 @@ dependencies = [ "log", "lsp", "nanoid", + "node_runtime", "parking_lot 0.11.2", "pretty_assertions", "project", @@ -10003,6 +10004,7 @@ dependencies = [ "lazy_static", "log", "menu", + "node_runtime", "parking_lot 0.11.2", "postage", "project", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 6177c236203d6a2343a5af673d5ac78eadfd7151..cb22147b04ca8ccc17eed9583292be5d6158fc8e 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -72,6 +72,7 @@ fs = { path = "../fs", features = ["test-support"] } git = { path = "../git", features = ["test-support"] } live_kit_client = { path = "../live_kit_client", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } +node_runtime = { path = "../node_runtime" } project = { path = "../project", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b7fc1b8b34c471ab2149ac2fdb6b9e4b21303ddb..8f56a8be09c5064eae3c7af9a69aee90f9b0030a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -72,7 +72,7 @@ use std::{ str, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, - Arc, OnceLock, + Arc, }, time::{Duration, Instant}, }; @@ -154,6 +154,7 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, + node_runtime: Option>, prettier_instances: HashMap< (Option, PathBuf), Shared, Arc>>>, @@ -554,27 +555,14 @@ impl SearchMatchCandidate { } } -static NODE_RUNTIME: OnceLock> = OnceLock::new(); - impl Project { pub fn init_settings(cx: &mut AppContext) { settings::register::(cx); } - pub fn init( - client: &Arc, - node_runtime: Option>, - cx: &mut AppContext, - ) { + pub fn init(client: &Arc, cx: &mut AppContext) { Self::init_settings(cx); - // TODO kb move it to Project::local and other constructors? - if let Some(node_runtime) = node_runtime { - NODE_RUNTIME - .set(node_runtime) - .unwrap_or_else(|_| panic!("multiple init calls tried to set node runtime")); - } - client.add_model_message_handler(Self::handle_add_collaborator); client.add_model_message_handler(Self::handle_update_project_collaborator); client.add_model_message_handler(Self::handle_remove_collaborator); @@ -624,6 +612,7 @@ impl Project { pub fn local( client: Arc, + node_runtime: Arc, user_store: ModelHandle, languages: Arc, fs: Arc, @@ -679,6 +668,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + node_runtime: Some(node_runtime), prettier_instances: HashMap::default(), } }) @@ -777,6 +767,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + node_runtime: None, prettier_instances: HashMap::default(), }; for worktree in worktrees { @@ -811,13 +802,23 @@ impl Project { root_paths: impl IntoIterator, cx: &mut gpui::TestAppContext, ) -> ModelHandle { + use node_runtime::FakeNodeRuntime; + let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background()); let http_client = util::http::FakeHttpClient::with_404_response(); let client = cx.update(|cx| client::Client::new(http_client.clone(), cx)); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - let project = - cx.update(|cx| Project::local(client, user_store, Arc::new(languages), fs, cx)); + let project = cx.update(|cx| { + Project::local( + client, + FakeNodeRuntime::new(), + user_store, + Arc::new(languages), + fs, + cx, + ) + }); for path in root_paths { let (tree, _) = project .update(cx, |project, cx| { @@ -8201,7 +8202,7 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Option, Arc>>>>> { - let node_runtime = Arc::clone(NODE_RUNTIME.get()?); + let node_runtime = Arc::clone(self.node_runtime.as_ref()?); let buffer_file = File::from_dyn(buffer.read(cx).file()); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index d1240a45cea5ced287514da3569ae4a782f36883..99f19ed9d06b21ad6f9ba66464c016beb0b0bba0 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -30,6 +30,7 @@ gpui = { path = "../gpui" } install_cli = { path = "../install_cli" } language = { path = "../language" } menu = { path = "../menu" } +node_runtime = { path = "../node_runtime" } project = { path = "../project" } settings = { path = "../settings" } terminal = { path = "../terminal" } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8b068fa10cf984a5301cd9e6d7d73dc118218f6f..454b0138e646d00c42b09b947f6f15d292465a26 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -42,6 +42,7 @@ use gpui::{ use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use itertools::Itertools; use language::{LanguageRegistry, Rope}; +use node_runtime::NodeRuntime; use std::{ any::TypeId, borrow::Cow, @@ -456,6 +457,7 @@ pub struct AppState { pub initialize_workspace: fn(WeakViewHandle, bool, Arc, AsyncAppContext) -> Task>, pub background_actions: BackgroundActions, + pub node_runtime: Arc, } pub struct WorkspaceStore { @@ -474,6 +476,7 @@ struct Follower { impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Arc { + use node_runtime::FakeNodeRuntime; use settings::SettingsStore; if !cx.has_global::() { @@ -498,6 +501,7 @@ impl AppState { user_store, // channel_store, workspace_store, + node_runtime: FakeNodeRuntime::new(), initialize_workspace: |_, _, _, _| Task::ready(Ok(())), build_window_options: |_, _, _| Default::default(), background_actions: || &[], @@ -816,6 +820,7 @@ impl Workspace { )> { let project_handle = Project::local( app_state.client.clone(), + app_state.node_runtime.clone(), app_state.user_store.clone(), app_state.languages.clone(), app_state.fs.clone(), @@ -3517,6 +3522,8 @@ impl Workspace { #[cfg(any(test, feature = "test-support"))] pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { + use node_runtime::FakeNodeRuntime; + let client = project.read(cx).client(); let user_store = project.read(cx).user_store(); @@ -3530,6 +3537,7 @@ impl Workspace { build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _, _| Task::ready(Ok(())), background_actions: || &[], + node_runtime: FakeNodeRuntime::new(), }); Self::new(0, project, app_state, cx) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8093632d3a446a745d519feff98a087316141eec..16189f6c4e43237d12d7579f590561ef0125fde1 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -138,7 +138,7 @@ fn main() { theme::init(Assets, cx); context_menu::init(cx); - project::Project::init(&client, Some(Arc::clone(&node_runtime)), cx); + project::Project::init(&client, cx); client::init(&client, cx); command_palette::init(cx); language::init(cx); @@ -154,7 +154,12 @@ fn main() { semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx); vim::init(cx); terminal_view::init(cx); - copilot::init(copilot_language_server_id, http.clone(), node_runtime, cx); + copilot::init( + copilot_language_server_id, + http.clone(), + node_runtime.clone(), + cx, + ); assistant::init(cx); component_test::init(cx); @@ -181,6 +186,7 @@ fn main() { initialize_workspace, background_actions, workspace_store, + node_runtime, }); cx.set_global(Arc::downgrade(&app_state)); From 4f956d71e2de17dcce6588fef6696570a4918e27 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 8 Sep 2023 15:41:23 +0300 Subject: [PATCH 071/180] Slightly better prettier settings and discovery --- Cargo.lock | 1 + assets/settings/default.json | 2 + crates/collab/src/tests/test_server.rs | 3 ++ crates/language/src/language_settings.rs | 4 ++ crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 32 ++++++++----- crates/project/src/project.rs | 60 ++++++++---------------- crates/util/src/paths.rs | 1 + 8 files changed, 52 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24e312803b168aefe8e30259767436626d93b38c..0d7e25037792866cdcad56b21380745ecb393a6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5531,6 +5531,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "util", ] [[package]] diff --git a/assets/settings/default.json b/assets/settings/default.json index be47ac9c8c1b91e90df800a79367cf4ebcd5ac02..677cc57820aec4e0910815591c980b871d152b00 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -201,6 +201,8 @@ // } // TODO kb description "formatter": "auto", + // TODO kb description + better settings + "prettier": true, // How to soft-wrap long lines of text. This setting can take // three values: // diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 2e13874125472cd53b68d4d688c90ca02569615a..ccd48a0a1b10f050bb47bdf765d26fda1b107904 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -15,6 +15,7 @@ use fs::FakeFs; use futures::{channel::oneshot, StreamExt as _}; use gpui::{executor::Deterministic, ModelHandle, Task, TestAppContext, WindowHandle}; use language::LanguageRegistry; +use node_runtime::FakeNodeRuntime; use parking_lot::Mutex; use project::{Project, WorktreeId}; use rpc::RECEIVE_TIMEOUT; @@ -218,6 +219,7 @@ impl TestServer { build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _, _| Task::ready(Ok(())), background_actions: || &[], + node_runtime: FakeNodeRuntime::new(), }); cx.update(|cx| { @@ -569,6 +571,7 @@ impl TestClient { self.client().clone(), self.app_state.user_store.clone(), self.app_state.languages.clone(), + self.app_state.node_runtime.clone(), self.app_state.fs.clone(), cx, ) diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 8778ba8d6405cc415381f5153e7afecd03c8efa8..844e0c7c36f800336954681e6ec86be02572ec99 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -47,6 +47,7 @@ pub struct LanguageSettings { pub show_wrap_guides: bool, pub wrap_guides: Vec, pub format_on_save: FormatOnSave, + pub prettier: bool, pub remove_trailing_whitespace_on_save: bool, pub ensure_final_newline_on_save: bool, pub formatter: Formatter, @@ -92,6 +93,8 @@ pub struct LanguageSettingsContent { #[serde(default)] pub format_on_save: Option, #[serde(default)] + pub prettier: Option, + #[serde(default)] pub remove_trailing_whitespace_on_save: Option, #[serde(default)] pub ensure_final_newline_on_save: Option, @@ -398,6 +401,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent ); merge(&mut settings.formatter, src.formatter.clone()); merge(&mut settings.format_on_save, src.format_on_save.clone()); + merge(&mut settings.prettier, src.prettier); merge( &mut settings.remove_trailing_whitespace_on_save, src.remove_trailing_whitespace_on_save, diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 2b8bf99fc2c453b61b225a3b5e34e637691797bc..ab8d4d9e16f8558fcbc3eeeb44000a15cef16bbe 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -11,6 +11,7 @@ language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } node_runtime = { path = "../node_runtime"} +util = { path = "../util" } serde.workspace = true serde_derive.workspace = true diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index efb3a0fcf5e4c58b70c3a1b373bc92865e885777..454f845aa4a0c69bafa9a97b94e61fb0a7fa8115 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -130,20 +130,21 @@ impl Prettier { None => Vec::new(), }; - match find_closest_prettier_path(paths_to_check, fs.as_ref()) + match find_closest_prettier_dir(paths_to_check, fs.as_ref()) .await - .with_context(|| format!("Finding prettier starting with {starting_path:?}"))? + .with_context(|| format!("finding prettier starting with {starting_path:?}"))? { - Some(prettier_path) => Ok(prettier_path), - None => { - // TODO kb return the default prettier, how, without state? - Ok(PathBuf::new()) - } + Some(prettier_dir) => Ok(prettier_dir), + None => Ok(util::paths::DEFAULT_PRETTIER_DIR.to_path_buf()), } } - pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { - todo!() + pub async fn start(prettier_dir: &Path, node: Arc) -> anyhow::Result { + anyhow::ensure!( + prettier_dir.is_dir(), + "Prettier dir {prettier_dir:?} is not a directory" + ); + anyhow::bail!("TODO kb: start prettier server in {prettier_dir:?}") } pub async fn format(&self, buffer: &ModelHandle) -> anyhow::Result { @@ -156,14 +157,14 @@ impl Prettier { } const PRETTIER_PACKAGE_NAME: &str = "prettier"; -async fn find_closest_prettier_path( +async fn find_closest_prettier_dir( paths_to_check: Vec, fs: &dyn Fs, ) -> anyhow::Result> { for path in paths_to_check { let possible_package_json = path.join("package.json"); if let Some(package_json_metadata) = fs - .metadata(&path) + .metadata(&possible_package_json) .await .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))? { @@ -192,7 +193,7 @@ async fn find_closest_prettier_path( let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME); if let Some(node_modules_location_metadata) = fs - .metadata(&path) + .metadata(&possible_node_modules_location) .await .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))? { @@ -203,3 +204,10 @@ async fn find_closest_prettier_path( } Ok(None) } + +async fn prepare_default_prettier( + fs: Arc, + node: Arc, +) -> anyhow::Result { + todo!("TODO kb need to call per language that supports it, and have to use extra packages sometimes") +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8f56a8be09c5064eae3c7af9a69aee90f9b0030a..56b86e52746968e9541c0b0c4bede10e1fd4dcbf 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -154,7 +154,7 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, - node_runtime: Option>, + node: Option>, prettier_instances: HashMap< (Option, PathBuf), Shared, Arc>>>, @@ -612,7 +612,7 @@ impl Project { pub fn local( client: Arc, - node_runtime: Arc, + node: Arc, user_store: ModelHandle, languages: Arc, fs: Arc, @@ -668,7 +668,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), - node_runtime: Some(node_runtime), + node: Some(node), prettier_instances: HashMap::default(), } }) @@ -767,7 +767,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), - node_runtime: None, + node: None, prettier_instances: HashMap::default(), }; for worktree in worktrees { @@ -802,8 +802,6 @@ impl Project { root_paths: impl IntoIterator, cx: &mut gpui::TestAppContext, ) -> ModelHandle { - use node_runtime::FakeNodeRuntime; - let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background()); let http_client = util::http::FakeHttpClient::with_404_response(); @@ -812,7 +810,7 @@ impl Project { let project = cx.update(|cx| { Project::local( client, - FakeNodeRuntime::new(), + node_runtime::FakeNodeRuntime::new(), user_store, Arc::new(languages), fs, @@ -8202,43 +8200,25 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Option, Arc>>>>> { - let node_runtime = Arc::clone(self.node_runtime.as_ref()?); - let buffer_file = File::from_dyn(buffer.read(cx).file()); + let buffer = buffer.read(cx); + let buffer_file = buffer.file(); + let language_settings = language_settings(buffer.language(), buffer_file, cx).clone(); + if !language_settings.prettier { + return None; + } + + let node = Arc::clone(self.node.as_ref()?); + let buffer_file = File::from_dyn(buffer_file); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file .as_ref() .map(|file| file.worktree.read(cx).abs_path()); let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); - // TODO kb return None if config opted out of prettier - if true { - let fs = Arc::clone(&self.fs); - let buffer_path = buffer_path.clone(); - let worktree_path = worktree_path.clone(); - cx.spawn(|_, _| async move { - let prettier_path = Prettier::locate( - worktree_path - .zip(buffer_path) - .map(|(worktree_root_path, starting_path)| { - dbg!(LocateStart { - worktree_root_path, - starting_path, - }) - }), - fs, - ) - .await - .unwrap(); - dbg!(prettier_path); - }) - .detach(); - return None; - } - let task = cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); // TODO kb can we have a cache for this instead? - let prettier_path = match cx + let prettier_dir = match cx .background() .spawn(Prettier::locate( worktree_path @@ -8263,21 +8243,21 @@ impl Project { if let Some(existing_prettier) = this.update(&mut cx, |project, _| { project .prettier_instances - .get(&(worktree_id, prettier_path.clone())) + .get(&(worktree_id, prettier_dir.clone())) .cloned() }) { return existing_prettier; } - let task_prettier_path = prettier_path.clone(); + let task_prettier_dir = prettier_dir.clone(); let new_prettier_task = cx .background() .spawn(async move { Ok(Arc::new( - Prettier::start(&task_prettier_path, node_runtime) + Prettier::start(&task_prettier_dir, node) .await .with_context(|| { - format!("starting new prettier for path {task_prettier_path:?}") + format!("starting new prettier for path {task_prettier_dir:?}") })?, )) .map_err(Arc::new) @@ -8286,7 +8266,7 @@ impl Project { this.update(&mut cx, |project, _| { project .prettier_instances - .insert((worktree_id, prettier_path), new_prettier_task.clone()); + .insert((worktree_id, prettier_dir), new_prettier_task.clone()); }); new_prettier_task }); diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 4578ce0bc9105583db07030aaadeb90ede7079b0..96d77236a904b38847674b01fdccbe0dc4c9c502 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -11,6 +11,7 @@ lazy_static::lazy_static! { pub static ref SUPPORT_DIR: PathBuf = HOME.join("Library/Application Support/Zed"); pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages"); pub static ref COPILOT_DIR: PathBuf = HOME.join("Library/Application Support/Zed/copilot"); + pub static ref DEFAULT_PRETTIER_DIR: PathBuf = HOME.join("Library/Application Support/Zed/prettier"); pub static ref DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db"); pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json"); pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json"); From 12ea12e4e78d0ae2fcf53d110757f86cb48745e0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 8 Sep 2023 21:46:28 +0300 Subject: [PATCH 072/180] Make language adapters able to require certain bundled formatters --- assets/settings/default.json | 2 -- crates/language/src/language.rs | 24 ++++++++++++++++++++++++ crates/language/src/language_settings.rs | 4 ---- crates/project/src/project.rs | 21 ++++++++++++++------- crates/zed/src/languages/css.rs | 6 +++++- crates/zed/src/languages/html.rs | 6 +++++- crates/zed/src/languages/json.rs | 8 +++++++- crates/zed/src/languages/php.rs | 6 +++++- crates/zed/src/languages/svelte.rs | 6 +++++- crates/zed/src/languages/typescript.rs | 10 +++++++++- crates/zed/src/languages/yaml.rs | 7 ++++++- 11 files changed, 80 insertions(+), 20 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 677cc57820aec4e0910815591c980b871d152b00..be47ac9c8c1b91e90df800a79367cf4ebcd5ac02 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -201,8 +201,6 @@ // } // TODO kb description "formatter": "auto", - // TODO kb description + better settings - "prettier": true, // How to soft-wrap long lines of text. This setting can take // three values: // diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 7d113a88af1de8e7b0b243bf54c15565de544730..ed0a0d8ee80712a8861adc4f0ff3f249e9952cd6 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -227,6 +227,10 @@ impl CachedLspAdapter { ) -> Option { self.adapter.label_for_symbol(name, kind, language).await } + + pub fn enabled_formatters(&self) -> Vec { + self.adapter.enabled_formatters() + } } pub trait LspAdapterDelegate: Send + Sync { @@ -333,6 +337,26 @@ pub trait LspAdapter: 'static + Send + Sync { async fn language_ids(&self) -> HashMap { Default::default() } + + // TODO kb enable this for + // markdown somehow? + // tailwind (needs a css plugin, there are 2 of them) + fn enabled_formatters(&self) -> Vec { + Vec::new() + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BundledFormatter { + Prettier { plugin_names: Vec }, +} + +impl BundledFormatter { + pub fn prettier() -> Self { + Self::Prettier { + plugin_names: Vec::new(), + } + } } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 844e0c7c36f800336954681e6ec86be02572ec99..8778ba8d6405cc415381f5153e7afecd03c8efa8 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -47,7 +47,6 @@ pub struct LanguageSettings { pub show_wrap_guides: bool, pub wrap_guides: Vec, pub format_on_save: FormatOnSave, - pub prettier: bool, pub remove_trailing_whitespace_on_save: bool, pub ensure_final_newline_on_save: bool, pub formatter: Formatter, @@ -93,8 +92,6 @@ pub struct LanguageSettingsContent { #[serde(default)] pub format_on_save: Option, #[serde(default)] - pub prettier: Option, - #[serde(default)] pub remove_trailing_whitespace_on_save: Option, #[serde(default)] pub ensure_final_newline_on_save: Option, @@ -401,7 +398,6 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent ); merge(&mut settings.formatter, src.formatter.clone()); merge(&mut settings.format_on_save, src.format_on_save.clone()); - merge(&mut settings.prettier, src.prettier); merge( &mut settings.remove_trailing_whitespace_on_save, src.remove_trailing_whitespace_on_save, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 56b86e52746968e9541c0b0c4bede10e1fd4dcbf..bfcce50c014d81d7f8286042a589656bd3153cd8 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -37,11 +37,11 @@ use language::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, serialize_anchor, serialize_version, split_operations, }, - range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction, - CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, - File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate, - OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, - ToOffset, ToPointUtf16, Transaction, Unclipped, + range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter, + CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, + Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, + LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, + TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, }; use log::error; use lsp::{ @@ -2651,6 +2651,8 @@ impl Project { } } + // TODO kb 2 usages of this method (buffer language select + settings change) should take care of + // `LspAdapter::enabled_formatters` collecting and initializing. Remove `Option` for prettier instances? fn start_language_servers( &mut self, worktree: &ModelHandle, @@ -8202,8 +8204,13 @@ impl Project { ) -> Option, Arc>>>>> { let buffer = buffer.read(cx); let buffer_file = buffer.file(); - let language_settings = language_settings(buffer.language(), buffer_file, cx).clone(); - if !language_settings.prettier { + let buffer_language = buffer.language()?; + if !buffer_language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .any(|formatter| matches!(formatter, BundledFormatter::Prettier { .. })) + { return None; } diff --git a/crates/zed/src/languages/css.rs b/crates/zed/src/languages/css.rs index fdbc179209603ea41c16e3a5aa6aac0d6a7a7f8e..a28523a741b67e687693d506466f64a290bd3586 100644 --- a/crates/zed/src/languages/css.rs +++ b/crates/zed/src/languages/css.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -96,6 +96,10 @@ impl LspAdapter for CssLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index b8f1c70cce2ae00ca2a1647840e483844ea2a2e9..af8fb1690c1debd2c46b40f7ed3e0974714972ea 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -96,6 +96,10 @@ impl LspAdapter for HtmlLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 63f909ae2a2e264ea672dee48e305ba1be82e066..e66a5d96a69d04fd3b5e68faf83e093f2d30bb5f 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -4,7 +4,9 @@ use collections::HashMap; use feature_flags::FeatureFlagAppExt; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; -use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{ + BundledFormatter, LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate, +}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -144,6 +146,10 @@ impl LspAdapter for JsonLspAdapter { async fn language_ids(&self) -> HashMap { [("JSON".into(), "jsonc".into())].into_iter().collect() } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/php.rs b/crates/zed/src/languages/php.rs index 3096fd16e6b87764a0f9dd127a0dec6eaba0a77a..f8f9351111faf76cb806c3fc03b603425c67bf2d 100644 --- a/crates/zed/src/languages/php.rs +++ b/crates/zed/src/languages/php.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use collections::HashMap; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; @@ -103,6 +103,10 @@ impl LspAdapter for IntelephenseLspAdapter { async fn language_ids(&self) -> HashMap { HashMap::from_iter([("PHP".into(), "php".into())]) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs index 5e42d80e77fe04763643cbb6cd6ba298ba9e9e92..487720ce465c6e01ee0f9e04f8725b1885e855f7 100644 --- a/crates/zed/src/languages/svelte.rs +++ b/crates/zed/src/languages/svelte.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -95,6 +95,10 @@ impl LspAdapter for SvelteLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 676d0fd4c0d7afeaf1d3d39bd0c91f17c1a862cf..78b1214b1a29126959fa15113ef9ea29b309e712 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -4,7 +4,7 @@ use async_tar::Archive; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt}; use gpui::AppContext; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::{CodeActionKind, LanguageServerBinary}; use node_runtime::NodeRuntime; use serde_json::{json, Value}; @@ -161,6 +161,10 @@ impl LspAdapter for TypeScriptLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_ts_server_binary( @@ -309,6 +313,10 @@ impl LspAdapter for EsLintLspAdapter { async fn initialization_options(&self) -> Option { None } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_eslint_server_binary( diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index 8b438d0949dc0ef1f514f3c315c3eab98174d506..c9168b34800a87a59616a78098533f4301126b3e 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; use language::{ - language_settings::all_language_settings, LanguageServerName, LspAdapter, LspAdapterDelegate, + language_settings::all_language_settings, BundledFormatter, LanguageServerName, LspAdapter, + LspAdapterDelegate, }; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; @@ -108,6 +109,10 @@ impl LspAdapter for YamlLspAdapter { })) .boxed() } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( From 1ff17bd15d23bf0b78424a24fbd448f4b97c5665 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 12 Sep 2023 14:04:18 +0300 Subject: [PATCH 073/180] Install default prettier and plugins on startup --- crates/prettier/src/prettier.rs | 7 -- crates/project/src/project.rs | 124 +++++++++++++++++++++++++++++--- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 454f845aa4a0c69bafa9a97b94e61fb0a7fa8115..d68af349d9362c9f08d935e1dcf8345f8c704800 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -204,10 +204,3 @@ async fn find_closest_prettier_dir( } Ok(None) } - -async fn prepare_default_prettier( - fs: Arc, - node: Arc, -) -> anyhow::Result { - todo!("TODO kb need to call per language that supports it, and have to use extra packages sometimes") -} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bfcce50c014d81d7f8286042a589656bd3153cd8..ffc15ee0d3e085cf5eabcda5a2180f582ad66b06 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -20,7 +20,7 @@ use futures::{ mpsc::{self, UnboundedReceiver}, oneshot, }, - future::{try_join_all, Shared}, + future::{self, try_join_all, Shared}, stream::FuturesUnordered, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, }; @@ -31,7 +31,9 @@ use gpui::{ }; use itertools::Itertools; use language::{ - language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind}, + language_settings::{ + language_settings, FormatOnSave, Formatter, InlayHintKind, LanguageSettings, + }, point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, @@ -79,8 +81,11 @@ use std::{ use terminals::Terminals; use text::Anchor; use util::{ - debug_panic, defer, http::HttpClient, merge_json_value_into, - paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _, + debug_panic, defer, + http::HttpClient, + merge_json_value_into, + paths::{DEFAULT_PRETTIER_DIR, LOCAL_SETTINGS_RELATIVE_PATH}, + post_inc, ResultExt, TryFutureExt as _, }; pub use fs::*; @@ -832,17 +837,28 @@ impl Project { fn on_settings_changed(&mut self, cx: &mut ModelContext) { let mut language_servers_to_start = Vec::new(); + let mut language_formatters_to_check = Vec::new(); for buffer in self.opened_buffers.values() { if let Some(buffer) = buffer.upgrade(cx) { let buffer = buffer.read(cx); - if let Some((file, language)) = buffer.file().zip(buffer.language()) { - let settings = language_settings(Some(language), Some(file), cx); + let buffer_file = buffer.file(); + let buffer_language = buffer.language(); + let settings = language_settings(buffer_language, buffer_file, cx); + if let Some(language) = buffer_language { if settings.enable_language_server { - if let Some(file) = File::from_dyn(Some(file)) { + if let Some(file) = File::from_dyn(buffer_file) { language_servers_to_start - .push((file.worktree.clone(), language.clone())); + .push((file.worktree.clone(), Arc::clone(language))); } } + let worktree = buffer_file + .map(|f| f.worktree_id()) + .map(WorktreeId::from_usize); + language_formatters_to_check.push(( + worktree, + Arc::clone(language), + settings.clone(), + )); } } } @@ -895,6 +911,11 @@ impl Project { .detach(); } + // TODO kb restart all formatters if settings change + for (worktree, language, settings) in language_formatters_to_check { + self.maybe_start_default_formatters(worktree, &language, &settings, cx); + } + // Start all the newly-enabled language servers. for (worktree, language) in language_servers_to_start { let worktree_path = worktree.read(cx).abs_path(); @@ -2643,7 +2664,15 @@ impl Project { } }); - if let Some(file) = File::from_dyn(buffer.read(cx).file()) { + let buffer_file = buffer.read(cx).file().cloned(); + let worktree = buffer_file + .as_ref() + .map(|f| f.worktree_id()) + .map(WorktreeId::from_usize); + let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); + self.maybe_start_default_formatters(worktree, &new_language, &settings, cx); + + if let Some(file) = File::from_dyn(buffer_file.as_ref()) { let worktree = file.worktree.clone(); if let Some(tree) = worktree.read(cx).as_local() { self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx); @@ -2651,8 +2680,6 @@ impl Project { } } - // TODO kb 2 usages of this method (buffer language select + settings change) should take care of - // `LspAdapter::enabled_formatters` collecting and initializing. Remove `Option` for prettier instances? fn start_language_servers( &mut self, worktree: &ModelHandle, @@ -8279,6 +8306,81 @@ impl Project { }); Some(task) } + + fn maybe_start_default_formatters( + &mut self, + worktree: Option, + new_language: &Language, + language_settings: &LanguageSettings, + cx: &mut ModelContext, + ) { + match &language_settings.formatter { + Formatter::Prettier { .. } | Formatter::Auto => {} + Formatter::LanguageServer | Formatter::External { .. } => return, + }; + let Some(node) = self.node.as_ref().cloned() else { + return; + }; + + let mut prettier_plugins = None; + for formatter in new_language + .lsp_adapters() + .into_iter() + .flat_map(|adapter| adapter.enabled_formatters()) + { + match formatter { + BundledFormatter::Prettier { plugin_names } => prettier_plugins + .get_or_insert_with(|| HashSet::default()) + .extend(plugin_names), + } + } + let Some(prettier_plugins) = prettier_plugins else { + return; + }; + + let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); + if let Some(_already_running) = self + .prettier_instances + .get(&(worktree, default_prettier_dir.to_path_buf())) + { + // TODO kb need to compare plugins, install missing and restart prettier + return; + } + + let fs = Arc::clone(&self.fs); + cx.background() + .spawn(async move { + let prettier_dir_metadata = fs.metadata(default_prettier_dir).await.with_context(|| format!("fetching FS metadata for prettier default dir {default_prettier_dir:?}"))?; + if prettier_dir_metadata.is_none() { + fs.create_dir(default_prettier_dir).await.with_context(|| format!("creating prettier default dir {default_prettier_dir:?}"))?; + } + + let packages_to_versions = future::try_join_all( + prettier_plugins + .iter() + .map(|s| s.as_str()) + .chain(Some("prettier")) + .map(|package_name| async { + let returned_package_name = package_name.to_string(); + let latest_version = node.npm_package_latest_version(package_name) + .await + .with_context(|| { + format!("fetching latest npm version for package {returned_package_name}") + })?; + anyhow::Ok((returned_package_name, latest_version)) + }), + ) + .await + .context("fetching latest npm versions")?; + + let borrowed_packages = packages_to_versions.iter().map(|(package, version)| { + (package.as_str(), version.as_str()) + }).collect::>(); + node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } } fn subscribe_for_copilot_events( From 86618a64c6ab00e51d569aaa99142d37e1583c3e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 12 Sep 2023 16:34:56 +0300 Subject: [PATCH 074/180] Require prettier argument and library in the wrapper --- .../prettier_server/package-lock.json | 35 ++++-------- crates/prettier/prettier_server/package.json | 2 +- crates/prettier/prettier_server/src/index.js | 53 ++++++++++++++++++- 3 files changed, 61 insertions(+), 29 deletions(-) diff --git a/crates/prettier/prettier_server/package-lock.json b/crates/prettier/prettier_server/package-lock.json index 6d495d72b472667088ccfb55e93c881c0d46e653..01fd75f3ddd44f2f62f9435ac4d43c42dbc0f75a 100644 --- a/crates/prettier/prettier_server/package-lock.json +++ b/crates/prettier/prettier_server/package-lock.json @@ -1,29 +1,12 @@ { - "name": "prettier_server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "prettier_server", - "version": "1.0.0", - "dependencies": { - "prettier": "^3.0.3" - } - }, - "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } + "name": "prettier_server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prettier_server", + "version": "1.0.0" + } } - } } diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json index 599f308e9f175a32f4a372800d8629d10aac80a3..a591a37bdb6ca6614da464784ef30fc3b82659db 100644 --- a/crates/prettier/prettier_server/package.json +++ b/crates/prettier/prettier_server/package.json @@ -8,7 +8,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Zed Industries", - "dependencies": { + "dev-dependencies": { "prettier": "^3.0" } } diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index 5ac35d7ef9c842565377030c2b45f405001093e2..482e9968877af7005e60b484490d65502d8cc35d 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -1,4 +1,36 @@ const { Buffer } = require('buffer'); +const fs = require("fs"); +const path = require("path"); + +let prettierContainerPath = process.argv[2]; +if (prettierContainerPath == null || prettierContainerPath.length == 0) { + console.error(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); + process.exit(1); +} +fs.stat(prettierContainerPath, (err, stats) => { + if (err) { + console.error(`Path '${prettierContainerPath}' does not exist.`); + process.exit(1); + } + + if (!stats.isDirectory()) { + console.log(`Path '${prettierContainerPath}' exists but is not a directory.`); + process.exit(1); + } +}); +let prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); + +(async () => { + let prettier; + try { + prettier = await loadPrettier(prettierPath); + } catch (error) { + console.error(error); + process.exit(1); + } + console.log("Prettier loadded successfully."); + // TODO kb do the rest here +})() let buffer = Buffer.alloc(0); process.stdin.resume(); @@ -28,14 +60,15 @@ function handleData() { try { const message = JSON.parse(bytes); handleMessage(message); - } catch (_) { - sendResponse(makeError("Request JSON parse error")); + } catch (e) { + sendResponse(makeError(`Request JSON parse error: ${e}`)); return; } } // format // clear_cache +// // shutdown // error @@ -55,3 +88,19 @@ function sendResponse(response) { process.stdout.write(length); process.stdout.write(message); } + +function loadPrettier(prettierPath) { + return new Promise((resolve, reject) => { + fs.access(prettierPath, fs.constants.F_OK, (err) => { + if (err) { + reject(`Path '${prettierPath}' does not exist.Error: ${err}`); + } else { + try { + resolve(require(prettierPath)); + } catch (err) { + reject(`Error requiring prettier module from path '${prettierPath}'.Error: ${err}`); + } + } + }); + }); +} From bb2cc2d157a5f3aa4a8a715d6eeb9df97f4f4d3d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Sep 2023 17:14:22 +0300 Subject: [PATCH 075/180] Async-ify prettier wrapper --- crates/prettier/prettier_server/src/index.js | 98 +++++++++++++------- 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index 482e9968877af7005e60b484490d65502d8cc35d..15aeb30dca0209c1ace021ff46b4977c40859ded 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -1,8 +1,9 @@ const { Buffer } = require('buffer'); const fs = require("fs"); const path = require("path"); +const { once } = require('events'); -let prettierContainerPath = process.argv[2]; +const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { console.error(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); process.exit(1); @@ -18,48 +19,83 @@ fs.stat(prettierContainerPath, (err, stats) => { process.exit(1); } }); -let prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); +const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); + (async () => { let prettier; try { prettier = await loadPrettier(prettierPath); } catch (error) { - console.error(error); + console.error("Failed to load prettier: ", error); process.exit(1); } console.log("Prettier loadded successfully."); - // TODO kb do the rest here + process.stdin.resume(); + handleBuffer(prettier); })() -let buffer = Buffer.alloc(0); -process.stdin.resume(); -process.stdin.on('data', (data) => { - buffer = Buffer.concat([buffer, data]); - handleData(); -}); -process.stdin.on('end', () => { - handleData(); -}); - -function handleData() { - if (buffer.length < 4) { - return; +async function handleBuffer(prettier) { + for await (let messageText of readStdin()) { + handleData(messageText, prettier).catch(e => { + console.error("Failed to handle formatter request", e); + }); } +} - const length = buffer.readUInt32LE(0); - console.log(length); - console.log(buffer.toString()); - if (buffer.length < 4 + length) { - return; - } +async function* readStdin() { + const bufferLengthOffset = 4; + let buffer = Buffer.alloc(0); + let streamEnded = false; + process.stdin.on('end', () => { + streamEnded = true; + }); + process.stdin.on('data', (data) => { + buffer = Buffer.concat([buffer, data]); + }); - const bytes = buffer.subarray(4, 4 + length); - buffer = buffer.subarray(4 + length); + try { + main_loop: while (true) { + while (buffer.length < bufferLengthOffset) { + if (streamEnded) { + sendResponse(makeError(`Unexpected end of stream: less than ${bufferLengthOffset} characters passed`)); + buffer = Buffer.alloc(0); + streamEnded = false; + await once(process.stdin, 'readable'); + continue main_loop; + } + await once(process.stdin, 'readable'); + } + + const length = buffer.readUInt32LE(0); + + while (buffer.length < (bufferLengthOffset + length)) { + if (streamEnded) { + sendResponse(makeError( + `Unexpected end of stream: buffer length ${buffer.length} does not match expected length ${bufferLengthOffset} + ${length}`)); + buffer = Buffer.alloc(0); + streamEnded = false; + await once(process.stdin, 'readable'); + continue main_loop; + } + await once(process.stdin, 'readable'); + } + + const message = buffer.subarray(4, 4 + length); + buffer = buffer.subarray(4 + length); + yield message.toString('utf8'); + } + } catch (e) { + console.error(`Error reading stdin: ${e}`); + } finally { + process.stdin.off('data'); + } +} +async function handleData(messageText, prettier) { try { - const message = JSON.parse(bytes); - handleMessage(message); + const message = JSON.parse(messageText); + await handleMessage(prettier, message); } catch (e) { sendResponse(makeError(`Request JSON parse error: ${e}`)); return; @@ -72,8 +108,8 @@ function handleData() { // shutdown // error -function handleMessage(message) { - console.log(message); +async function handleMessage(prettier, message) { + console.log(`message: ${message}`); sendResponse({ method: "hi", result: null }); } @@ -82,8 +118,8 @@ function makeError(message) { } function sendResponse(response) { - let message = Buffer.from(JSON.stringify(response)); - let length = Buffer.alloc(4); + const message = Buffer.from(JSON.stringify(response)); + const length = Buffer.alloc(4); length.writeUInt32LE(message.length); process.stdout.write(length); process.stdout.write(message); From 010bb73ac2a28903a0822486d34b00042c868d7b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Sep 2023 23:10:42 +0300 Subject: [PATCH 076/180] Use LSP-like protocol for prettier wrapper commands --- crates/prettier/prettier_server/src/index.js | 61 +++++++++++++------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index 15aeb30dca0209c1ace021ff46b4977c40859ded..d232ac9efe5ad4b9405d6baa7d0a18e2250c715e 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -44,7 +44,6 @@ async function handleBuffer(prettier) { } async function* readStdin() { - const bufferLengthOffset = 4; let buffer = Buffer.alloc(0); let streamEnded = false; process.stdin.on('end', () => { @@ -54,41 +53,63 @@ async function* readStdin() { buffer = Buffer.concat([buffer, data]); }); + async function handleStreamEnded(errorMessage) { + sendResponse(makeError(errorMessage)); + buffer = Buffer.alloc(0); + messageLength = null; + await once(process.stdin, 'readable'); + streamEnded = false; + } + try { + const headersSeparator = "\r\n\r\n"; + let contentLengthHeaderName = 'Content-Length'; + let headersLength = null; + let messageLength = null; main_loop: while (true) { - while (buffer.length < bufferLengthOffset) { - if (streamEnded) { - sendResponse(makeError(`Unexpected end of stream: less than ${bufferLengthOffset} characters passed`)); - buffer = Buffer.alloc(0); - streamEnded = false; + if (messageLength === null) { + while (buffer.indexOf(headersSeparator) === -1) { + if (streamEnded) { + await handleStreamEnded('Unexpected end of stream: headers not found'); + continue main_loop; + } else if (buffer.length > contentLengthHeaderName.length * 10) { + await handleStreamEnded(`Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`); + continue main_loop; + } await once(process.stdin, 'readable'); + } + const headers = buffer.subarray(0, buffer.indexOf(headersSeparator)).toString('ascii'); + const contentLengthHeader = headers.split('\r\n').map(header => header.split(': ')) + .filter(header => header[2] === undefined) + .filter(header => (header[1] || '').length > 0) + .find(header => header[0].trim() === contentLengthHeaderName); + if (contentLengthHeader === undefined) { + await handleStreamEnded(`Missing or incorrect Content-Length header: ${headers}`); continue main_loop; } - await once(process.stdin, 'readable'); + headersLength = headers.length + headersSeparator.length; + messageLength = parseInt(contentLengthHeader[1], 10); } - const length = buffer.readUInt32LE(0); - - while (buffer.length < (bufferLengthOffset + length)) { + while (buffer.length < (headersLength + messageLength)) { if (streamEnded) { - sendResponse(makeError( - `Unexpected end of stream: buffer length ${buffer.length} does not match expected length ${bufferLengthOffset} + ${length}`)); - buffer = Buffer.alloc(0); - streamEnded = false; - await once(process.stdin, 'readable'); + await handleStreamEnded( + `Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`); continue main_loop; } await once(process.stdin, 'readable'); } - const message = buffer.subarray(4, 4 + length); - buffer = buffer.subarray(4 + length); + const messageEnd = headersLength + messageLength; + const message = buffer.subarray(headersLength, messageEnd); + buffer = buffer.subarray(messageEnd); + messageLength = null; yield message.toString('utf8'); } } catch (e) { console.error(`Error reading stdin: ${e}`); } finally { - process.stdin.off('data'); + process.stdin.off('data', () => { }); } } @@ -98,7 +119,6 @@ async function handleData(messageText, prettier) { await handleMessage(prettier, message); } catch (e) { sendResponse(makeError(`Request JSON parse error: ${e}`)); - return; } } @@ -109,7 +129,8 @@ async function handleData(messageText, prettier) { // error async function handleMessage(prettier, message) { - console.log(`message: ${message}`); + // TODO kb handle message.method, message.params and message.id + console.log(`message: ${JSON.stringify(message)}`); sendResponse({ method: "hi", result: null }); } From dca93fb1770f658a1fd92a2ced73f66553dff1cf Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 17 Sep 2023 01:21:46 +0300 Subject: [PATCH 077/180] Initialize prettier_server.js wrapper along with default prettier --- crates/prettier/prettier_server/.gitignore | 1 - crates/prettier/prettier_server/.zed/settings.json | 7 ------- crates/prettier/prettier_server/package-lock.json | 12 ------------ crates/prettier/prettier_server/package.json | 14 -------------- crates/prettier/src/prettier.rs | 2 ++ .../src/index.js => src/prettier_server.js} | 0 crates/project/src/project.rs | 10 +++++----- 7 files changed, 7 insertions(+), 39 deletions(-) delete mode 100644 crates/prettier/prettier_server/.gitignore delete mode 100644 crates/prettier/prettier_server/.zed/settings.json delete mode 100644 crates/prettier/prettier_server/package-lock.json delete mode 100644 crates/prettier/prettier_server/package.json rename crates/prettier/{prettier_server/src/index.js => src/prettier_server.js} (100%) diff --git a/crates/prettier/prettier_server/.gitignore b/crates/prettier/prettier_server/.gitignore deleted file mode 100644 index c2658d7d1b31848c3b71960543cb0368e56cd4c7..0000000000000000000000000000000000000000 --- a/crates/prettier/prettier_server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/crates/prettier/prettier_server/.zed/settings.json b/crates/prettier/prettier_server/.zed/settings.json deleted file mode 100644 index 856536c62db360e7ab69b2b356167826f64c47d5..0000000000000000000000000000000000000000 --- a/crates/prettier/prettier_server/.zed/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "languages": { - "JavaScript": { - "tab_size": 4 - } - } -} diff --git a/crates/prettier/prettier_server/package-lock.json b/crates/prettier/prettier_server/package-lock.json deleted file mode 100644 index 01fd75f3ddd44f2f62f9435ac4d43c42dbc0f75a..0000000000000000000000000000000000000000 --- a/crates/prettier/prettier_server/package-lock.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "prettier_server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "prettier_server", - "version": "1.0.0" - } - } -} diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json deleted file mode 100644 index a591a37bdb6ca6614da464784ef30fc3b82659db..0000000000000000000000000000000000000000 --- a/crates/prettier/prettier_server/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "prettier_server", - "version": "1.0.0", - "description": "", - "main": "src/index.js", - "scripts": { - "start": "node src/index.js", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Zed Industries", - "dev-dependencies": { - "prettier": "^3.0" - } -} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index d68af349d9362c9f08d935e1dcf8345f8c704800..e5b001f226bbae40b12f4875eb95e99e8ebd4fb1 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -18,6 +18,8 @@ pub struct LocateStart { pub starting_path: Arc, } +pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); + impl Prettier { // This was taken from the prettier-vscode extension. pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/src/prettier_server.js similarity index 100% rename from crates/prettier/prettier_server/src/index.js rename to crates/prettier/src/prettier_server.js diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ffc15ee0d3e085cf5eabcda5a2180f582ad66b06..b3d00afd8a350d6ab734d0682bd471abaf5b452a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -79,7 +79,7 @@ use std::{ time::{Duration, Instant}, }; use terminals::Terminals; -use text::Anchor; +use text::{Anchor, LineEnding, Rope}; use util::{ debug_panic, defer, http::HttpClient, @@ -8350,10 +8350,10 @@ impl Project { let fs = Arc::clone(&self.fs); cx.background() .spawn(async move { - let prettier_dir_metadata = fs.metadata(default_prettier_dir).await.with_context(|| format!("fetching FS metadata for prettier default dir {default_prettier_dir:?}"))?; - if prettier_dir_metadata.is_none() { - fs.create_dir(default_prettier_dir).await.with_context(|| format!("creating prettier default dir {default_prettier_dir:?}"))?; - } + let prettier_wrapper_path = default_prettier_dir.join("prettier_server.js"); + // method creates parent directory if it doesn't exist + fs.save(&prettier_wrapper_path, &Rope::from(prettier::PRETTIER_SERVER_JS), LineEnding::Unix).await + .with_context(|| format!("writing prettier_server.js file at {prettier_wrapper_path:?}"))?; let packages_to_versions = future::try_join_all( prettier_plugins From 2a68f014027b501d1a15cdfbf58358540543b086 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 14:56:40 +0300 Subject: [PATCH 078/180] Draft prettier_server formatting --- Cargo.lock | 1 + crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 85 ++++++++++++++++++++++---- crates/prettier/src/prettier_server.js | 82 +++++++++++++++---------- crates/project/src/project.rs | 25 +++----- 5 files changed, 137 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d7e25037792866cdcad56b21380745ecb393a6d..1795d3d7481d98cac2560b47dc7475001042f048 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5527,6 +5527,7 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "lsp", "node_runtime", "serde", "serde_derive", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index ab8d4d9e16f8558fcbc3eeeb44000a15cef16bbe..d10e8abdf94c1de4ca7b47897acab70a9eaf6cfd 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -10,6 +10,7 @@ path = "src/prettier.rs" language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } +lsp = { path = "../lsp" } node_runtime = { path = "../node_runtime"} util = { path = "../util" } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index e5b001f226bbae40b12f4875eb95e99e8ebd4fb1..fc7736b0e0f42b755b1fde097844c189cb16bdda 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -4,12 +4,15 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; -use gpui::ModelHandle; +use gpui::{AsyncAppContext, ModelHandle, Task}; use language::{Buffer, Diff}; +use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; +use serde::{Deserialize, Serialize}; +use util::paths::DEFAULT_PRETTIER_DIR; pub struct Prettier { - _private: (), + server: Arc, } #[derive(Debug)] @@ -18,7 +21,9 @@ pub struct LocateStart { pub starting_path: Arc, } +pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js"; pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); +const PRETTIER_PACKAGE_NAME: &str = "prettier"; impl Prettier { // This was taken from the prettier-vscode extension. @@ -141,16 +146,55 @@ impl Prettier { } } - pub async fn start(prettier_dir: &Path, node: Arc) -> anyhow::Result { - anyhow::ensure!( - prettier_dir.is_dir(), - "Prettier dir {prettier_dir:?} is not a directory" - ); - anyhow::bail!("TODO kb: start prettier server in {prettier_dir:?}") + pub fn start( + prettier_dir: PathBuf, + node: Arc, + cx: AsyncAppContext, + ) -> Task> { + cx.spawn(|cx| async move { + anyhow::ensure!( + prettier_dir.is_dir(), + "Prettier dir {prettier_dir:?} is not a directory" + ); + let prettier_server = DEFAULT_PRETTIER_DIR.join(PRETTIER_SERVER_FILE); + anyhow::ensure!( + prettier_server.is_file(), + "no prettier server package found at {prettier_server:?}" + ); + + let node_path = node.binary_path().await?; + let server = LanguageServer::new( + LanguageServerId(0), + LanguageServerBinary { + path: node_path, + arguments: vec![prettier_server.into(), prettier_dir.into()], + }, + Path::new("/"), + None, + cx, + ) + .context("prettier server creation")?; + let server = server + .initialize(None) + .await + .context("prettier server initialization")?; + Ok(Self { server }) + }) } - pub async fn format(&self, buffer: &ModelHandle) -> anyhow::Result { - todo!() + pub async fn format( + &self, + buffer: &ModelHandle, + cx: &AsyncAppContext, + ) -> anyhow::Result { + let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text()); + let response = self + .server + .request::(PrettierFormatParams { text: buffer_text }) + .await + .context("prettier format request")?; + dbg!("Formatted text", response.text); + anyhow::bail!("TODO kb calculate the diff") } pub async fn clear_cache(&self) -> anyhow::Result<()> { @@ -158,7 +202,6 @@ impl Prettier { } } -const PRETTIER_PACKAGE_NAME: &str = "prettier"; async fn find_closest_prettier_dir( paths_to_check: Vec, fs: &dyn Fs, @@ -206,3 +249,23 @@ async fn find_closest_prettier_dir( } Ok(None) } + +enum PrettierFormat {} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct PrettierFormatParams { + text: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct PrettierFormatResult { + text: String, +} + +impl lsp::request::Request for PrettierFormat { + type Params = PrettierFormatParams; + type Result = PrettierFormatResult; + const METHOD: &'static str = "prettier/format"; +} diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index d232ac9efe5ad4b9405d6baa7d0a18e2250c715e..0caa45b917e084bc5851f6966956e33ea4b47fd8 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -5,17 +5,17 @@ const { once } = require('events'); const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { - console.error(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); + sendResponse(makeError(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`)); process.exit(1); } fs.stat(prettierContainerPath, (err, stats) => { if (err) { - console.error(`Path '${prettierContainerPath}' does not exist.`); + sendResponse(makeError(`Path '${prettierContainerPath}' does not exist.`)); process.exit(1); } if (!stats.isDirectory()) { - console.log(`Path '${prettierContainerPath}' exists but is not a directory.`); + sendResponse(makeError(`Path '${prettierContainerPath}' exists but is not a directory.`)); process.exit(1); } }); @@ -26,19 +26,19 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); let prettier; try { prettier = await loadPrettier(prettierPath); - } catch (error) { - console.error("Failed to load prettier: ", error); + } catch (e) { + sendResponse(makeError(`Failed to load prettier: ${e}`)); process.exit(1); } - console.log("Prettier loadded successfully."); + sendResponse(makeError("Prettier loadded successfully.")); process.stdin.resume(); handleBuffer(prettier); })() async function handleBuffer(prettier) { for await (let messageText of readStdin()) { - handleData(messageText, prettier).catch(e => { - console.error("Failed to handle formatter request", e); + handleMessage(messageText, prettier).catch(e => { + sendResponse(makeError(`error during message handling: ${e}`)); }); } } @@ -107,43 +107,63 @@ async function* readStdin() { yield message.toString('utf8'); } } catch (e) { - console.error(`Error reading stdin: ${e}`); + sendResponse(makeError(`Error reading stdin: ${e}`)); } finally { process.stdin.off('data', () => { }); } } -async function handleData(messageText, prettier) { - try { - const message = JSON.parse(messageText); - await handleMessage(prettier, message); - } catch (e) { - sendResponse(makeError(`Request JSON parse error: ${e}`)); - } -} - -// format -// clear_cache -// +// ? // shutdown // error +async function handleMessage(messageText, prettier) { + const message = JSON.parse(messageText); + const { method, id, params } = message; + if (method === undefined) { + throw new Error(`Message method is undefined: ${messageText}`); + } + if (id === undefined) { + throw new Error(`Message id is undefined: ${messageText}`); + } -async function handleMessage(prettier, message) { - // TODO kb handle message.method, message.params and message.id - console.log(`message: ${JSON.stringify(message)}`); - sendResponse({ method: "hi", result: null }); + if (method === 'prettier/format') { + if (params === undefined || params.text === undefined) { + throw new Error(`Message params.text is undefined: ${messageText}`); + } + let formattedText = await prettier.format(params.text); + sendResponse({ id, result: { text: formattedText } }); + } else if (method === 'prettier/clear_cache') { + prettier.clearConfigCache(); + sendResponse({ id, result: null }); + } else if (method === 'initialize') { + sendResponse({ + id, + result: { + "capabilities": {} + } + }); + } else { + throw new Error(`Unknown method: ${method}`); + } } function makeError(message) { - return { method: "error", message }; + return { + error: { + "code": -32600, // invalid request code + message, + } + }; } function sendResponse(response) { - const message = Buffer.from(JSON.stringify(response)); - const length = Buffer.alloc(4); - length.writeUInt32LE(message.length); - process.stdout.write(length); - process.stdout.write(message); + let responsePayloadString = JSON.stringify({ + jsonrpc: "2.0", + ...response + }); + let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}\r\n\r\n`; + let dataToSend = headers + responsePayloadString; + process.stdout.write(dataToSend); } function loadPrettier(prettierPath) { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b3d00afd8a350d6ab734d0682bd471abaf5b452a..84b5f09cac6d49d32c0e2dcb1aea089821ea8415 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -53,7 +53,7 @@ use lsp::{ use lsp_command::*; use node_runtime::NodeRuntime; use postage::watch; -use prettier::{LocateStart, Prettier}; +use prettier::{LocateStart, Prettier, PRETTIER_SERVER_FILE, PRETTIER_SERVER_JS}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -4138,7 +4138,7 @@ impl Project { Ok(prettier) => { format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer) + .format(buffer, &cx) .await .context("formatting via prettier")?, )); @@ -4176,7 +4176,7 @@ impl Project { Ok(prettier) => { format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer) + .format(buffer, &cx) .await .context("formatting via prettier")?, )); @@ -8283,18 +8283,12 @@ impl Project { return existing_prettier; } - let task_prettier_dir = prettier_dir.clone(); + let start_task = Prettier::start(prettier_dir.clone(), node, cx.clone()); let new_prettier_task = cx .background() .spawn(async move { - Ok(Arc::new( - Prettier::start(&task_prettier_dir, node) - .await - .with_context(|| { - format!("starting new prettier for path {task_prettier_dir:?}") - })?, - )) - .map_err(Arc::new) + Ok(Arc::new(start_task.await.context("starting new prettier")?)) + .map_err(Arc::new) }) .shared(); this.update(&mut cx, |project, _| { @@ -8344,16 +8338,17 @@ impl Project { .get(&(worktree, default_prettier_dir.to_path_buf())) { // TODO kb need to compare plugins, install missing and restart prettier + // TODO kb move the entire prettier init logic into prettier.rs return; } let fs = Arc::clone(&self.fs); cx.background() .spawn(async move { - let prettier_wrapper_path = default_prettier_dir.join("prettier_server.js"); + let prettier_wrapper_path = default_prettier_dir.join(PRETTIER_SERVER_FILE); // method creates parent directory if it doesn't exist - fs.save(&prettier_wrapper_path, &Rope::from(prettier::PRETTIER_SERVER_JS), LineEnding::Unix).await - .with_context(|| format!("writing prettier_server.js file at {prettier_wrapper_path:?}"))?; + fs.save(&prettier_wrapper_path, &Rope::from(PRETTIER_SERVER_JS), LineEnding::Unix).await + .with_context(|| format!("writing {PRETTIER_SERVER_FILE} file at {prettier_wrapper_path:?}"))?; let packages_to_versions = future::try_join_all( prettier_plugins From 6a8e3fd02d4cd1936b277cd3a40ddac4a076f0fe Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 17:16:16 +0300 Subject: [PATCH 079/180] Add more parameters into prettier invocations --- crates/prettier/src/prettier.rs | 12 ++++++++++-- crates/prettier/src/prettier_server.js | 24 ++++++++++++++++-------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index fc7736b0e0f42b755b1fde097844c189cb16bdda..180b9c3de5ab94edad78a20443489d34a3dd9502 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -187,10 +187,15 @@ impl Prettier { buffer: &ModelHandle, cx: &AsyncAppContext, ) -> anyhow::Result { - let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text()); + let (buffer_text, buffer_language) = + buffer.read_with(cx, |buffer, _| (buffer.text(), buffer.language().cloned())); let response = self .server - .request::(PrettierFormatParams { text: buffer_text }) + .request::(PrettierFormatParams { + text: buffer_text, + path: None, + parser: None, + }) .await .context("prettier format request")?; dbg!("Formatted text", response.text); @@ -256,6 +261,9 @@ enum PrettierFormat {} #[serde(rename_all = "camelCase")] struct PrettierFormatParams { text: String, + // TODO kb have "options" or something more generic instead? + parser: Option, + path: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 0caa45b917e084bc5851f6966956e33ea4b47fd8..a0f7f0da5e320835ae9e027867a1c7cca5ed7538 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -43,6 +43,8 @@ async function handleBuffer(prettier) { } } +const headerSeparator = "\r\n"; + async function* readStdin() { let buffer = Buffer.alloc(0); let streamEnded = false; @@ -62,13 +64,12 @@ async function* readStdin() { } try { - const headersSeparator = "\r\n\r\n"; let contentLengthHeaderName = 'Content-Length'; let headersLength = null; let messageLength = null; main_loop: while (true) { if (messageLength === null) { - while (buffer.indexOf(headersSeparator) === -1) { + while (buffer.indexOf(`${headerSeparator}${headerSeparator}`) === -1) { if (streamEnded) { await handleStreamEnded('Unexpected end of stream: headers not found'); continue main_loop; @@ -78,16 +79,16 @@ async function* readStdin() { } await once(process.stdin, 'readable'); } - const headers = buffer.subarray(0, buffer.indexOf(headersSeparator)).toString('ascii'); - const contentLengthHeader = headers.split('\r\n').map(header => header.split(': ')) + const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii'); + const contentLengthHeader = headers.split(headerSeparator).map(header => header.split(':')) .filter(header => header[2] === undefined) .filter(header => (header[1] || '').length > 0) .find(header => header[0].trim() === contentLengthHeaderName); if (contentLengthHeader === undefined) { - await handleStreamEnded(`Missing or incorrect Content-Length header: ${headers}`); + await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`); continue main_loop; } - headersLength = headers.length + headersSeparator.length; + headersLength = headers.length + headerSeparator.length * 2; messageLength = parseInt(contentLengthHeader[1], 10); } @@ -130,7 +131,14 @@ async function handleMessage(messageText, prettier) { if (params === undefined || params.text === undefined) { throw new Error(`Message params.text is undefined: ${messageText}`); } - let formattedText = await prettier.format(params.text); + + let options = {}; + if (message.path !== undefined) { + options.filepath = message.path; + } else { + options.parser = message.parser || 'babel'; + } + const formattedText = await prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { prettier.clearConfigCache(); @@ -161,7 +169,7 @@ function sendResponse(response) { jsonrpc: "2.0", ...response }); - let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}\r\n\r\n`; + let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; let dataToSend = headers + responsePayloadString; process.stdout.write(dataToSend); } From e2056756ef97af2add0221ad8916315939d476db Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 17:34:58 +0300 Subject: [PATCH 080/180] Calculate the diff --- crates/prettier/src/prettier.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 180b9c3de5ab94edad78a20443489d34a3dd9502..f94801c01b63411ed188bc1810751ba2b8153488 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -187,6 +187,7 @@ impl Prettier { buffer: &ModelHandle, cx: &AsyncAppContext, ) -> anyhow::Result { + // TODO kb prettier needs either a path or a `parser` (depends on a language) option to format let (buffer_text, buffer_language) = buffer.read_with(cx, |buffer, _| (buffer.text(), buffer.language().cloned())); let response = self @@ -198,8 +199,8 @@ impl Prettier { }) .await .context("prettier format request")?; - dbg!("Formatted text", response.text); - anyhow::bail!("TODO kb calculate the diff") + let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); + Ok(diff_task.await) } pub async fn clear_cache(&self) -> anyhow::Result<()> { From 2a5b9b635ba1d250453984f9f8dc7fef32811d9d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 18:08:55 +0300 Subject: [PATCH 081/180] Better pass prettier options --- crates/language/src/language.rs | 9 ++++-- crates/prettier/src/prettier.rs | 41 +++++++++++++++++++------- crates/prettier/src/prettier_server.js | 12 +++++--- crates/project/src/project.rs | 2 +- crates/zed/src/languages/css.rs | 2 +- crates/zed/src/languages/html.rs | 2 +- crates/zed/src/languages/json.rs | 2 +- crates/zed/src/languages/php.rs | 7 ++--- crates/zed/src/languages/svelte.rs | 6 +--- crates/zed/src/languages/typescript.rs | 4 +-- crates/zed/src/languages/yaml.rs | 2 +- 11 files changed, 55 insertions(+), 34 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index ed0a0d8ee80712a8861adc4f0ff3f249e9952cd6..31415a2dd6c498a1c5a05ed2c680aade8dfe9dae 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -341,6 +341,7 @@ pub trait LspAdapter: 'static + Send + Sync { // TODO kb enable this for // markdown somehow? // tailwind (needs a css plugin, there are 2 of them) + // svelte (needs a plugin) fn enabled_formatters(&self) -> Vec { Vec::new() } @@ -348,12 +349,16 @@ pub trait LspAdapter: 'static + Send + Sync { #[derive(Clone, Debug, PartialEq, Eq)] pub enum BundledFormatter { - Prettier { plugin_names: Vec }, + Prettier { + parser_name: &'static str, + plugin_names: Vec, + }, } impl BundledFormatter { - pub fn prettier() -> Self { + pub fn prettier(parser_name: &'static str) -> Self { Self::Prettier { + parser_name, plugin_names: Vec::new(), } } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index f94801c01b63411ed188bc1810751ba2b8153488..68374d8a03b8a05745d3982da55752c5b581054d 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle, Task}; -use language::{Buffer, Diff}; +use language::{Buffer, BundledFormatter, Diff}; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; @@ -187,16 +187,30 @@ impl Prettier { buffer: &ModelHandle, cx: &AsyncAppContext, ) -> anyhow::Result { - // TODO kb prettier needs either a path or a `parser` (depends on a language) option to format - let (buffer_text, buffer_language) = - buffer.read_with(cx, |buffer, _| (buffer.text(), buffer.language().cloned())); + let params = buffer.read_with(cx, |buffer, cx| { + let path = buffer + .file() + .map(|file| file.full_path(cx)) + .map(|path| path.to_path_buf()); + let parser = buffer.language().and_then(|language| { + language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .find_map(|formatter| match formatter { + BundledFormatter::Prettier { parser_name, .. } => { + Some(parser_name.to_string()) + } + }) + }); + PrettierFormatParams { + text: buffer.text(), + options: FormatOptions { parser, path }, + } + }); let response = self .server - .request::(PrettierFormatParams { - text: buffer_text, - path: None, - parser: None, - }) + .request::(params) .await .context("prettier format request")?; let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); @@ -262,9 +276,14 @@ enum PrettierFormat {} #[serde(rename_all = "camelCase")] struct PrettierFormatParams { text: String, - // TODO kb have "options" or something more generic instead? + options: FormatOptions, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct FormatOptions { parser: Option, - path: Option, + path: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index a0f7f0da5e320835ae9e027867a1c7cca5ed7538..5e86720cd8349657980ffafe5b08798e640cf12d 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -131,12 +131,16 @@ async function handleMessage(messageText, prettier) { if (params === undefined || params.text === undefined) { throw new Error(`Message params.text is undefined: ${messageText}`); } + if (params.options === undefined) { + throw new Error(`Message params.options is undefined: ${messageText}`); + } let options = {}; - if (message.path !== undefined) { - options.filepath = message.path; - } else { - options.parser = message.parser || 'babel'; + if (params.options.path !== undefined) { + options.filepath = params.options.path; + } + if (params.options.parser !== undefined) { + options.parser = params.options.parser; } const formattedText = await prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 84b5f09cac6d49d32c0e2dcb1aea089821ea8415..cf8df9eafd406ae7bd02267d9df1b359a2c9d2f1 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8323,7 +8323,7 @@ impl Project { .flat_map(|adapter| adapter.enabled_formatters()) { match formatter { - BundledFormatter::Prettier { plugin_names } => prettier_plugins + BundledFormatter::Prettier { plugin_names, .. } => prettier_plugins .get_or_insert_with(|| HashSet::default()) .extend(plugin_names), } diff --git a/crates/zed/src/languages/css.rs b/crates/zed/src/languages/css.rs index a28523a741b67e687693d506466f64a290bd3586..f046437d75ec2ac824c81b33498522581fb89a9a 100644 --- a/crates/zed/src/languages/css.rs +++ b/crates/zed/src/languages/css.rs @@ -98,7 +98,7 @@ impl LspAdapter for CssLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("css")] } } diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index af8fb1690c1debd2c46b40f7ed3e0974714972ea..6f27b7ca8faa8ca5474a9dbcf7b6af219610ab2b 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -98,7 +98,7 @@ impl LspAdapter for HtmlLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("html")] } } diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index e66a5d96a69d04fd3b5e68faf83e093f2d30bb5f..f017af0a22a278dc10d8a36c6941e0fc3adaa1a1 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -148,7 +148,7 @@ impl LspAdapter for JsonLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("json")] } } diff --git a/crates/zed/src/languages/php.rs b/crates/zed/src/languages/php.rs index f8f9351111faf76cb806c3fc03b603425c67bf2d..bf65deb642d0ca39f1806acdab13d98e0d5195a8 100644 --- a/crates/zed/src/languages/php.rs +++ b/crates/zed/src/languages/php.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use collections::HashMap; -use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; @@ -100,13 +100,10 @@ impl LspAdapter for IntelephenseLspAdapter { async fn initialization_options(&self) -> Option { None } + async fn language_ids(&self) -> HashMap { HashMap::from_iter([("PHP".into(), "php".into())]) } - - fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] - } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs index 487720ce465c6e01ee0f9e04f8725b1885e855f7..5e42d80e77fe04763643cbb6cd6ba298ba9e9e92 100644 --- a/crates/zed/src/languages/svelte.rs +++ b/crates/zed/src/languages/svelte.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -95,10 +95,6 @@ impl LspAdapter for SvelteLspAdapter { "provideFormatter": true })) } - - fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] - } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 78b1214b1a29126959fa15113ef9ea29b309e712..f09c9645881401aaa43ba00c9f13757ca0b53224 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -163,7 +163,7 @@ impl LspAdapter for TypeScriptLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("typescript")] } } @@ -315,7 +315,7 @@ impl LspAdapter for EsLintLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("babel")] } } diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index c9168b34800a87a59616a78098533f4301126b3e..1c1ce1866866260b909262ae66e0771713b0df0d 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -111,7 +111,7 @@ impl LspAdapter for YamlLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("yaml")] } } From 39ad3a625c29f32f30a032b70e30ff7bd0811eb8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 18:36:18 +0300 Subject: [PATCH 082/180] Generify prettier properties, add tabWidth --- crates/prettier/src/prettier.rs | 18 ++++++++++++++---- crates/prettier/src/prettier_server.js | 10 +--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 68374d8a03b8a05745d3982da55752c5b581054d..a02b13951005f5510ff928f166258e5df7807035 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle, Task}; +use language::language_settings::language_settings; use language::{Buffer, BundledFormatter, Diff}; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; @@ -188,11 +189,13 @@ impl Prettier { cx: &AsyncAppContext, ) -> anyhow::Result { let params = buffer.read_with(cx, |buffer, cx| { - let path = buffer - .file() + let buffer_file = buffer.file(); + let buffer_language = buffer.language(); + let language_settings = language_settings(buffer_language, buffer_file, cx); + let path = buffer_file .map(|file| file.full_path(cx)) .map(|path| path.to_path_buf()); - let parser = buffer.language().and_then(|language| { + let parser = buffer_language.and_then(|language| { language .lsp_adapters() .iter() @@ -203,9 +206,14 @@ impl Prettier { } }) }); + let tab_width = Some(language_settings.tab_size.get()); PrettierFormatParams { text: buffer.text(), - options: FormatOptions { parser, path }, + options: FormatOptions { + parser, + path, + tab_width, + }, } }); let response = self @@ -283,7 +291,9 @@ struct PrettierFormatParams { #[serde(rename_all = "camelCase")] struct FormatOptions { parser: Option, + #[serde(rename = "filepath")] path: Option, + tab_width: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 5e86720cd8349657980ffafe5b08798e640cf12d..27259143342da681b69814dac9de36f46b494af6 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -134,15 +134,7 @@ async function handleMessage(messageText, prettier) { if (params.options === undefined) { throw new Error(`Message params.options is undefined: ${messageText}`); } - - let options = {}; - if (params.options.path !== undefined) { - options.filepath = params.options.path; - } - if (params.options.parser !== undefined) { - options.parser = params.options.parser; - } - const formattedText = await prettier.format(params.text, options); + const formattedText = await prettier.format(params.text, params.options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { prettier.clearConfigCache(); From e8409a0108f719a624af2113f2c8cf7f94bc6fcc Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 19 Sep 2023 10:25:33 +0300 Subject: [PATCH 083/180] Even more generic header printing in prettier_server --- crates/prettier/src/prettier_server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 27259143342da681b69814dac9de36f46b494af6..02c21811e2a9520d583174c4ea0466551cd55572 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -44,6 +44,7 @@ async function handleBuffer(prettier) { } const headerSeparator = "\r\n"; +const contentLengthHeaderName = 'Content-Length'; async function* readStdin() { let buffer = Buffer.alloc(0); @@ -64,7 +65,6 @@ async function* readStdin() { } try { - let contentLengthHeaderName = 'Content-Length'; let headersLength = null; let messageLength = null; main_loop: while (true) { @@ -165,7 +165,7 @@ function sendResponse(response) { jsonrpc: "2.0", ...response }); - let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; + let headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; let dataToSend = headers + responsePayloadString; process.stdout.write(dataToSend); } From 4b15a2bd63c1e702178ff83b15ea29dafb437c5e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 12:19:12 +0300 Subject: [PATCH 084/180] Rebase fixes --- crates/semantic_index/examples/eval.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/semantic_index/examples/eval.rs b/crates/semantic_index/examples/eval.rs index 573cf73d783fde74aba7669b1d09fc088bed67ff..33d6b3689c1f617a96d5fab00d41e51fa28d63f4 100644 --- a/crates/semantic_index/examples/eval.rs +++ b/crates/semantic_index/examples/eval.rs @@ -494,6 +494,7 @@ fn main() { let project = cx.update(|cx| { Project::local( client.clone(), + node_runtime::FakeNodeRuntime::new(), user_store.clone(), languages.clone(), fs.clone(), From 6cac58b34c67507d555ffccda6a04d86a312e47c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 13:07:10 +0300 Subject: [PATCH 085/180] Add prettier language servers to LSP logs panel --- crates/prettier/src/prettier.rs | 68 ++++++++++++++++++--------------- crates/project/src/project.rs | 27 ++++++++++--- 2 files changed, 59 insertions(+), 36 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index a02b13951005f5510ff928f166258e5df7807035..26368ddcdc0902246c9b9f2a3ae18ce1c1277a09 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; -use gpui::{AsyncAppContext, ModelHandle, Task}; +use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; use language::{Buffer, BundledFormatter, Diff}; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; @@ -147,40 +147,42 @@ impl Prettier { } } - pub fn start( + pub async fn start( + server_id: LanguageServerId, prettier_dir: PathBuf, node: Arc, cx: AsyncAppContext, - ) -> Task> { - cx.spawn(|cx| async move { - anyhow::ensure!( - prettier_dir.is_dir(), - "Prettier dir {prettier_dir:?} is not a directory" - ); - let prettier_server = DEFAULT_PRETTIER_DIR.join(PRETTIER_SERVER_FILE); - anyhow::ensure!( - prettier_server.is_file(), - "no prettier server package found at {prettier_server:?}" - ); + ) -> anyhow::Result { + let backgroud = cx.background(); + anyhow::ensure!( + prettier_dir.is_dir(), + "Prettier dir {prettier_dir:?} is not a directory" + ); + let prettier_server = DEFAULT_PRETTIER_DIR.join(PRETTIER_SERVER_FILE); + anyhow::ensure!( + prettier_server.is_file(), + "no prettier server package found at {prettier_server:?}" + ); - let node_path = node.binary_path().await?; - let server = LanguageServer::new( - LanguageServerId(0), - LanguageServerBinary { - path: node_path, - arguments: vec![prettier_server.into(), prettier_dir.into()], - }, - Path::new("/"), - None, - cx, - ) - .context("prettier server creation")?; - let server = server - .initialize(None) - .await - .context("prettier server initialization")?; - Ok(Self { server }) - }) + let node_path = backgroud + .spawn(async move { node.binary_path().await }) + .await?; + let server = LanguageServer::new( + server_id, + LanguageServerBinary { + path: node_path, + arguments: vec![prettier_server.into(), prettier_dir.into()], + }, + Path::new("/"), + None, + cx, + ) + .context("prettier server creation")?; + let server = backgroud + .spawn(server.initialize(None)) + .await + .context("prettier server initialization")?; + Ok(Self { server }) } pub async fn format( @@ -228,6 +230,10 @@ impl Prettier { pub async fn clear_cache(&self) -> anyhow::Result<()> { todo!() } + + pub fn server(&self) -> &Arc { + &self.server + } } async fn find_closest_prettier_dir( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index cf8df9eafd406ae7bd02267d9df1b359a2c9d2f1..8e080b594eb10c2d974aac868875c8a47c7b9b10 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8283,12 +8283,29 @@ impl Project { return existing_prettier; } - let start_task = Prettier::start(prettier_dir.clone(), node, cx.clone()); + let task_prettier_dir = prettier_dir.clone(); + let weak_project = this.downgrade(); + let new_server_id = + this.update(&mut cx, |this, _| this.languages.next_language_server_id()); let new_prettier_task = cx - .background() - .spawn(async move { - Ok(Arc::new(start_task.await.context("starting new prettier")?)) - .map_err(Arc::new) + .spawn(|mut cx| async move { + let prettier = + Prettier::start(new_server_id, task_prettier_dir, node, cx.clone()) + .await + .context("prettier start") + .map_err(Arc::new)?; + if let Some(project) = weak_project.upgrade(&mut cx) { + let prettier_server = Arc::clone(prettier.server()); + project.update(&mut cx, |project, cx| { + project.supplementary_language_servers.insert( + new_server_id, + // TODO kb same name repeats for different prettiers, distinguish + (LanguageServerName(Arc::from("prettier")), prettier_server), + ); + cx.emit(Event::LanguageServerAdded(new_server_id)); + }); + } + anyhow::Ok(Arc::new(prettier)).map_err(Arc::new) }) .shared(); this.update(&mut cx, |project, _| { From 06cac18d788c1373d1e38d22c9fcc0fe21ac6546 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 14:51:02 +0300 Subject: [PATCH 086/180] Return message id in prettier_server error responses --- crates/prettier/src/prettier_server.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 02c21811e2a9520d583174c4ea0466551cd55572..2e7e1fd2e60089d4df412ac1ef67540c0f3131ea 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -37,8 +37,15 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); async function handleBuffer(prettier) { for await (let messageText of readStdin()) { - handleMessage(messageText, prettier).catch(e => { - sendResponse(makeError(`error during message handling: ${e}`)); + let message; + try { + message = JSON.parse(messageText); + } catch (e) { + sendResponse(makeError(`Failed to parse message '${messageText}': ${e}`)); + continue; + } + handleMessage(message, prettier).catch(e => { + sendResponse({ id: message.id, ...makeError(`error during message handling: ${e}`) }); }); } } @@ -117,22 +124,21 @@ async function* readStdin() { // ? // shutdown // error -async function handleMessage(messageText, prettier) { - const message = JSON.parse(messageText); +async function handleMessage(message, prettier) { const { method, id, params } = message; if (method === undefined) { - throw new Error(`Message method is undefined: ${messageText}`); + throw new Error(`Message method is undefined: ${JSON.stringify(message)}`); } if (id === undefined) { - throw new Error(`Message id is undefined: ${messageText}`); + throw new Error(`Message id is undefined: ${JSON.stringify(message)}`); } if (method === 'prettier/format') { if (params === undefined || params.text === undefined) { - throw new Error(`Message params.text is undefined: ${messageText}`); + throw new Error(`Message params.text is undefined: ${JSON.stringify(message)}`); } if (params.options === undefined) { - throw new Error(`Message params.options is undefined: ${messageText}`); + throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } const formattedText = await prettier.format(params.text, params.options); sendResponse({ id, result: { text: formattedText } }); From b6872702070c7f7681324df94ebf8721a28172d5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 14:54:20 +0300 Subject: [PATCH 087/180] Implement missing prettier_server clear method --- crates/prettier/src/prettier.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 26368ddcdc0902246c9b9f2a3ae18ce1c1277a09..d5df6f092cfbca3c13c835d2fa5906220bf769d7 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -209,7 +209,7 @@ impl Prettier { }) }); let tab_width = Some(language_settings.tab_size.get()); - PrettierFormatParams { + FormatParams { text: buffer.text(), options: FormatOptions { parser, @@ -220,7 +220,7 @@ impl Prettier { }); let response = self .server - .request::(params) + .request::(params) .await .context("prettier format request")?; let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); @@ -228,7 +228,10 @@ impl Prettier { } pub async fn clear_cache(&self) -> anyhow::Result<()> { - todo!() + self.server + .request::(()) + .await + .context("prettier clear cache") } pub fn server(&self) -> &Arc { @@ -284,11 +287,11 @@ async fn find_closest_prettier_dir( Ok(None) } -enum PrettierFormat {} +enum Format {} #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct PrettierFormatParams { +struct FormatParams { text: String, options: FormatOptions, } @@ -304,12 +307,20 @@ struct FormatOptions { #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct PrettierFormatResult { +struct FormatResult { text: String, } -impl lsp::request::Request for PrettierFormat { - type Params = PrettierFormatParams; - type Result = PrettierFormatResult; +impl lsp::request::Request for Format { + type Params = FormatParams; + type Result = FormatResult; const METHOD: &'static str = "prettier/format"; } + +enum ClearCache {} + +impl lsp::request::Request for ClearCache { + type Params = (); + type Result = (); + const METHOD: &'static str = "prettier/clear_cache"; +} From 1b70e7d0df21d74f5133636580550d33fa4fbb28 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 15:00:26 +0300 Subject: [PATCH 088/180] Before server startup, log to stderr --- crates/prettier/src/prettier_server.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 2e7e1fd2e60089d4df412ac1ef67540c0f3131ea..e5c131cc00cecef932134c5b3d34879d135d9d5b 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -5,17 +5,17 @@ const { once } = require('events'); const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { - sendResponse(makeError(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`)); + process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); process.exit(1); } fs.stat(prettierContainerPath, (err, stats) => { if (err) { - sendResponse(makeError(`Path '${prettierContainerPath}' does not exist.`)); + process.stderr.write(`Path '${prettierContainerPath}' does not exist.`); process.exit(1); } if (!stats.isDirectory()) { - sendResponse(makeError(`Path '${prettierContainerPath}' exists but is not a directory.`)); + process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory.`); process.exit(1); } }); @@ -27,10 +27,10 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); try { prettier = await loadPrettier(prettierPath); } catch (e) { - sendResponse(makeError(`Failed to load prettier: ${e}`)); + process.stderr.write(`Failed to load prettier: ${e}`); process.exit(1); } - sendResponse(makeError("Prettier loadded successfully.")); + process.stderr.write("Prettier loadded successfully."); process.stdin.resume(); handleBuffer(prettier); })() From f42cb109a0f115bed92720721c4cf59a73f93574 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 21 Sep 2023 15:46:50 +0300 Subject: [PATCH 089/180] Improve prettier_server LSP names in the log panel --- crates/prettier/src/prettier.rs | 27 ++++++++++++-- crates/project/src/project.rs | 63 +++++++++++++++++++++++++++------ 2 files changed, 76 insertions(+), 14 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index d5df6f092cfbca3c13c835d2fa5906220bf769d7..5723cc67fc7079ee2224006a1645e37b6ef49406 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -13,6 +13,9 @@ use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; pub struct Prettier { + worktree_id: Option, + default: bool, + prettier_dir: PathBuf, server: Arc, } @@ -143,11 +146,12 @@ impl Prettier { .with_context(|| format!("finding prettier starting with {starting_path:?}"))? { Some(prettier_dir) => Ok(prettier_dir), - None => Ok(util::paths::DEFAULT_PRETTIER_DIR.to_path_buf()), + None => Ok(DEFAULT_PRETTIER_DIR.to_path_buf()), } } pub async fn start( + worktree_id: Option, server_id: LanguageServerId, prettier_dir: PathBuf, node: Arc, @@ -171,7 +175,7 @@ impl Prettier { server_id, LanguageServerBinary { path: node_path, - arguments: vec![prettier_server.into(), prettier_dir.into()], + arguments: vec![prettier_server.into(), prettier_dir.as_path().into()], }, Path::new("/"), None, @@ -182,7 +186,12 @@ impl Prettier { .spawn(server.initialize(None)) .await .context("prettier server initialization")?; - Ok(Self { server }) + Ok(Self { + worktree_id, + server, + default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), + prettier_dir, + }) } pub async fn format( @@ -237,6 +246,18 @@ impl Prettier { pub fn server(&self) -> &Arc { &self.server } + + pub fn is_default(&self) -> bool { + self.default + } + + pub fn prettier_dir(&self) -> &Path { + &self.prettier_dir + } + + pub fn worktree_id(&self) -> Option { + self.worktree_id + } } async fn find_closest_prettier_dir( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8e080b594eb10c2d974aac868875c8a47c7b9b10..2afedecad62539811832dbe9d5c4804a2f27b853 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8289,19 +8289,60 @@ impl Project { this.update(&mut cx, |this, _| this.languages.next_language_server_id()); let new_prettier_task = cx .spawn(|mut cx| async move { - let prettier = - Prettier::start(new_server_id, task_prettier_dir, node, cx.clone()) - .await - .context("prettier start") - .map_err(Arc::new)?; + let prettier = Prettier::start( + worktree_id.map(|id| id.to_usize()), + new_server_id, + task_prettier_dir, + node, + cx.clone(), + ) + .await + .context("prettier start") + .map_err(Arc::new)?; if let Some(project) = weak_project.upgrade(&mut cx) { - let prettier_server = Arc::clone(prettier.server()); project.update(&mut cx, |project, cx| { - project.supplementary_language_servers.insert( - new_server_id, - // TODO kb same name repeats for different prettiers, distinguish - (LanguageServerName(Arc::from("prettier")), prettier_server), - ); + let name = if prettier.is_default() { + LanguageServerName(Arc::from("prettier (default)")) + } else { + let prettier_dir = prettier.prettier_dir(); + let worktree_path = prettier + .worktree_id() + .map(WorktreeId::from_usize) + .and_then(|id| project.worktree_for_id(id, cx)) + .map(|worktree| worktree.read(cx).abs_path()); + match worktree_path { + Some(worktree_path) => { + if worktree_path.as_ref() == prettier_dir { + LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default() + ))) + } else { + let dir_to_display = match prettier_dir + .strip_prefix(&worktree_path) + .ok() + { + Some(relative_path) => relative_path, + None => prettier_dir, + }; + LanguageServerName(Arc::from(format!( + "prettier ({})", + dir_to_display.display(), + ))) + } + } + None => LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir.display(), + ))), + } + }; + project + .supplementary_language_servers + .insert(new_server_id, (name, Arc::clone(prettier.server()))); cx.emit(Event::LanguageServerAdded(new_server_id)); }); } From d021842fa1ff8dcc76f00e5b5a1950134edb11c9 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 21 Sep 2023 23:49:22 +0300 Subject: [PATCH 090/180] Properly log pre-lsp prettier_server events --- crates/prettier/src/prettier_server.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index e5c131cc00cecef932134c5b3d34879d135d9d5b..001adca474c0d662c82a204c3fedf9da6a5c60a7 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -5,17 +5,17 @@ const { once } = require('events'); const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { - process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); + process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`); process.exit(1); } fs.stat(prettierContainerPath, (err, stats) => { if (err) { - process.stderr.write(`Path '${prettierContainerPath}' does not exist.`); + process.stderr.write(`Path '${prettierContainerPath}' does not exist\n`); process.exit(1); } if (!stats.isDirectory()) { - process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory.`); + process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory\n`); process.exit(1); } }); @@ -27,10 +27,10 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); try { prettier = await loadPrettier(prettierPath); } catch (e) { - process.stderr.write(`Failed to load prettier: ${e}`); + process.stderr.write(`Failed to load prettier: ${e}\n`); process.exit(1); } - process.stderr.write("Prettier loadded successfully."); + process.stderr.write("Prettier loadded successfully\n"); process.stdin.resume(); handleBuffer(prettier); })() From f4667cbc3374463053f6e315bd90a535909a8ae0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 15:52:34 +0300 Subject: [PATCH 091/180] Resolve prettier config on server init --- crates/prettier/src/prettier.rs | 1 + crates/prettier/src/prettier_server.js | 20 +++++++++++++++----- crates/project/src/project.rs | 24 +++++++++++++++++++++++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 5723cc67fc7079ee2224006a1645e37b6ef49406..01f1a055d3c1da82dbec6f61f9b79f927c4acb2c 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -222,6 +222,7 @@ impl Prettier { text: buffer.text(), options: FormatOptions { parser, + // TODO kb is not absolute now path, tab_width, }, diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 001adca474c0d662c82a204c3fedf9da6a5c60a7..5896c295f63d1f1860105ef88569c5cc2c1653d8 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -21,18 +21,27 @@ fs.stat(prettierContainerPath, (err, stats) => { }); const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); +class Prettier { + constructor(path, prettier, config) { + this.path = path; + this.prettier = prettier; + this.config = config; + } +} (async () => { let prettier; + let config; try { prettier = await loadPrettier(prettierPath); + config = await prettier.resolveConfig(prettierPath) || {}; } catch (e) { process.stderr.write(`Failed to load prettier: ${e}\n`); process.exit(1); } - process.stderr.write("Prettier loadded successfully\n"); + process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${config}\n`); process.stdin.resume(); - handleBuffer(prettier); + handleBuffer(new Prettier(prettierPath, prettier, config)); })() async function handleBuffer(prettier) { @@ -121,7 +130,7 @@ async function* readStdin() { } } -// ? +// TODO kb, more methods? // shutdown // error async function handleMessage(message, prettier) { @@ -140,10 +149,11 @@ async function handleMessage(message, prettier) { if (params.options === undefined) { throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } - const formattedText = await prettier.format(params.text, params.options); + const formattedText = await prettier.prettier.format(params.text, { ...prettier.config, ...params.options }); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { - prettier.clearConfigCache(); + prettier.prettier.clearConfigCache(); + prettier.config = await prettier.prettier.resolveConfig(prettier.path) || {}; sendResponse({ id, result: null }); } else if (method === 'initialize') { sendResponse({ diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2afedecad62539811832dbe9d5c4804a2f27b853..a25fc2e178965428d9513c71db6a175b4b46448f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -911,7 +911,7 @@ impl Project { .detach(); } - // TODO kb restart all formatters if settings change + // TODO kb restart all default formatters if Zed prettier settings change for (worktree, language, settings) in language_formatters_to_check { self.maybe_start_default_formatters(worktree, &language, &settings, cx); } @@ -5987,6 +5987,7 @@ impl Project { this.update_local_worktree_buffers(&worktree, changes, cx); this.update_local_worktree_language_servers(&worktree, changes, cx); this.update_local_worktree_settings(&worktree, changes, cx); + this.update_prettier_settings(&worktree, changes, cx); cx.emit(Event::WorktreeUpdatedEntries( worktree.read(cx).id(), changes.clone(), @@ -6366,6 +6367,27 @@ impl Project { .detach(); } + fn update_prettier_settings( + &self, + worktree: &ModelHandle, + changes: &[(Arc, ProjectEntryId, PathChange)], + cx: &mut ModelContext<'_, Project>, + ) { + let prettier_config_files = Prettier::CONFIG_FILE_NAMES + .iter() + .map(Path::new) + .collect::>(); + let prettier_config_changes = changes + .iter() + .filter(|(path, _, _)| prettier_config_files.contains(path.as_ref())) + .collect::>(); + dbg!(prettier_config_changes); + dbg!(changes.len()); + // TODO kb: reset caches for all worktree-related prettiers (including the default one) on prettier config file _changes_ + // prepare node + prettier + plugins + prettier_server on files with Languages that have prettier formatter. Do not start it yet. + // !! Ignore **/node_modules/** files in both checks above. + } + pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { let new_active_entry = entry.and_then(|project_path| { let worktree = self.worktree_for_id(project_path.worktree_id, cx)?; From b109075bf2e3eaa3aa5b11ea3b2e21ba8daf3827 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 16:40:45 +0300 Subject: [PATCH 092/180] Watch for prettier file changes --- crates/project/src/project.rs | 60 +++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a25fc2e178965428d9513c71db6a175b4b46448f..bcf640d865efe0f7303ec3eee5a79dedbc248ee2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6377,15 +6377,58 @@ impl Project { .iter() .map(Path::new) .collect::>(); - let prettier_config_changes = changes + + let prettier_config_file_changed = changes + .iter() + .filter(|(_, _, change)| !matches!(change, PathChange::Loaded)) + .filter(|(path, _, _)| { + !path + .components() + .any(|component| component.as_os_str().to_str() == Some("node_modules")) + }) + .find(|(path, _, _)| prettier_config_files.contains(path.as_ref())); + let current_worktree_id = worktree.read(cx).id(); + if let Some((config_path, _, _)) = prettier_config_file_changed { + log::info!( + "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}" + ); + } + + let prettiers_to_reload = self + .prettier_instances .iter() - .filter(|(path, _, _)| prettier_config_files.contains(path.as_ref())) + .filter_map(|((worktree_id, prettier_path), prettier_task)| { + if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) { + Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) + } else { + None + } + }) .collect::>(); - dbg!(prettier_config_changes); - dbg!(changes.len()); - // TODO kb: reset caches for all worktree-related prettiers (including the default one) on prettier config file _changes_ - // prepare node + prettier + plugins + prettier_server on files with Languages that have prettier formatter. Do not start it yet. - // !! Ignore **/node_modules/** files in both checks above. + + cx.background() + .spawn(async move { + for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| { + async move { + prettier_task.await? + .clear_cache() + .await + .with_context(|| { + format!( + "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" + ) + }) + .map_err(Arc::new) + } + })) + .await + { + if let Err(e) = task_result { + log::error!("Failed to clear cache for prettier: {e:#}"); + } + } + }) + .detach(); } pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { @@ -8305,6 +8348,7 @@ impl Project { return existing_prettier; } + log::info!("Found prettier at {prettier_dir:?}, starting."); let task_prettier_dir = prettier_dir.clone(); let weak_project = this.downgrade(); let new_server_id = @@ -8321,6 +8365,8 @@ impl Project { .await .context("prettier start") .map_err(Arc::new)?; + log::info!("Had started prettier in {:?}", prettier.prettier_dir()); + if let Some(project) = weak_project.upgrade(&mut cx) { project.update(&mut cx, |project, cx| { let name = if prettier.is_default() { From 6ec3927dd3bdd0e0729990d9dd9fbdcf196b6743 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 18:40:12 +0300 Subject: [PATCH 093/180] Allow to configure default prettier --- Cargo.lock | 1 + assets/settings/default.json | 16 ++++++++++- crates/language/src/language_settings.rs | 8 ++++-- crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 36 ++++++++++++++++++++---- crates/prettier/src/prettier_server.js | 8 +++++- crates/project/src/project.rs | 3 +- 7 files changed, 60 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1795d3d7481d98cac2560b47dc7475001042f048..4d9b3ee71364be0a79f98ad3ef55b95087561d74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5523,6 +5523,7 @@ name = "prettier" version = "0.1.0" dependencies = [ "anyhow", + "collections", "fs", "futures 0.3.28", "gpui", diff --git a/assets/settings/default.json b/assets/settings/default.json index be47ac9c8c1b91e90df800a79367cf4ebcd5ac02..1611d80e2ffbb24018c07d0081e930cc05defc2e 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -199,7 +199,11 @@ // "arguments": ["--stdin-filepath", "{buffer_path}"] // } // } - // TODO kb description + // 3. Format code using Zed's Prettier integration: + // "formatter": "prettier" + // 4. Default. Format files using Zed's Prettier integration (if applicable), + // or falling back to formatting via language server: + // "formatter": "auto" "formatter": "auto", // How to soft-wrap long lines of text. This setting can take // three values: @@ -430,6 +434,16 @@ "tab_size": 2 } }, + // Zed's Prettier integration settings. + // If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if + // project has no other Prettier installed. + "prettier": { + // Use regular Prettier json configuration: + // "trailingComma": "es5", + // "tabWidth": 4, + // "semi": false, + // "singleQuote": true + }, // LSP Specific settings. "lsp": { // Specify the LSP name as a key here. diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 8778ba8d6405cc415381f5153e7afecd03c8efa8..9cac5c523ea64320381d7f60d1d144020ff2599a 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -50,6 +50,7 @@ pub struct LanguageSettings { pub remove_trailing_whitespace_on_save: bool, pub ensure_final_newline_on_save: bool, pub formatter: Formatter, + pub prettier: HashMap, pub enable_language_server: bool, pub show_copilot_suggestions: bool, pub show_whitespaces: ShowWhitespaceSetting, @@ -98,6 +99,8 @@ pub struct LanguageSettingsContent { #[serde(default)] pub formatter: Option, #[serde(default)] + pub prettier: Option>, + #[serde(default)] pub enable_language_server: Option, #[serde(default)] pub show_copilot_suggestions: Option, @@ -155,9 +158,7 @@ pub enum Formatter { #[default] Auto, LanguageServer, - Prettier { - config: (), // Support some of the most important settings in the prettier-vscode extension. - }, + Prettier, External { command: Arc, arguments: Arc<[String]>, @@ -397,6 +398,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent src.preferred_line_length, ); merge(&mut settings.formatter, src.formatter.clone()); + merge(&mut settings.prettier, src.prettier.clone()); merge(&mut settings.format_on_save, src.format_on_save.clone()); merge( &mut settings.remove_trailing_whitespace_on_save, diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index d10e8abdf94c1de4ca7b47897acab70a9eaf6cfd..129e928da2ce3f4ca980c938253135bd5a37d322 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" path = "src/prettier.rs" [dependencies] +collections = { path = "../collections"} language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 01f1a055d3c1da82dbec6f61f9b79f927c4acb2c..77baf6cbf3a92616237b7dbba192bf201e68bfc1 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,8 +1,9 @@ -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; +use collections::HashMap; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; @@ -202,7 +203,6 @@ impl Prettier { let params = buffer.read_with(cx, |buffer, cx| { let buffer_file = buffer.file(); let buffer_language = buffer.language(); - let language_settings = language_settings(buffer_language, buffer_file, cx); let path = buffer_file .map(|file| file.full_path(cx)) .map(|path| path.to_path_buf()); @@ -217,14 +217,38 @@ impl Prettier { } }) }); - let tab_width = Some(language_settings.tab_size.get()); + + let prettier_options = if self.default { + let language_settings = language_settings(buffer_language, buffer_file, cx); + let mut options = language_settings.prettier.clone(); + if !options.contains_key("tabWidth") { + options.insert( + "tabWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.tab_size.get(), + )), + ); + } + if !options.contains_key("printWidth") { + options.insert( + "printWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.preferred_line_length, + )), + ); + } + Some(options) + } else { + None + }; + FormatParams { text: buffer.text(), options: FormatOptions { parser, // TODO kb is not absolute now path, - tab_width, + prettier_options, }, } }); @@ -318,13 +342,13 @@ struct FormatParams { options: FormatOptions, } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FormatOptions { parser: Option, #[serde(rename = "filepath")] path: Option, - tab_width: Option, + prettier_options: Option>, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 5896c295f63d1f1860105ef88569c5cc2c1653d8..0a16a73b1419c72313224e75519434195a27a390 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -149,7 +149,13 @@ async function handleMessage(message, prettier) { if (params.options === undefined) { throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } - const formattedText = await prettier.prettier.format(params.text, { ...prettier.config, ...params.options }); + + const options = { + ...(params.options.prettierOptions || prettier.config), + parser: params.options.parser, + path: params.options.path + }; + const formattedText = await prettier.prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { prettier.prettier.clearConfigCache(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bcf640d865efe0f7303ec3eee5a79dedbc248ee2..059256ee3f4a4cb9568f8977f962685a01f15f52 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -911,7 +911,6 @@ impl Project { .detach(); } - // TODO kb restart all default formatters if Zed prettier settings change for (worktree, language, settings) in language_formatters_to_check { self.maybe_start_default_formatters(worktree, &language, &settings, cx); } @@ -8428,7 +8427,7 @@ impl Project { } fn maybe_start_default_formatters( - &mut self, + &self, worktree: Option, new_language: &Language, language_settings: &LanguageSettings, From afee29ad3f9df1eb79d43ed12a593e3449bc0c55 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 18:48:30 +0300 Subject: [PATCH 094/180] Do not clear cache for default prettiers --- crates/prettier/src/prettier_server.js | 3 ++ crates/project/src/project.rs | 70 +++++++++++++------------- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 0a16a73b1419c72313224e75519434195a27a390..dcc625b8779b5d19aebeef6ffbb529de18f98135 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -155,6 +155,9 @@ async function handleMessage(message, prettier) { parser: params.options.parser, path: params.options.path }; + // TODO kb always resolve prettier config for each file. + // need to understand if default prettier can be affected by other configs in the project + // (restart default prettiers on config changes too then) const formattedText = await prettier.prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 059256ee3f4a4cb9568f8977f962685a01f15f52..df504ad506ba53f122d06590465dfbc5c8dff9d8 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6391,43 +6391,42 @@ impl Project { log::info!( "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}" ); - } - - let prettiers_to_reload = self - .prettier_instances - .iter() - .filter_map(|((worktree_id, prettier_path), prettier_task)| { - if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) { - Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) - } else { - None - } - }) - .collect::>(); - - cx.background() - .spawn(async move { - for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| { - async move { - prettier_task.await? - .clear_cache() - .await - .with_context(|| { - format!( - "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" - ) - }) - .map_err(Arc::new) + let prettiers_to_reload = self + .prettier_instances + .iter() + .filter_map(|((worktree_id, prettier_path), prettier_task)| { + if worktree_id == &Some(current_worktree_id) { + Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) + } else { + None } - })) - .await - { - if let Err(e) = task_result { - log::error!("Failed to clear cache for prettier: {e:#}"); + }) + .collect::>(); + + cx.background() + .spawn(async move { + for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| { + async move { + prettier_task.await? + .clear_cache() + .await + .with_context(|| { + format!( + "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" + ) + }) + .map_err(Arc::new) + } + })) + .await + { + if let Err(e) = task_result { + log::error!("Failed to clear cache for prettier: {e:#}"); + } } - } - }) - .detach(); + }) + .detach(); + } } pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { @@ -8410,6 +8409,7 @@ impl Project { project .supplementary_language_servers .insert(new_server_id, (name, Arc::clone(prettier.server()))); + // TODO kb could there be a race with multiple default prettier instances added? cx.emit(Event::LanguageServerAdded(new_server_id)); }); } From 8a807102a668c57eb18c8dfbdeef467066f25685 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 23:49:03 +0300 Subject: [PATCH 095/180] Properly support prettier plugins --- Cargo.lock | 1 + crates/language/src/language.rs | 10 ++-- crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 72 ++++++++++++++++++++++---- crates/prettier/src/prettier_server.js | 1 + crates/project/src/project.rs | 30 ++++++----- crates/zed/src/languages/svelte.rs | 9 +++- crates/zed/src/languages/tailwind.rs | 9 +++- 8 files changed, 100 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d9b3ee71364be0a79f98ad3ef55b95087561d74..0175755f967cbc88f63ed8967b06b5360c6daa29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5528,6 +5528,7 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "log", "lsp", "node_runtime", "serde", diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 31415a2dd6c498a1c5a05ed2c680aade8dfe9dae..c565d09c98f8f142b5a5f561cbdc2e7e61051714 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -338,10 +338,6 @@ pub trait LspAdapter: 'static + Send + Sync { Default::default() } - // TODO kb enable this for - // markdown somehow? - // tailwind (needs a css plugin, there are 2 of them) - // svelte (needs a plugin) fn enabled_formatters(&self) -> Vec { Vec::new() } @@ -350,15 +346,15 @@ pub trait LspAdapter: 'static + Send + Sync { #[derive(Clone, Debug, PartialEq, Eq)] pub enum BundledFormatter { Prettier { - parser_name: &'static str, - plugin_names: Vec, + parser_name: Option<&'static str>, + plugin_names: Vec<&'static str>, }, } impl BundledFormatter { pub fn prettier(parser_name: &'static str) -> Self { Self::Prettier { - parser_name, + parser_name: Some(parser_name), plugin_names: Vec::new(), } } diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 129e928da2ce3f4ca980c938253135bd5a37d322..cfac4c4757e2871321da86a4061c8077a379cc4f 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -15,6 +15,7 @@ lsp = { path = "../lsp" } node_runtime = { path = "../node_runtime"} util = { path = "../util" } +log.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 77baf6cbf3a92616237b7dbba192bf201e68bfc1..3e21cad272cddb65e5f6a76670210f53d9752b4a 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; -use collections::HashMap; +use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; @@ -29,6 +29,7 @@ pub struct LocateStart { pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js"; pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); const PRETTIER_PACKAGE_NAME: &str = "prettier"; +const TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME: &str = "prettier-plugin-tailwindcss"; impl Prettier { // This was taken from the prettier-vscode extension. @@ -206,17 +207,64 @@ impl Prettier { let path = buffer_file .map(|file| file.full_path(cx)) .map(|path| path.to_path_buf()); - let parser = buffer_language.and_then(|language| { - language - .lsp_adapters() - .iter() - .flat_map(|adapter| adapter.enabled_formatters()) - .find_map(|formatter| match formatter { - BundledFormatter::Prettier { parser_name, .. } => { - Some(parser_name.to_string()) + let parsers_with_plugins = buffer_language + .into_iter() + .flat_map(|language| { + language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .filter_map(|formatter| match formatter { + BundledFormatter::Prettier { + parser_name, + plugin_names, + } => Some((parser_name, plugin_names)), + }) + }) + .fold( + HashMap::default(), + |mut parsers_with_plugins, (parser_name, plugins)| { + match parser_name { + Some(parser_name) => parsers_with_plugins + .entry(parser_name) + .or_insert_with(HashSet::default) + .extend(plugins), + None => parsers_with_plugins.values_mut().for_each(|existing_plugins| { + existing_plugins.extend(plugins.iter()); + }), } - }) - }); + parsers_with_plugins + }, + ); + + let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len()); + if parsers_with_plugins.len() > 1 { + log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); + } + + // TODO kb move the entire prettier server js file into *.mjs one instead? + let plugin_name_into_path = |plugin_name: &str| self.prettier_dir.join("node_modules").join(plugin_name).join("dist").join("index.mjs"); + let (parser, plugins) = match selected_parser_with_plugins { + Some((parser, plugins)) => { + // Tailwind plugin requires being added last + // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins + let mut add_tailwind_back = false; + + let mut plugins = plugins.into_iter().filter(|&&plugin_name| { + if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME { + add_tailwind_back = true; + false + } else { + true + } + }).map(|plugin_name| plugin_name_into_path(plugin_name)).collect::>(); + if add_tailwind_back { + plugins.push(plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME)); + } + (Some(parser.to_string()), plugins) + }, + None => (None, Vec::new()), + }; let prettier_options = if self.default { let language_settings = language_settings(buffer_language, buffer_file, cx); @@ -246,6 +294,7 @@ impl Prettier { text: buffer.text(), options: FormatOptions { parser, + plugins, // TODO kb is not absolute now path, prettier_options, @@ -345,6 +394,7 @@ struct FormatParams { #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FormatOptions { + plugins: Vec, parser: Option, #[serde(rename = "filepath")] path: Option, diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index dcc625b8779b5d19aebeef6ffbb529de18f98135..780a9c3106df1503dfef77a62cdae236955bf9e5 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -153,6 +153,7 @@ async function handleMessage(message, prettier) { const options = { ...(params.options.prettierOptions || prettier.config), parser: params.options.parser, + plugins: params.options.plugins, path: params.options.path }; // TODO kb always resolve prettier config for each file. diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index df504ad506ba53f122d06590465dfbc5c8dff9d8..f66ca039d778d6f68b088d558c43897496b7f08d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -852,6 +852,7 @@ impl Project { } } let worktree = buffer_file + // TODO kb wrong usage (+ look around for another one like this) .map(|f| f.worktree_id()) .map(WorktreeId::from_usize); language_formatters_to_check.push(( @@ -912,7 +913,7 @@ impl Project { } for (worktree, language, settings) in language_formatters_to_check { - self.maybe_start_default_formatters(worktree, &language, &settings, cx); + self.install_default_formatters(worktree, &language, &settings, cx); } // Start all the newly-enabled language servers. @@ -2669,7 +2670,7 @@ impl Project { .map(|f| f.worktree_id()) .map(WorktreeId::from_usize); let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); - self.maybe_start_default_formatters(worktree, &new_language, &settings, cx); + self.install_default_formatters(worktree, &new_language, &settings, cx); if let Some(file) = File::from_dyn(buffer_file.as_ref()) { let worktree = file.worktree.clone(); @@ -6395,7 +6396,7 @@ impl Project { .prettier_instances .iter() .filter_map(|((worktree_id, prettier_path), prettier_task)| { - if worktree_id == &Some(current_worktree_id) { + if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) { Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) } else { None @@ -6412,7 +6413,7 @@ impl Project { .await .with_context(|| { format!( - "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" + "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update" ) }) .map_err(Arc::new) @@ -8410,6 +8411,7 @@ impl Project { .supplementary_language_servers .insert(new_server_id, (name, Arc::clone(prettier.server()))); // TODO kb could there be a race with multiple default prettier instances added? + // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) cx.emit(Event::LanguageServerAdded(new_server_id)); }); } @@ -8426,7 +8428,7 @@ impl Project { Some(task) } - fn maybe_start_default_formatters( + fn install_default_formatters( &self, worktree: Option, new_language: &Language, @@ -8458,14 +8460,10 @@ impl Project { }; let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); - if let Some(_already_running) = self + let already_running_prettier = self .prettier_instances .get(&(worktree, default_prettier_dir.to_path_buf())) - { - // TODO kb need to compare plugins, install missing and restart prettier - // TODO kb move the entire prettier init logic into prettier.rs - return; - } + .cloned(); let fs = Arc::clone(&self.fs); cx.background() @@ -8478,8 +8476,7 @@ impl Project { let packages_to_versions = future::try_join_all( prettier_plugins .iter() - .map(|s| s.as_str()) - .chain(Some("prettier")) + .chain(Some(&"prettier")) .map(|package_name| async { let returned_package_name = package_name.to_string(); let latest_version = node.npm_package_latest_version(package_name) @@ -8497,6 +8494,13 @@ impl Project { (package.as_str(), version.as_str()) }).collect::>(); node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?; + + if !prettier_plugins.is_empty() { + if let Some(prettier) = already_running_prettier { + prettier.await.map_err(|e| anyhow::anyhow!("Default prettier startup await failure: {e:#}"))?.clear_cache().await.context("clearing default prettier cache after plugins install")?; + } + } + anyhow::Ok(()) }) .detach_and_log_err(cx); diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs index 5e42d80e77fe04763643cbb6cd6ba298ba9e9e92..2089fe88b1ce03702a62e61033ec954fa9bdb789 100644 --- a/crates/zed/src/languages/svelte.rs +++ b/crates/zed/src/languages/svelte.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -95,6 +95,13 @@ impl LspAdapter for SvelteLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::Prettier { + parser_name: Some("svelte"), + plugin_names: vec!["prettier-plugin-svelte"], + }] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/tailwind.rs b/crates/zed/src/languages/tailwind.rs index cf07fa71c9c82f7866c0dcabbf8d8fbd50acb3cf..8e81f728dc1f39de023a8292018518ad772d3b3a 100644 --- a/crates/zed/src/languages/tailwind.rs +++ b/crates/zed/src/languages/tailwind.rs @@ -6,7 +6,7 @@ use futures::{ FutureExt, StreamExt, }; use gpui::AppContext; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::{json, Value}; @@ -127,6 +127,13 @@ impl LspAdapter for TailwindLspAdapter { .into_iter(), ) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::Prettier { + parser_name: None, + plugin_names: vec!["prettier-plugin-tailwindcss"], + }] + } } async fn get_cached_server_binary( From 658b58378ec8e86e52da83bc089ffe6f49af7574 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 23 Sep 2023 00:04:49 +0300 Subject: [PATCH 096/180] Properly use WorktreeId --- crates/project/src/project.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f66ca039d778d6f68b088d558c43897496b7f08d..90e7f2446ba56a04ebba2665e1c8556c8477c745 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -841,22 +841,18 @@ impl Project { for buffer in self.opened_buffers.values() { if let Some(buffer) = buffer.upgrade(cx) { let buffer = buffer.read(cx); - let buffer_file = buffer.file(); + let buffer_file = File::from_dyn(buffer.file()); let buffer_language = buffer.language(); - let settings = language_settings(buffer_language, buffer_file, cx); + let settings = language_settings(buffer_language, buffer.file(), cx); if let Some(language) = buffer_language { if settings.enable_language_server { - if let Some(file) = File::from_dyn(buffer_file) { + if let Some(file) = buffer_file { language_servers_to_start .push((file.worktree.clone(), Arc::clone(language))); } } - let worktree = buffer_file - // TODO kb wrong usage (+ look around for another one like this) - .map(|f| f.worktree_id()) - .map(WorktreeId::from_usize); language_formatters_to_check.push(( - worktree, + buffer_file.map(|f| f.worktree_id(cx)), Arc::clone(language), settings.clone(), )); @@ -2665,14 +2661,12 @@ impl Project { }); let buffer_file = buffer.read(cx).file().cloned(); - let worktree = buffer_file - .as_ref() - .map(|f| f.worktree_id()) - .map(WorktreeId::from_usize); let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); + let buffer_file = File::from_dyn(buffer_file.as_ref()); + let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); self.install_default_formatters(worktree, &new_language, &settings, cx); - if let Some(file) = File::from_dyn(buffer_file.as_ref()) { + if let Some(file) = buffer_file { let worktree = file.worktree.clone(); if let Some(tree) = worktree.read(cx).as_local() { self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx); From a9f80a603ca98470080589e9badf5abe275e9532 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 23 Sep 2023 00:39:46 +0300 Subject: [PATCH 097/180] Resolve prettier config before every formatting --- crates/prettier/src/prettier.rs | 11 +++-------- crates/prettier/src/prettier_server.js | 12 ++++++++---- crates/project/src/project.rs | 11 ++++++++--- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 3e21cad272cddb65e5f6a76670210f53d9752b4a..c08098ba23fa9067cd4eece273d8a224f1d0b043 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -199,14 +199,11 @@ impl Prettier { pub async fn format( &self, buffer: &ModelHandle, + buffer_path: Option, cx: &AsyncAppContext, ) -> anyhow::Result { let params = buffer.read_with(cx, |buffer, cx| { - let buffer_file = buffer.file(); let buffer_language = buffer.language(); - let path = buffer_file - .map(|file| file.full_path(cx)) - .map(|path| path.to_path_buf()); let parsers_with_plugins = buffer_language .into_iter() .flat_map(|language| { @@ -242,7 +239,6 @@ impl Prettier { log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); } - // TODO kb move the entire prettier server js file into *.mjs one instead? let plugin_name_into_path = |plugin_name: &str| self.prettier_dir.join("node_modules").join(plugin_name).join("dist").join("index.mjs"); let (parser, plugins) = match selected_parser_with_plugins { Some((parser, plugins)) => { @@ -267,7 +263,7 @@ impl Prettier { }; let prettier_options = if self.default { - let language_settings = language_settings(buffer_language, buffer_file, cx); + let language_settings = language_settings(buffer_language, buffer.file(), cx); let mut options = language_settings.prettier.clone(); if !options.contains_key("tabWidth") { options.insert( @@ -295,8 +291,7 @@ impl Prettier { options: FormatOptions { parser, plugins, - // TODO kb is not absolute now - path, + path: buffer_path, prettier_options, }, } diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 780a9c3106df1503dfef77a62cdae236955bf9e5..7e8d1b67cba491b1c3e671d2a48de3c637c012c6 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -150,15 +150,19 @@ async function handleMessage(message, prettier) { throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } + let resolvedConfig = {}; + if (params.options.filepath !== undefined) { + resolvedConfig = await prettier.prettier.resolveConfig(params.options.filepath) || {}; + } + const options = { ...(params.options.prettierOptions || prettier.config), + ...resolvedConfig, parser: params.options.parser, plugins: params.options.plugins, - path: params.options.path + path: params.options.filepath }; - // TODO kb always resolve prettier config for each file. - // need to understand if default prettier can be affected by other configs in the project - // (restart default prettiers on config changes too then) + process.stderr.write(`Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${params.options.filepath || ''}' with options: ${JSON.stringify(options)}\n`); const formattedText = await prettier.prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 90e7f2446ba56a04ebba2665e1c8556c8477c745..9786afa71b6518c64efeac468b8a06a7dc74f434 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4130,9 +4130,12 @@ impl Project { .await { Ok(prettier) => { + let buffer_path = buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) + }); format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer, &cx) + .format(buffer, buffer_path, &cx) .await .context("formatting via prettier")?, )); @@ -4168,9 +4171,12 @@ impl Project { .await { Ok(prettier) => { + let buffer_path = buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) + }); format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer, &cx) + .format(buffer, buffer_path, &cx) .await .context("formatting via prettier")?, )); @@ -8309,7 +8315,6 @@ impl Project { let task = cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); - // TODO kb can we have a cache for this instead? let prettier_dir = match cx .background() .spawn(Prettier::locate( From 2d5741aef8eb2f7c0986a2aa39bec73131350ddd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 25 Sep 2023 15:08:39 +0300 Subject: [PATCH 098/180] Better prettier format logging --- crates/prettier/src/prettier.rs | 1 + crates/project/src/project.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index c08098ba23fa9067cd4eece273d8a224f1d0b043..7871d78d21c215753cfdded1e43959e3ed3a97e9 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -285,6 +285,7 @@ impl Prettier { } else { None }; + log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); FormatParams { text: buffer.text(), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9786afa71b6518c64efeac468b8a06a7dc74f434..53797b5bb906ae28d436ed703e1dab42d8409426 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8489,6 +8489,7 @@ impl Project { .await .context("fetching latest npm versions")?; + log::info!("Fetching default prettier and plugins: {packages_to_versions:?}"); let borrowed_packages = packages_to_versions.iter().map(|(package, version)| { (package.as_str(), version.as_str()) }).collect::>(); From 6c1c7eaf75e666281af278e6365e71d59638b3a5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 25 Sep 2023 22:43:57 +0300 Subject: [PATCH 099/180] Better detect Svelte plugins --- crates/prettier/src/prettier.rs | 39 +++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 7871d78d21c215753cfdded1e43959e3ed3a97e9..eb25ec20fc2d292241db7d54a2821580e0577928 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -239,8 +239,24 @@ impl Prettier { log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); } - let plugin_name_into_path = |plugin_name: &str| self.prettier_dir.join("node_modules").join(plugin_name).join("dist").join("index.mjs"); - let (parser, plugins) = match selected_parser_with_plugins { + let prettier_node_modules = self.prettier_dir.join("node_modules"); + anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); + let plugin_name_into_path = |plugin_name: &str| { + let prettier_plugin_dir = prettier_node_modules.join(plugin_name); + for possible_plugin_path in [ + prettier_plugin_dir.join("dist").join("index.mjs"), + prettier_plugin_dir.join("index.mjs"), + prettier_plugin_dir.join("plugin.js"), + prettier_plugin_dir.join("index.js"), + prettier_plugin_dir, + ] { + if possible_plugin_path.is_file() { + return Some(possible_plugin_path); + } + } + None + }; + let (parser, located_plugins) = match selected_parser_with_plugins { Some((parser, plugins)) => { // Tailwind plugin requires being added last // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins @@ -253,9 +269,9 @@ impl Prettier { } else { true } - }).map(|plugin_name| plugin_name_into_path(plugin_name)).collect::>(); + }).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::>(); if add_tailwind_back { - plugins.push(plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME)); + plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME))); } (Some(parser.to_string()), plugins) }, @@ -285,9 +301,18 @@ impl Prettier { } else { None }; + + let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| { + match located_plugin_path { + Some(path) => Some(path), + None => { + log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}"); + None}, + } + }).collect(); log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); - FormatParams { + anyhow::Ok(FormatParams { text: buffer.text(), options: FormatOptions { parser, @@ -295,8 +320,8 @@ impl Prettier { path: buffer_path, prettier_options, }, - } - }); + }) + }).context("prettier params calculation")?; let response = self .server .request::(params) From faf1d38a6debe283ccd4245d03205c285c5e8676 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 26 Sep 2023 01:53:28 +0300 Subject: [PATCH 100/180] Draft local and remote prettier separation --- Cargo.lock | 1 + crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 58 +++- crates/prettier/src/prettier_server.js | 3 - crates/project/src/project.rs | 360 +++++++++++++++++-------- crates/rpc/proto/zed.proto | 29 +- crates/rpc/src/proto.rs | 12 +- 7 files changed, 328 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0175755f967cbc88f63ed8967b06b5360c6daa29..fd10a3c3b45f9fcb40d2101785128bbc04989281 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5523,6 +5523,7 @@ name = "prettier" version = "0.1.0" dependencies = [ "anyhow", + "client", "collections", "fs", "futures 0.3.28", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index cfac4c4757e2871321da86a4061c8077a379cc4f..586341e66c8d923cf99292dbef5662eab5047dc7 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" path = "src/prettier.rs" [dependencies] +client = { path = "../client" } collections = { path = "../collections"} language = { path = "../language" } gpui = { path = "../gpui" } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index eb25ec20fc2d292241db7d54a2821580e0577928..147262baed69b8eee7c927980d8ebf95318c4eb9 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; +use client::Client; use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; @@ -13,13 +14,24 @@ use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; -pub struct Prettier { +pub enum Prettier { + Local(Local), + Remote(Remote), +} + +pub struct Local { worktree_id: Option, default: bool, prettier_dir: PathBuf, server: Arc, } +pub struct Remote { + worktree_id: Option, + prettier_dir: PathBuf, + client: Arc, +} + #[derive(Debug)] pub struct LocateStart { pub worktree_root_path: Arc, @@ -48,6 +60,14 @@ impl Prettier { ".editorconfig", ]; + pub fn remote(worktree_id: Option, prettier_dir: PathBuf, client: Arc) -> Self { + Self::Remote(Remote { + worktree_id, + prettier_dir, + client, + }) + } + pub async fn locate( starting_path: Option, fs: Arc, @@ -188,12 +208,12 @@ impl Prettier { .spawn(server.initialize(None)) .await .context("prettier server initialization")?; - Ok(Self { + Ok(Self::Local(Local { worktree_id, server, default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), prettier_dir, - }) + })) } pub async fn format( @@ -239,7 +259,7 @@ impl Prettier { log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); } - let prettier_node_modules = self.prettier_dir.join("node_modules"); + let prettier_node_modules = self.prettier_dir().join("node_modules"); anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); let plugin_name_into_path = |plugin_name: &str| { let prettier_plugin_dir = prettier_node_modules.join(plugin_name); @@ -278,7 +298,7 @@ impl Prettier { None => (None, Vec::new()), }; - let prettier_options = if self.default { + let prettier_options = if self.is_default() { let language_settings = language_settings(buffer_language, buffer.file(), cx); let mut options = language_settings.prettier.clone(); if !options.contains_key("tabWidth") { @@ -323,7 +343,8 @@ impl Prettier { }) }).context("prettier params calculation")?; let response = self - .server + .server() + .expect("TODO kb split into local and remote") .request::(params) .await .context("prettier format request")?; @@ -332,26 +353,39 @@ impl Prettier { } pub async fn clear_cache(&self) -> anyhow::Result<()> { - self.server + self.server() + .expect("TODO kb split into local and remote") .request::(()) .await .context("prettier clear cache") } - pub fn server(&self) -> &Arc { - &self.server + pub fn server(&self) -> Option<&Arc> { + match self { + Prettier::Local(local) => Some(&local.server), + Prettier::Remote(_) => None, + } } pub fn is_default(&self) -> bool { - self.default + match self { + Prettier::Local(local) => local.default, + Prettier::Remote(_) => false, + } } pub fn prettier_dir(&self) -> &Path { - &self.prettier_dir + match self { + Prettier::Local(local) => &local.prettier_dir, + Prettier::Remote(remote) => &remote.prettier_dir, + } } pub fn worktree_id(&self) -> Option { - self.worktree_id + match self { + Prettier::Local(local) => local.worktree_id, + Prettier::Remote(remote) => remote.worktree_id, + } } } diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 7e8d1b67cba491b1c3e671d2a48de3c637c012c6..db3e26ef6dd781a1937199ed1c22ce09b8c02328 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -130,9 +130,6 @@ async function* readStdin() { } } -// TODO kb, more methods? -// shutdown -// error async function handleMessage(message, prettier) { const { method, id, params } = message; if (method === undefined) { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 53797b5bb906ae28d436ed703e1dab42d8409426..3e85e707289bc74c4b8befec1c28b03cae7dafdb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -613,6 +613,8 @@ impl Project { client.add_model_request_handler(Self::handle_open_buffer_by_path); client.add_model_request_handler(Self::handle_save_buffer); client.add_model_message_handler(Self::handle_update_diff_base); + client.add_model_request_handler(Self::handle_prettier_instance_for_buffer); + client.add_model_request_handler(Self::handle_invoke_prettier); } pub fn local( @@ -4124,10 +4126,8 @@ impl Project { if let Some(prettier_task) = this .update(&mut cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) - }) { - match prettier_task - .await - .await + }).await { + match prettier_task.await { Ok(prettier) => { let buffer_path = buffer.read_with(&cx, |buffer, cx| { @@ -4165,10 +4165,8 @@ impl Project { if let Some(prettier_task) = this .update(&mut cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) - }) { - match prettier_task - .await - .await + }).await { + match prettier_task.await { Ok(prettier) => { let buffer_path = buffer.read_with(&cx, |buffer, cx| { @@ -8288,143 +8286,269 @@ impl Project { } } + async fn handle_prettier_instance_for_buffer( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> anyhow::Result { + let prettier_instance_for_buffer_task = this.update(&mut cx, |this, cx| { + let buffer = this + .opened_buffers + .get(&envelope.payload.buffer_id) + .and_then(|buffer| buffer.upgrade(cx)) + .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; + anyhow::Ok(this.prettier_instance_for_buffer(&buffer, cx)) + })?; + + let prettier_path = match prettier_instance_for_buffer_task.await { + Some(prettier) => match prettier.await { + Ok(prettier) => Some(prettier.prettier_dir().display().to_string()), + Err(e) => { + anyhow::bail!("Failed to create prettier instance for remote request: {e:#}") + } + }, + None => None, + }; + Ok(proto::PrettierInstanceForBufferResponse { prettier_path }) + } + + async fn handle_invoke_prettier( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> anyhow::Result { + let prettier = this + .read_with(&cx, |this, _| { + this.prettier_instances + .get(&( + envelope.payload.worktree_id.map(WorktreeId::from_proto), + PathBuf::from(&envelope.payload.buffer_path), + )) + .cloned() + }) + .with_context(|| { + format!( + "Missing prettier for worktree {:?} and path {}", + envelope.payload.worktree_id, envelope.payload.buffer_path, + ) + })? + .await; + let prettier = match prettier { + Ok(prettier) => prettier, + Err(e) => anyhow::bail!("Prettier instance failed to start: {e:#}"), + }; + + let buffer = this + .update(&mut cx, |this, cx| { + this.opened_buffers + .get(&envelope.payload.buffer_id) + .and_then(|buffer| buffer.upgrade(cx)) + }) + .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; + + let buffer_path = buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) + }); + + let diff = prettier + .format(&buffer, buffer_path, &cx) + .await + .context("handle buffer formatting")?; + todo!("TODO kb serialize diff") + } + fn prettier_instance_for_buffer( &mut self, buffer: &ModelHandle, cx: &mut ModelContext, - ) -> Option, Arc>>>>> { + ) -> Task, Arc>>>>> { let buffer = buffer.read(cx); let buffer_file = buffer.file(); - let buffer_language = buffer.language()?; + let Some(buffer_language) = buffer.language() else { + return Task::ready(None); + }; if !buffer_language .lsp_adapters() .iter() .flat_map(|adapter| adapter.enabled_formatters()) .any(|formatter| matches!(formatter, BundledFormatter::Prettier { .. })) { - return None; + return Task::ready(None); } - let node = Arc::clone(self.node.as_ref()?); let buffer_file = File::from_dyn(buffer_file); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file .as_ref() - .map(|file| file.worktree.read(cx).abs_path()); + .and_then(|file| Some(file.worktree.read(cx).abs_path())); let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); + if self.is_local() || worktree_id.is_none() || worktree_path.is_none() { + let Some(node) = self.node.as_ref().map(Arc::clone) else { + return Task::ready(None); + }; + let task = cx.spawn(|this, mut cx| async move { + let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); + let prettier_dir = match cx + .background() + .spawn(Prettier::locate( + worktree_path.zip(buffer_path).map( + |(worktree_root_path, starting_path)| LocateStart { + worktree_root_path, + starting_path, + }, + ), + fs, + )) + .await + { + Ok(path) => path, + Err(e) => { + return Some( + Task::ready(Err(Arc::new(e.context( + "determining prettier path for worktree {worktree_path:?}", + )))) + .shared(), + ); + } + }; - let task = cx.spawn(|this, mut cx| async move { - let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); - let prettier_dir = match cx - .background() - .spawn(Prettier::locate( - worktree_path - .zip(buffer_path) - .map(|(worktree_root_path, starting_path)| LocateStart { - worktree_root_path, - starting_path, - }), - fs, - )) - .await - { - Ok(path) => path, - Err(e) => { - return Task::Ready(Some(Result::Err(Arc::new( - e.context("determining prettier path for worktree {worktree_path:?}"), - )))) - .shared(); + if let Some(existing_prettier) = this.update(&mut cx, |project, _| { + project + .prettier_instances + .get(&(worktree_id, prettier_dir.clone())) + .cloned() + }) { + return Some(existing_prettier); } - }; - if let Some(existing_prettier) = this.update(&mut cx, |project, _| { - project - .prettier_instances - .get(&(worktree_id, prettier_dir.clone())) - .cloned() - }) { - return existing_prettier; - } + log::info!("Found prettier at {prettier_dir:?}, starting."); + let task_prettier_dir = prettier_dir.clone(); + let weak_project = this.downgrade(); + let new_server_id = + this.update(&mut cx, |this, _| this.languages.next_language_server_id()); + let new_prettier_task = cx + .spawn(|mut cx| async move { + let prettier = Prettier::start( + worktree_id.map(|id| id.to_usize()), + new_server_id, + task_prettier_dir, + node, + cx.clone(), + ) + .await + .context("prettier start") + .map_err(Arc::new)?; + log::info!("Had started prettier in {:?}", prettier.prettier_dir()); - log::info!("Found prettier at {prettier_dir:?}, starting."); - let task_prettier_dir = prettier_dir.clone(); - let weak_project = this.downgrade(); - let new_server_id = - this.update(&mut cx, |this, _| this.languages.next_language_server_id()); - let new_prettier_task = cx - .spawn(|mut cx| async move { - let prettier = Prettier::start( - worktree_id.map(|id| id.to_usize()), - new_server_id, - task_prettier_dir, - node, - cx.clone(), - ) - .await - .context("prettier start") - .map_err(Arc::new)?; - log::info!("Had started prettier in {:?}", prettier.prettier_dir()); - - if let Some(project) = weak_project.upgrade(&mut cx) { - project.update(&mut cx, |project, cx| { - let name = if prettier.is_default() { - LanguageServerName(Arc::from("prettier (default)")) - } else { - let prettier_dir = prettier.prettier_dir(); - let worktree_path = prettier - .worktree_id() - .map(WorktreeId::from_usize) - .and_then(|id| project.worktree_for_id(id, cx)) - .map(|worktree| worktree.read(cx).abs_path()); - match worktree_path { - Some(worktree_path) => { - if worktree_path.as_ref() == prettier_dir { - LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier_dir - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or_default() - ))) - } else { - let dir_to_display = match prettier_dir - .strip_prefix(&worktree_path) - .ok() - { - Some(relative_path) => relative_path, - None => prettier_dir, - }; - LanguageServerName(Arc::from(format!( - "prettier ({})", - dir_to_display.display(), - ))) + if let Some((project, prettier_server)) = + weak_project.upgrade(&mut cx).zip(prettier.server()) + { + project.update(&mut cx, |project, cx| { + let name = if prettier.is_default() { + LanguageServerName(Arc::from("prettier (default)")) + } else { + let prettier_dir = prettier.prettier_dir(); + let worktree_path = prettier + .worktree_id() + .map(WorktreeId::from_usize) + .and_then(|id| project.worktree_for_id(id, cx)) + .map(|worktree| worktree.read(cx).abs_path()); + match worktree_path { + Some(worktree_path) => { + if worktree_path.as_ref() == prettier_dir { + LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default() + ))) + } else { + let dir_to_display = match prettier_dir + .strip_prefix(&worktree_path) + .ok() + { + Some(relative_path) => relative_path, + None => prettier_dir, + }; + LanguageServerName(Arc::from(format!( + "prettier ({})", + dir_to_display.display(), + ))) + } } + None => LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir.display(), + ))), } - None => LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier_dir.display(), - ))), - } - }; - project - .supplementary_language_servers - .insert(new_server_id, (name, Arc::clone(prettier.server()))); - // TODO kb could there be a race with multiple default prettier instances added? - // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) - cx.emit(Event::LanguageServerAdded(new_server_id)); - }); + }; + + project + .supplementary_language_servers + .insert(new_server_id, (name, Arc::clone(prettier_server))); + // TODO kb could there be a race with multiple default prettier instances added? + // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) + cx.emit(Event::LanguageServerAdded(new_server_id)); + }); + } + Ok(Arc::new(prettier)).map_err(Arc::new) + }) + .shared(); + this.update(&mut cx, |project, _| { + project + .prettier_instances + .insert((worktree_id, prettier_dir), new_prettier_task.clone()); + }); + Some(new_prettier_task) + }); + task + } else if let Some(project_id) = self.remote_id() { + let client = self.client.clone(); + let request = proto::PrettierInstanceForBuffer { + project_id, + buffer_id: buffer.remote_id(), + }; + let task = cx.spawn(|this, mut cx| async move { + match client.request(request).await { + Ok(response) => { + response + .prettier_path + .map(PathBuf::from) + .map(|prettier_path| { + let prettier_task = Task::ready( + Ok(Arc::new(Prettier::remote( + worktree_id.map(|id| id.to_usize()), + prettier_path.clone(), + client, + ))) + .map_err(Arc::new), + ) + .shared(); + this.update(&mut cx, |project, _| { + project.prettier_instances.insert( + (worktree_id, prettier_path), + prettier_task.clone(), + ); + }); + prettier_task + }) } - anyhow::Ok(Arc::new(prettier)).map_err(Arc::new) - }) - .shared(); - this.update(&mut cx, |project, _| { - project - .prettier_instances - .insert((worktree_id, prettier_dir), new_prettier_task.clone()); + Err(e) => { + log::error!("Prettier init remote request failed: {e:#}"); + None + } + } }); - new_prettier_task - }); - Some(task) + + task + } else { + Task::ready(Some( + Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(), + )) + } } fn install_default_formatters( diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 3501e70e6ac4191c5fe3141620e18cd8f7b8c32d..64fd8c620db76cfd089da899fb51ed38f44a648d 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -170,7 +170,12 @@ message Envelope { LinkChannel link_channel = 140; UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; // current max: 144 + MoveChannel move_channel = 142; + + PrettierInstanceForBuffer prettier_instance_for_buffer = 145; + PrettierInstanceForBufferResponse prettier_instance_for_buffer_response = 146; + InvokePrettierForBuffer invoke_prettier_for_buffer = 147; + InvokePrettierForBufferResponse invoke_prettier_for_buffer_response = 148; // Current max: 148 } } @@ -1557,3 +1562,25 @@ message UpdateDiffBase { uint64 buffer_id = 2; optional string diff_base = 3; } + +message PrettierInstanceForBuffer { + uint64 project_id = 1; + uint64 buffer_id = 2; +} + +message PrettierInstanceForBufferResponse { + optional string prettier_path = 1; +} + +message InvokePrettierForBuffer { + uint64 project_id = 1; + string buffer_path = 2; + uint64 buffer_id = 3; + optional uint64 worktree_id = 4; + string method = 5; + optional string command_parameters = 6; +} + +message InvokePrettierForBufferResponse { + optional string diff = 1; +} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index f0d7937f6f9592b2574bff7b93d3097ceba24d94..b51f1d8ae99c369188db2bd89748036c6f8f0f4f 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -273,6 +273,10 @@ messages!( (UpdateChannelBufferCollaborators, Foreground), (AckBufferOperation, Background), (AckChannelMessage, Background), + (PrettierInstanceForBuffer, Background), + (InvokePrettierForBuffer, Background), + (PrettierInstanceForBufferResponse, Background), + (InvokePrettierForBufferResponse, Background), ); request_messages!( @@ -349,7 +353,9 @@ request_messages!( (UpdateProject, Ack), (UpdateWorktree, Ack), (JoinChannelBuffer, JoinChannelBufferResponse), - (LeaveChannelBuffer, Ack) + (LeaveChannelBuffer, Ack), + (PrettierInstanceForBuffer, PrettierInstanceForBufferResponse), + (InvokePrettierForBuffer, InvokePrettierForBufferResponse), ); entity_messages!( @@ -400,7 +406,9 @@ entity_messages!( UpdateProjectCollaborator, UpdateWorktree, UpdateWorktreeSettings, - UpdateDiffBase + UpdateDiffBase, + PrettierInstanceForBuffer, + InvokePrettierForBuffer, ); entity_messages!( From 2ec2036c2f739894cd80c2f26f7f6471a93906ea Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 26 Sep 2023 18:32:55 +0300 Subject: [PATCH 101/180] Invoke remote Prettier commands --- crates/prettier/src/prettier.rs | 285 ++++++++++++++++++-------------- crates/project/src/project.rs | 9 +- crates/rpc/proto/zed.proto | 9 +- 3 files changed, 170 insertions(+), 133 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 147262baed69b8eee7c927980d8ebf95318c4eb9..2df96224b6974da348ffd1e108d4045e293f27df 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -9,6 +9,7 @@ use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; use language::{Buffer, BundledFormatter, Diff}; +use lsp::request::Request; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; @@ -216,148 +217,176 @@ impl Prettier { })) } + pub async fn invoke( + &self, + buffer: &ModelHandle, + buffer_path: Option, + method: &str, + cx: &AsyncAppContext, + ) -> anyhow::Result> { + match method { + Format::METHOD => self + .format(buffer, buffer_path, cx) + .await + .context("invoke method") + .map(Some), + ClearCache::METHOD => { + self.clear_cache().await.context("invoke method")?; + Ok(None) + } + unknown => anyhow::bail!("Unknown method {unknown}"), + } + } + pub async fn format( &self, buffer: &ModelHandle, buffer_path: Option, cx: &AsyncAppContext, ) -> anyhow::Result { - let params = buffer.read_with(cx, |buffer, cx| { - let buffer_language = buffer.language(); - let parsers_with_plugins = buffer_language - .into_iter() - .flat_map(|language| { - language - .lsp_adapters() - .iter() - .flat_map(|adapter| adapter.enabled_formatters()) - .filter_map(|formatter| match formatter { - BundledFormatter::Prettier { - parser_name, - plugin_names, - } => Some((parser_name, plugin_names)), + match self { + Self::Local(local) => { + let params = buffer.read_with(cx, |buffer, cx| { + let buffer_language = buffer.language(); + let parsers_with_plugins = buffer_language + .into_iter() + .flat_map(|language| { + language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .filter_map(|formatter| match formatter { + BundledFormatter::Prettier { + parser_name, + plugin_names, + } => Some((parser_name, plugin_names)), + }) }) - }) - .fold( - HashMap::default(), - |mut parsers_with_plugins, (parser_name, plugins)| { - match parser_name { - Some(parser_name) => parsers_with_plugins - .entry(parser_name) - .or_insert_with(HashSet::default) - .extend(plugins), - None => parsers_with_plugins.values_mut().for_each(|existing_plugins| { - existing_plugins.extend(plugins.iter()); - }), + .fold( + HashMap::default(), + |mut parsers_with_plugins, (parser_name, plugins)| { + match parser_name { + Some(parser_name) => parsers_with_plugins + .entry(parser_name) + .or_insert_with(HashSet::default) + .extend(plugins), + None => parsers_with_plugins.values_mut().for_each(|existing_plugins| { + existing_plugins.extend(plugins.iter()); + }), + } + parsers_with_plugins + }, + ); + + let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len()); + if parsers_with_plugins.len() > 1 { + log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); + } + + let prettier_node_modules = self.prettier_dir().join("node_modules"); + anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); + let plugin_name_into_path = |plugin_name: &str| { + let prettier_plugin_dir = prettier_node_modules.join(plugin_name); + for possible_plugin_path in [ + prettier_plugin_dir.join("dist").join("index.mjs"), + prettier_plugin_dir.join("index.mjs"), + prettier_plugin_dir.join("plugin.js"), + prettier_plugin_dir.join("index.js"), + prettier_plugin_dir, + ] { + if possible_plugin_path.is_file() { + return Some(possible_plugin_path); + } } - parsers_with_plugins - }, - ); + None + }; + let (parser, located_plugins) = match selected_parser_with_plugins { + Some((parser, plugins)) => { + // Tailwind plugin requires being added last + // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins + let mut add_tailwind_back = false; + + let mut plugins = plugins.into_iter().filter(|&&plugin_name| { + if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME { + add_tailwind_back = true; + false + } else { + true + } + }).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::>(); + if add_tailwind_back { + plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME))); + } + (Some(parser.to_string()), plugins) + }, + None => (None, Vec::new()), + }; - let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len()); - if parsers_with_plugins.len() > 1 { - log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); - } + let prettier_options = if self.is_default() { + let language_settings = language_settings(buffer_language, buffer.file(), cx); + let mut options = language_settings.prettier.clone(); + if !options.contains_key("tabWidth") { + options.insert( + "tabWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.tab_size.get(), + )), + ); + } + if !options.contains_key("printWidth") { + options.insert( + "printWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.preferred_line_length, + )), + ); + } + Some(options) + } else { + None + }; - let prettier_node_modules = self.prettier_dir().join("node_modules"); - anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); - let plugin_name_into_path = |plugin_name: &str| { - let prettier_plugin_dir = prettier_node_modules.join(plugin_name); - for possible_plugin_path in [ - prettier_plugin_dir.join("dist").join("index.mjs"), - prettier_plugin_dir.join("index.mjs"), - prettier_plugin_dir.join("plugin.js"), - prettier_plugin_dir.join("index.js"), - prettier_plugin_dir, - ] { - if possible_plugin_path.is_file() { - return Some(possible_plugin_path); - } - } - None - }; - let (parser, located_plugins) = match selected_parser_with_plugins { - Some((parser, plugins)) => { - // Tailwind plugin requires being added last - // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins - let mut add_tailwind_back = false; - - let mut plugins = plugins.into_iter().filter(|&&plugin_name| { - if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME { - add_tailwind_back = true; - false - } else { - true + let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| { + match located_plugin_path { + Some(path) => Some(path), + None => { + log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}"); + None}, } - }).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::>(); - if add_tailwind_back { - plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME))); - } - (Some(parser.to_string()), plugins) - }, - None => (None, Vec::new()), - }; - - let prettier_options = if self.is_default() { - let language_settings = language_settings(buffer_language, buffer.file(), cx); - let mut options = language_settings.prettier.clone(); - if !options.contains_key("tabWidth") { - options.insert( - "tabWidth".to_string(), - serde_json::Value::Number(serde_json::Number::from( - language_settings.tab_size.get(), - )), - ); - } - if !options.contains_key("printWidth") { - options.insert( - "printWidth".to_string(), - serde_json::Value::Number(serde_json::Number::from( - language_settings.preferred_line_length, - )), - ); - } - Some(options) - } else { - None - }; - - let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| { - match located_plugin_path { - Some(path) => Some(path), - None => { - log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}"); - None}, - } - }).collect(); - log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); - - anyhow::Ok(FormatParams { - text: buffer.text(), - options: FormatOptions { - parser, - plugins, - path: buffer_path, - prettier_options, - }, - }) - }).context("prettier params calculation")?; - let response = self - .server() - .expect("TODO kb split into local and remote") - .request::(params) - .await - .context("prettier format request")?; - let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); - Ok(diff_task.await) + }).collect(); + log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); + + anyhow::Ok(FormatParams { + text: buffer.text(), + options: FormatOptions { + parser, + plugins, + path: buffer_path, + prettier_options, + }, + }) + }).context("prettier params calculation")?; + let response = local + .server + .request::(params) + .await + .context("prettier format request")?; + let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); + Ok(diff_task.await) + } + Self::Remote(remote) => todo!("TODO kb"), + } } pub async fn clear_cache(&self) -> anyhow::Result<()> { - self.server() - .expect("TODO kb split into local and remote") - .request::(()) - .await - .context("prettier clear cache") + match self { + Self::Local(local) => local + .server + .request::(()) + .await + .context("prettier clear cache"), + Self::Remote(remote) => todo!("TODO kb"), + } } pub fn server(&self) -> Option<&Arc> { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3e85e707289bc74c4b8befec1c28b03cae7dafdb..f3a92ad00f3a9a2f8c8b37ad6ce454cfecdabb9a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8353,10 +8353,13 @@ impl Project { }); let diff = prettier - .format(&buffer, buffer_path, &cx) + .invoke(&buffer, buffer_path, &envelope.payload.method, &cx) .await - .context("handle buffer formatting")?; - todo!("TODO kb serialize diff") + .with_context(|| format!("prettier invoke method {}", &envelope.payload.method))?; + + Ok(proto::InvokePrettierForBufferResponse { + diff: todo!("TODO kb serialize diff"), + }) } fn prettier_instance_for_buffer( diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 64fd8c620db76cfd089da899fb51ed38f44a648d..0622fcef909e1798d68daeda2c331da740afde69 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1578,9 +1578,14 @@ message InvokePrettierForBuffer { uint64 buffer_id = 3; optional uint64 worktree_id = 4; string method = 5; - optional string command_parameters = 6; } message InvokePrettierForBufferResponse { - optional string diff = 1; + optional Diff diff = 1; +} + +message Diff { + VectorClockEntry version = 1; + string line_ending = 2; + string edits = 3; } From b5705e079fd90276916b7e553ebecfa9ebe81158 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 27 Sep 2023 16:38:42 +0200 Subject: [PATCH 102/180] Draft remote prettier formatting --- crates/language/src/buffer.rs | 4 +-- crates/language/src/proto.rs | 41 ++++++++++++++++++++++- crates/prettier/src/prettier.rs | 58 ++++++++++++++++++++++++++++----- crates/project/src/project.rs | 35 +++++++++++--------- crates/rpc/proto/zed.proto | 15 ++++++--- 5 files changed, 121 insertions(+), 32 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 207c41e7cdf21a8ec4d61232e071296184b52a5f..eccfb0c05902c283b2f2acc06391e0cda8ca1724 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -317,8 +317,8 @@ pub struct Chunk<'a> { pub struct Diff { pub(crate) base_version: clock::Global, - line_ending: LineEnding, - edits: Vec<(Range, Arc)>, + pub(crate) line_ending: LineEnding, + pub(crate) edits: Vec<(Range, Arc)>, } #[derive(Clone, Copy)] diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index c4abe39d4782aafbe90594e3a0bc5de70787fa03..5da1d5713fd2c63719f915d9930e7df7bd3cfa53 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -1,6 +1,6 @@ use crate::{ diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, - Language, + Diff, Language, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -587,3 +587,42 @@ pub fn serialize_version(version: &clock::Global) -> Vec proto::Diff { + proto::Diff { + version: serialize_version(&diff.base_version), + line_ending: serialize_line_ending(diff.line_ending) as i32, + edits: diff + .edits + .into_iter() + .map(|(range, edit)| proto::DiffEdit { + range: Some(proto::Range { + start: range.start as u64, + end: range.end as u64, + }), + edit: edit.to_string(), + }) + .collect(), + } +} + +pub fn deserialize_diff(diff: proto::Diff) -> Diff { + Diff { + base_version: deserialize_version(&diff.version), + line_ending: deserialize_line_ending( + rpc::proto::LineEnding::from_i32(diff.line_ending) + .unwrap_or_else(|| panic!("invalid line ending {}", diff.line_ending)), + ), + edits: diff + .edits + .into_iter() + .map(|edit| { + let range = edit.range.expect("incorrect edit without a range"); + ( + range.start as usize..range.end as usize, + Arc::from(edit.edit.as_str()), + ) + }) + .collect(), + } +} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 2df96224b6974da348ffd1e108d4045e293f27df..5647123c755d297ee67247be32016ea68b1a85a0 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,11 +3,12 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; -use client::Client; +use client::{proto, Client}; use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; +use language::proto::deserialize_diff; use language::{Buffer, BundledFormatter, Diff}; use lsp::request::Request; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; @@ -28,6 +29,7 @@ pub struct Local { } pub struct Remote { + project_id: u64, worktree_id: Option, prettier_dir: PathBuf, client: Arc, @@ -61,8 +63,14 @@ impl Prettier { ".editorconfig", ]; - pub fn remote(worktree_id: Option, prettier_dir: PathBuf, client: Arc) -> Self { + pub fn remote( + project_id: u64, + worktree_id: Option, + prettier_dir: PathBuf, + client: Arc, + ) -> Self { Self::Remote(Remote { + project_id, worktree_id, prettier_dir, client, @@ -80,7 +88,7 @@ impl Prettier { .components() .into_iter() .take_while(|path_component| { - path_component.as_os_str().to_str() != Some("node_modules") + path_component.as_os_str().to_string_lossy() != "node_modules" }) .collect::(); @@ -137,7 +145,7 @@ impl Prettier { for path_component in file_to_format.components().into_iter() { current_path = current_path.join(path_component); paths_to_check.push_front(current_path.clone()); - if path_component.as_os_str().to_str() == Some("node_modules") { + if path_component.as_os_str().to_string_lossy() == "node_modules" { break; } } @@ -219,14 +227,18 @@ impl Prettier { pub async fn invoke( &self, - buffer: &ModelHandle, + buffer: Option<&ModelHandle>, buffer_path: Option, method: &str, cx: &AsyncAppContext, ) -> anyhow::Result> { match method { Format::METHOD => self - .format(buffer, buffer_path, cx) + .format( + buffer.expect("missing buffer for format invocation"), + buffer_path, + cx, + ) .await .context("invoke method") .map(Some), @@ -374,7 +386,21 @@ impl Prettier { let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); Ok(diff_task.await) } - Self::Remote(remote) => todo!("TODO kb"), + Self::Remote(remote) => buffer + .read_with(cx, |buffer, _| { + remote.client.request(proto::InvokePrettierForBuffer { + buffer_id: Some(buffer.remote_id()), + worktree_id: self.worktree_id().map(|id| id as u64), + method: Format::METHOD.to_string(), + project_id: remote.project_id, + prettier_path: remote.prettier_dir.to_string_lossy().to_string(), + }) + }) + .await + .context("prettier diff invoke")? + .diff + .map(deserialize_diff) + .context("missing diff after prettier diff invocation"), } } @@ -385,7 +411,23 @@ impl Prettier { .request::(()) .await .context("prettier clear cache"), - Self::Remote(remote) => todo!("TODO kb"), + Self::Remote(remote) => remote + .client + .request(proto::InvokePrettierForBuffer { + buffer_id: None, + worktree_id: self.worktree_id().map(|id| id as u64), + method: ClearCache::METHOD.to_string(), + project_id: remote.project_id, + prettier_path: remote.prettier_dir.to_string_lossy().to_string(), + }) + .await + .map(|response| { + debug_assert!( + response.diff.is_none(), + "Cleare cache invocation returned diff data" + ) + }) + .context("prettier invoke clear cache"), } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f3a92ad00f3a9a2f8c8b37ad6ce454cfecdabb9a..9a1d78f083d7277e552080c47e5f3c4d7be1f6ca 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -37,7 +37,7 @@ use language::{ point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, - serialize_anchor, serialize_version, split_operations, + serialize_anchor, serialize_diff, serialize_version, split_operations, }, range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, @@ -6382,7 +6382,7 @@ impl Project { .filter(|(path, _, _)| { !path .components() - .any(|component| component.as_os_str().to_str() == Some("node_modules")) + .any(|component| component.as_os_str().to_string_lossy() == "node_modules") }) .find(|(path, _, _)| prettier_config_files.contains(path.as_ref())); let current_worktree_id = worktree.read(cx).id(); @@ -8324,14 +8324,14 @@ impl Project { this.prettier_instances .get(&( envelope.payload.worktree_id.map(WorktreeId::from_proto), - PathBuf::from(&envelope.payload.buffer_path), + PathBuf::from(&envelope.payload.prettier_path), )) .cloned() }) .with_context(|| { format!( - "Missing prettier for worktree {:?} and path {}", - envelope.payload.worktree_id, envelope.payload.buffer_path, + "Missing prettier for worktree {:?} and path {:?}", + envelope.payload.worktree_id, envelope.payload.prettier_path, ) })? .await; @@ -8340,25 +8340,27 @@ impl Project { Err(e) => anyhow::bail!("Prettier instance failed to start: {e:#}"), }; - let buffer = this - .update(&mut cx, |this, cx| { - this.opened_buffers - .get(&envelope.payload.buffer_id) - .and_then(|buffer| buffer.upgrade(cx)) - }) - .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; + let buffer = this.update(&mut cx, |this, cx| { + envelope + .payload + .buffer_id + .and_then(|id| this.opened_buffers.get(&id)) + .and_then(|buffer| buffer.upgrade(cx)) + }); - let buffer_path = buffer.read_with(&cx, |buffer, cx| { - File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) + let buffer_path = buffer.as_ref().and_then(|buffer| { + buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) + }) }); let diff = prettier - .invoke(&buffer, buffer_path, &envelope.payload.method, &cx) + .invoke(buffer.as_ref(), buffer_path, &envelope.payload.method, &cx) .await .with_context(|| format!("prettier invoke method {}", &envelope.payload.method))?; Ok(proto::InvokePrettierForBufferResponse { - diff: todo!("TODO kb serialize diff"), + diff: diff.map(serialize_diff), }) } @@ -8523,6 +8525,7 @@ impl Project { .map(|prettier_path| { let prettier_task = Task::ready( Ok(Arc::new(Prettier::remote( + project_id, worktree_id.map(|id| id.to_usize()), prettier_path.clone(), client, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 0622fcef909e1798d68daeda2c331da740afde69..cbd334e334293ce56d089e7b770ed5d407c90e05 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1574,8 +1574,8 @@ message PrettierInstanceForBufferResponse { message InvokePrettierForBuffer { uint64 project_id = 1; - string buffer_path = 2; - uint64 buffer_id = 3; + optional uint64 buffer_id = 3; + string prettier_path = 2; optional uint64 worktree_id = 4; string method = 5; } @@ -1585,7 +1585,12 @@ message InvokePrettierForBufferResponse { } message Diff { - VectorClockEntry version = 1; - string line_ending = 2; - string edits = 3; + repeated VectorClockEntry version = 1; + LineEnding line_ending = 2; + repeated DiffEdit edits = 3; +} + +message DiffEdit { + Range range = 1; + string edit = 2; } From 9bf22c56cdb1adad44ba82b7fcd4b9b04c7451e2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 9 Oct 2023 15:44:48 +0300 Subject: [PATCH 103/180] Rebase fixes --- crates/collab/src/tests/test_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index ccd48a0a1b10f050bb47bdf765d26fda1b107904..7397489b345ba87bb140e3c2d02627c33029a112 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -569,9 +569,9 @@ impl TestClient { cx.update(|cx| { Project::local( self.client().clone(), + self.app_state.node_runtime.clone(), self.app_state.user_store.clone(), self.app_state.languages.clone(), - self.app_state.node_runtime.clone(), self.app_state.fs.clone(), cx, ) From 986a516bf1237b343eb883c860019a692fb9a042 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 10 Oct 2023 17:16:43 +0300 Subject: [PATCH 104/180] Small style fixes --- crates/language/src/language.rs | 6 ++++++ crates/prettier/src/prettier.rs | 1 - crates/project/src/project.rs | 1 + crates/rpc/proto/zed.proto | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c565d09c98f8f142b5a5f561cbdc2e7e61051714..73906250f38f5847d55a339112db5958c84a0ee7 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -346,6 +346,12 @@ pub trait LspAdapter: 'static + Send + Sync { #[derive(Clone, Debug, PartialEq, Eq)] pub enum BundledFormatter { Prettier { + // See https://prettier.io/docs/en/options.html#parser for a list of valid values. + // Usually, every language has a single parser (standard or plugin-provided), hence `Some("parser_name")` can be used. + // There can not be multiple parsers for a single language, in case of a conflict, we would attempt to select the one with most plugins. + // + // But exceptions like Tailwind CSS exist, which uses standard parsers for CSS/JS/HTML/etc. but require an extra plugin to be installed. + // For those cases, `None` will install the plugin but apply other, regular parser defined for the language, and this would not be a conflict. parser_name: Option<&'static str>, plugin_names: Vec<&'static str>, }, diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 5647123c755d297ee67247be32016ea68b1a85a0..07a7cbd73a357aff809c34f318ea9b6513a59f50 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -47,7 +47,6 @@ const PRETTIER_PACKAGE_NAME: &str = "prettier"; const TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME: &str = "prettier-plugin-tailwindcss"; impl Prettier { - // This was taken from the prettier-vscode extension. pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ ".prettierrc", ".prettierrc.json", diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9a1d78f083d7277e552080c47e5f3c4d7be1f6ca..9dc3ff875867915f8a827b38a2ca89b60a8cd349 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8496,6 +8496,7 @@ impl Project { .insert(new_server_id, (name, Arc::clone(prettier_server))); // TODO kb could there be a race with multiple default prettier instances added? // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) + // also, is there a way to speed up initial prettier startup? now it takes a 1s or so on the first formatting attempt. cx.emit(Event::LanguageServerAdded(new_server_id)); }); } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index cbd334e334293ce56d089e7b770ed5d407c90e05..aae207f41cc68e695ce08c2831e7ded0b93334b6 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1574,8 +1574,8 @@ message PrettierInstanceForBufferResponse { message InvokePrettierForBuffer { uint64 project_id = 1; - optional uint64 buffer_id = 3; string prettier_path = 2; + optional uint64 buffer_id = 3; optional uint64 worktree_id = 4; string method = 5; } From 4a88a9e253d3cafc6dd54fc9c873a9098c2ec730 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 11 Oct 2023 14:46:59 +0300 Subject: [PATCH 105/180] Initialize prettier right after the buffer gets it language --- crates/project/src/project.rs | 41 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9dc3ff875867915f8a827b38a2ca89b60a8cd349..ebc2aaaf604cbc89a14807cdcf7a3dc6778e061f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -911,7 +911,8 @@ impl Project { } for (worktree, language, settings) in language_formatters_to_check { - self.install_default_formatters(worktree, &language, &settings, cx); + self.install_default_formatters(worktree, &language, &settings, cx) + .detach_and_log_err(cx); } // Start all the newly-enabled language servers. @@ -2666,7 +2667,20 @@ impl Project { let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); let buffer_file = File::from_dyn(buffer_file.as_ref()); let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); - self.install_default_formatters(worktree, &new_language, &settings, cx); + + let task_buffer = buffer.clone(); + let prettier_installation_task = + self.install_default_formatters(worktree, &new_language, &settings, cx); + cx.spawn(|project, mut cx| async move { + prettier_installation_task.await?; + let _ = project + .update(&mut cx, |project, cx| { + project.prettier_instance_for_buffer(&task_buffer, cx) + }) + .await; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); if let Some(file) = buffer_file { let worktree = file.worktree.clone(); @@ -8393,7 +8407,7 @@ impl Project { let Some(node) = self.node.as_ref().map(Arc::clone) else { return Task::ready(None); }; - let task = cx.spawn(|this, mut cx| async move { + cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); let prettier_dir = match cx .background() @@ -8494,9 +8508,6 @@ impl Project { project .supplementary_language_servers .insert(new_server_id, (name, Arc::clone(prettier_server))); - // TODO kb could there be a race with multiple default prettier instances added? - // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) - // also, is there a way to speed up initial prettier startup? now it takes a 1s or so on the first formatting attempt. cx.emit(Event::LanguageServerAdded(new_server_id)); }); } @@ -8509,15 +8520,14 @@ impl Project { .insert((worktree_id, prettier_dir), new_prettier_task.clone()); }); Some(new_prettier_task) - }); - task + }) } else if let Some(project_id) = self.remote_id() { let client = self.client.clone(); let request = proto::PrettierInstanceForBuffer { project_id, buffer_id: buffer.remote_id(), }; - let task = cx.spawn(|this, mut cx| async move { + cx.spawn(|this, mut cx| async move { match client.request(request).await { Ok(response) => { response @@ -8548,9 +8558,7 @@ impl Project { None } } - }); - - task + }) } else { Task::ready(Some( Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(), @@ -8564,13 +8572,13 @@ impl Project { new_language: &Language, language_settings: &LanguageSettings, cx: &mut ModelContext, - ) { + ) -> Task> { match &language_settings.formatter { Formatter::Prettier { .. } | Formatter::Auto => {} - Formatter::LanguageServer | Formatter::External { .. } => return, + Formatter::LanguageServer | Formatter::External { .. } => return Task::ready(Ok(())), }; let Some(node) = self.node.as_ref().cloned() else { - return; + return Task::ready(Ok(())); }; let mut prettier_plugins = None; @@ -8586,7 +8594,7 @@ impl Project { } } let Some(prettier_plugins) = prettier_plugins else { - return; + return Task::ready(Ok(())); }; let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); @@ -8634,7 +8642,6 @@ impl Project { anyhow::Ok(()) }) - .detach_and_log_err(cx); } } From 7c867b6e5456eac337d7008fc7ed89ba3b9d669a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Oct 2023 09:23:06 -0600 Subject: [PATCH 106/180] New entitlements: * Universal links * Shared keychain group (to make development easier) --- crates/zed/contents/embedded.provisionprofile | Bin 0 -> 12512 bytes crates/zed/resources/zed.entitlements | 12 ++++-------- script/bundle | 2 ++ 3 files changed, 6 insertions(+), 8 deletions(-) create mode 100644 crates/zed/contents/embedded.provisionprofile diff --git a/crates/zed/contents/embedded.provisionprofile b/crates/zed/contents/embedded.provisionprofile new file mode 100644 index 0000000000000000000000000000000000000000..8979e1fb9fb72e9f5adbbc05d3498e6218016581 GIT binary patch literal 12512 zcmdUV36v9M);8TVTeB*-iv&awnkJP!Q9x@;Qb{UFRVpirR;ntMr7C+;N!*}SRKRg# zbW}tXonhP+k#S>0{S#y`DfN-NmCVyrBp7vV1{lUG(#g~xg4L$iVJ208GE;N#+eH{=TFCcC=>PL zn!J=Ml{Nk#;vrDYjBc$K&gUgTtHl^h@>s`Zb}7K`lQNl40M3dKC$7&Uk_ZGSU$NoY}M4vMkmfkpD5uuu=_4c$7ZTc=}m zrqlJ-(~bJrd~Nep@0$D)TLwFjf3$%9ZTbR{pP!m2MZnbwB%5VLf$twA!Ad1T+joLk z>qvwm3T^;w8XSR8>Tlb(l`-?dy4ZyzLDUGtDG06a1TPkiooDy5-mAkT@Z&AB||$x`dGMDi*p&99aSsV+n(vS#iH1U?;!JX0GL0)f23N>5(I7F> z6mHf<)w)`5oiUt7kz503Nknv@q8xDLWX77RWx!rZG^3-vOeh@A)O=wHuhAHSBBD2h zz$EE|Xo;6YrMLl+xD?`|QKY#KHtbFE4yhVX=ftoa%7F8z#GIi9?{wv35wfI~qjjU` z#)piZCuvf_sopbz^WGp6)PKnm0q@;#4%|D1c@wgo&5A@e>gNSc%t2AwcX&5|_n5|N#U^`h5xLhO|62oY`m`kT*f88Dmm0k6W&DT}tyiAqK z+3h0*$({#s^6pcA%>6Q9(PR;0S_IK)zuG5yc`R;&z1M6gV2quudA{uw2!n zx1~7)T#2^yr4rTK%Dr_8S=VJ^kyt*4!FsSZ#Hq?34T%W&4R(scbW=XSdNYM&Cgrf{ zy%~hU6)|B{M2;M^4gw_!haMvwjUdnoHf#s;1VvTvf&S*XI+7l&+d$V-LI6p|vl)?d z*z3GABSs_M1|QY|Sw@mkJ-`nqos7#~iE!9t!b#^qAAldB3gNIToTH{U%X+V<6k}u=3F6nictAU0qV^G{yYcfYVIKSDL#Zy|W)F5J~f z%N7Rrd)PV^farV`tz_(ufVFOrv=(pKRw_an*-ZFL4a!jQa?S`qcXx%a$yAri%7js~ z&74WgkZ9DUbFq?JFr||X9iE}m?rOmpx5k}hkX7<*ChbO1KAuTvOIbHk*5id7q%Tnr z0l6qMWq`~2q)i}%0H?L_G^dprr>_<@rHdlj08FK6rU8BEBO$#^0>kNUTqSY>-woJS zA|(wWBTdWhrQyNWn?rL1;q@{oiZM6}EL;tskrxpqF_{sO7zMBeAPB5#z5-BYz+yOI z7SvU7TogTJGD@Ik1_r{yhk;f%R7(x|AwV)n6yQ%V=*`8#-Xa(uNF7X~5?}z~P%sYF z^1K{G;iOyumRxOH0_;GJ8(d`)4ubABCXB%h2Af=*E}4$&X^ByhB+N@O7+5U~jsfdO zak5R%M!bMkm>N{LR#MpqxEF~7Xs88Rg26S6lS3^loT0)0U<6|T17P49$>2INEkdM} zQ=FpW3pql`cu>ly+N?5 z7XwT}MNcMJ_hD2$oQ6dVP6lIPdzq2#VrLR3A@vy8w}7elc^K+nw-MDx8*l}KC6y7mXxx;Hhl3;$1nU4i6EG!k z=Y)#Z4X{sjz4>?wQ7w@aci0plD;02w77PjDeRV+2hOk4xq!dD=L>ko^WSTLvtW7FJ zTwSCYO+T#`^s)zzkVmKJvAwlsz1UH}v1OxUHN0E9O?gHi= z$Q?9*+D)t3&tl8$;Ik7Ic8Ht=OkfYuiU3%jN6JevR0q}{RosdqH?6MRya&l64`z<0 zBxmqeqk0L*Dd-obtMRZsAJ38`9LOmorKJBwUr3cRPk4y@9?n`7H>`wNeaIBmCzEX0 zSdNChz#4lSo-hyOCZuo;*aviG2;=mm3L^NksEmbb0Nv;0e(%7;MXV{W#~v^R6;!o; zz&mK9A>av+O00p$fxz~827x}79+YiW9tB(_0GA1zYy8JN0ESY$7^#qqSo1NkzUcup zGDGXqNS35ailfE8F${6_c_e|j-Ub101|d*Lq#I<-m4-7d51>RkRe5&Qq<`z}3^02) z(6PR;U2&PLkW@)QTk|?_#T5!R5Iw+52T})?nl#z~t~!uF1pyQ?1?veIai#N~h7RTm zOgU2rE*nh($Us9@E}UkvPR@?RV^}3DBZ0gj<}4?ebegHuYIr%K)j2KEdQ>vF%uzbd zG!Q=?%E8)DGK#q)I;>vvA`r*$d?6y4_$nD{l-(wsg}|!ON{ER?@^Z!};ck-kqQFWh zy-d-EAW_`zsVIhmJxUr=Y@R5@3`JlT&04lz4-`qb#cXjEn-$7Vsum$({RxJGJ4iSO z7-ehUz-$0i6=Bj5L`e$8%$yUqOGY-rP*J^LsM;Kg55{AjinpsyCjl)7Fd*dW$-G?k zDWX4^^@o{SHeh7ST?L{B#&GE5R1$ORU_+3x>b+FM45ehBMCvW+IG>A@^;Dv6C*`ha zGR0#Vk6l;w1X+;EP$C84q9=ej$p$WyC}3m2lSUPV@6#7bAF2bK0vO)wO{NfpkI*?_ zEAxOY*YE~Rx5mx^eeaXQre|408?dYOF3P}C2zbp#VE=G{*+d;+IC#i9uz#Qq{KtqI zQ2rM4v*#2pJ+L0&+4rv}XpjI_hk))%06#<7K0Dad4UGCv=tWFrrGxdNPgY zL;{wQ*PIMvK0+=8+%j+OstT2=zEsK-8i->MqX)#CGLx7%#DgGVhhvZjQH>hBil^Uh z0FNeMYu15iM!DtD0gne@6!3na^K zUa}c|iKuc(r=wx6;gP-dI3Rb;v3k7?AtRW5L@wmQ#S~rAccp8&jL@Jkx19NDT?QC7 z#DhrUfLp5G!{Iv40Uf7M5($!psKrq<#qz9KNEkwmAgq&0MadJ4lB|cXpoSXjsiBdE zC|A72lHj$H9F-Lcf-jZO7kPzHq6{fo16gfXl&gb)ArZrLdO_#NKq67)izboCJ8L>7 zuLEQyRZA7Z+EUbI!1KV*U=%G`r~_MvA!(rJKnD6EVYnkv!Q?`%93~F4b;SQ-U4aF= z0@Z;$KqNx|-6o7)Mq<@EZv)l=9<098H9bAtSaYZ0TnjHqk_Z8mu@j=oNqJdna(gyx z$~HNsK9*NK?x0@EHhE^_|1KgIW?%*pIS@%m`}`Dv0A1+)O(KZmMPZmGt3i*`Ym^dj zB&T>1SvVT#5{mJlJpfz6=6s|Ofx2o1eM%0Yt_;QbN%j!5b5o;J5Jz#s1X|j zctAvC2seO@b}^Lo2dp}(f;PIsUEm1Wl9se;t0Zh}?ip)w1HdW>CvcwpgQHCtqWYvi z!VMIL0B+zEO5k}u!3DwrGvrRoVMo;?`1zWfbJu;^x-FGJjbW?99jX|ysuxZnIk78& zmN`DkQw70WLkuM;7;q306*PuJoWmL_1!{t>>gGIY2MY75y>|whzOB>vzgOQCf*6cb zYQ#QD#0WSX#1P=yAQUQ!LPl})%aa<(&tQOi4)yUFnCm}#1wv3NSi@>+6rBVZ849D00Bo0Fg#sKY2_u^?p$25FBUF!x+72xly88lmw*fQrd6H>q?L3gHx& zboq-oRAC6aU#Bzs1#!WG{)o?*fxk}hvzZ<9`zbUjaGCyePG9P$H!6gKG*?*?)O6TD z%BMHI2F6gwX$=H2b-g*e-VB;hoX3_*IW7gVMSM4(li5_Ze~A9<)SN-Nsh=57%>?$Q z-MUl#dAGg=_m4D?X`2UDFs~u-^I~9Pki7c2DM0Rx1DKxeW;sqMmAb`Zu96?L#Qw|{ z*kW%caL^<}R^A^>Q_tgHA4*wZ<^QvN3|_YyuN$1z#B2Ff@n^G!MxB~9gv>veHTw!~k%Bnin&#j!u@)dQBLTRps$a3=rkD}&SnUpLrIZvuO5Y$X%%YrBr7n)5L zXuh_u)GPPFAc6I-ws-RuL9e&5;B0vdWbQVa;^uB!TyN>tnc|j&)xcWJX72ELL#;%4 z|4@C?=myQ$ceC@g&209VuTra^@%`!fwy_{zA8)4bhX*@GPHqndbd#YYMuI};6t&&($>Uq?=v=O>0ao&)W5(+tonB|w*$h^bId;r= zgIa`Ie=(?{!yu&DbKDuNp5a_k;uWB`s&J{9b)l%C1(3A{6P7eTH^2HJg1XLe?G97_ zPR0%1NpFkhUZI}08G}!wZN#wQJ#7=gCk$G z=-XU-e%J1YUwLt7;)pY1pN_#xSNh-devlk-(XPsBcia8D_Pi>W$=!kNJC3*ZoWA|m zJ?G3m|DmOy&7L*((0LauJMz$%ao@;KJg-gLDX;mww5v9K{H^(&_V6|5ikEIFDV^)% z@kj5^FMFbL@|E}1|NilvZ+>^lsrt?{W?r{QbNlMov`db?<-Pf1KiT-*z9r#_$E-QC z>+)Go9B3cjHmq&k(l4N;`=BGi!A_mr*7ifkXlNw(*WNY^nx@vB+;J>)^pYuE+f$BJ z)iK|How;h>S-(EuATJ)h7{W%5?i$%PV#KJnwhk*~hKzk>sBPJ4yZ6M?XH8msXc1l> z|7GLCE3VzPn_Gcg`TDW%!y6)NMvlJR`QBL%oV?@ny>rj|Xz!gDpZMwOOWs;_t|x9= z?!ABVRTrYadw21@n~yzf|Hl*Oo-_ISug(|NKa+Is{^5}uZW(t-Z(g%z{4dY7cCNVL z^&_QCE5`ofj|=zZ+V0*);^)0{X(18+=r{p-W%&x_`0i6SOQ z57~N){}CkncaZVnwZ9!R?b>%va`^X-_i=LiJ1JfMX){NvD`{3{=?^WYav^JM%tz*jz3j*p#m34FO>`O=4= z$i7&Ier0mQ)l+v1r60t_!jr_?)!PhHgxi%>FLt*eqo=I_K(zWJRhy`)UK2z{K*NCs z4pP;h#E*XqFg$ZqGYRHJk8tw)rMccWu+>D{p!CZ^|wEtFJ4NZ_BjH zF1hT4n-(}M`+s}OS-+V3=0n#U`S#|oOSk;>b$;T@b)WI=I~E?S{gBx@YTt#nOI8Fl zZ&;su==V4KIx|0XeH;DFh%px&b5`hxlge*wT5<9jj&(b7Ge6k-+NS*b6Y)RKdg&{< z_W36pk2z-jyBEzcy>!ZsofF^6ym`mWlaDxX`@L6}mY(Z9c9#oSi+l4JaLFW<52!*92b8D)8}Qkf3* zj9396tki<9Nk@TOIrjNQmV4^g&AU0X`P{)rrgEbHAwHtgBt2w>^m?7n3RwY7(wiZx z0Wc*{w8S79e8D&u$a}}|ai@?hr||_ek)I^qnm|faiTM8m4z&N&b0VVwpct;0VA*}m z$Pas_JuvI_;fv_><5xaA=j#)D4)z3AOdho-{?0S6y=xdYectRB&^um7-kW;i=#hIv zr>)w&4!QS)?1F6*Z}?=EY4%-{FaPN218t|Bv@p5$Q^JLA`18yYrv7Qnz7u( z8h=jgcmzfl1~mtAg7V}ksr&ZlT~izNY<+>w?aMbMrd3=fVD$I`Oi;MjeH-R0RF>6aj3hm47D$t-bZ49LYD^7 z7?AELiYDNU79m}K^R{KD{7bsj>IAG~Pg|4ysDL;H6efXZ+z>$g{&0{R_x~rznfuNL z!1|n@hV=oc6xco&1%lKYoYpZL0(5dMpp(m{_1(h|GTD0zL+Rx5eeh++IT-Ebb@8j+ zW4@j;WBcObP#ZUD2{f_)xI4!F9GhJBg}-t9D;pPod&vL!#ubar^KLYp`I7DV)eh6l z)QYv^e{DUot8u*lqZ#hc*FXKVrmO42mm_C>H(~vQ=Y4q7q2ztDzIbiV{0CQ_vV6`B z3;*(kX6y%(zg}8aangy=-T|* zzp;ns-LT=g`?oE*)imSy!tc*FPTO^H{OTDqZwrmJ9liMJUyVL)>8EczcER%edv|@O zTP6M4xpzN#^B3#ahAXZ|Y^zUVu6psW-T(EMl`G|^EzF+U%lBM<$+E!C_uc%Zr(ycv zUi;X#^Y&#=8a?973oadZljbP}pYGG@Yo!Ar$HyIlA>7{_s&i{Q7>kx3>>l0xa1;x$P`yRO`e1QA6ub8*K2v6aX!T zu}9sz<%PCIU)@N5-?jLYyB(Je8>T(@t^Ue6u}{hqo?kGFY1PH_@7?5f{C2VDUQV0r`_->TQz9{`Q3OpJx93v=7WFP+piXN-G;uyw~>ohLreZ`wTWSDSCV@`Ka;&}QzT dW%r!D=*yLl-1Fw1WXXK + com.apple.developer.associated-domains + applinks:zed.dev com.apple.security.automation.apple-events com.apple.security.cs.allow-jit @@ -10,14 +12,8 @@ com.apple.security.device.camera - com.apple.security.personal-information.addressbook - - com.apple.security.personal-information.calendars - - com.apple.security.personal-information.location - - com.apple.security.personal-information.photos-library - + com.apple.security.keychain-access-groups + MQ55VZLNZQ.dev.zed.Shared diff --git a/script/bundle b/script/bundle index a1d0b305c8fc3b4732c3dac7a4722f3ae3838ae9..e4eb23b21788c76a60bbd0ca46533528482014a2 100755 --- a/script/bundle +++ b/script/bundle @@ -134,6 +134,8 @@ else cp -R target/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" fi +cp crates/zed/contents/embedded.provisionprofile "${app_path}/Contents/" + 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 2d6725a41a4f05e65c788ad200881a5dbce996bf Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Oct 2023 09:45:57 -0600 Subject: [PATCH 107/180] Make collaboration warning more useful --- crates/collab_ui/src/collab_titlebar_item.rs | 49 +++++++++++++------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index d85aca164a412b3651fbdd197ef4c3418ec8bb93..211ee863e89f6ce7bfe3aa44826c0a8f827a6f85 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -2,6 +2,7 @@ use crate::{ contact_notification::ContactNotification, face_pile::FacePile, toggle_deafen, toggle_mute, toggle_screen_sharing, LeaveCall, ToggleDeafen, ToggleMute, ToggleScreenSharing, }; +use auto_update::AutoUpdateStatus; use call::{ActiveCall, ParticipantLocation, Room}; use client::{proto::PeerId, Client, ContactEventKind, SignIn, SignOut, User, UserStore}; use clock::ReplicaId; @@ -1177,22 +1178,38 @@ impl CollabTitlebarItem { .with_style(theme.titlebar.offline_icon.container) .into_any(), ), - client::Status::UpgradeRequired => Some( - MouseEventHandler::new::(0, cx, |_, _| { - Label::new( - "Please update Zed to collaborate", - theme.titlebar.outdated_warning.text.clone(), - ) - .contained() - .with_style(theme.titlebar.outdated_warning.container) - .aligned() - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, _, cx| { - auto_update::check(&Default::default(), cx); - }) - .into_any(), - ), + client::Status::UpgradeRequired => { + let auto_updater = auto_update::AutoUpdater::get(cx); + let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) { + Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate", + Some(AutoUpdateStatus::Installing) + | Some(AutoUpdateStatus::Downloading) + | Some(AutoUpdateStatus::Checking) => "Updating...", + Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => { + "Please update Zed to Collaborate" + } + }; + + Some( + MouseEventHandler::new::(0, cx, |_, _| { + Label::new(label, theme.titlebar.outdated_warning.text.clone()) + .contained() + .with_style(theme.titlebar.outdated_warning.container) + .aligned() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, _, cx| { + if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) { + if auto_updater.read(cx).status() == AutoUpdateStatus::Updated { + workspace::restart(&Default::default(), cx); + return; + } + } + auto_update::check(&Default::default(), cx); + }) + .into_any(), + ) + } _ => None, } } From e50f4c0ee5f3e32f166e6cb16e02042413088074 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 11 Oct 2023 19:08:35 +0300 Subject: [PATCH 108/180] Add prettier tests infrastructure --- crates/editor/src/editor_tests.rs | 66 ++++++++++++++++++++++++- crates/language/src/language.rs | 6 +++ crates/node_runtime/src/node_runtime.rs | 25 +++++++--- crates/prettier/Cargo.toml | 3 ++ crates/prettier/src/prettier.rs | 54 ++++++++++++++++---- crates/project/Cargo.toml | 2 + 6 files changed, 139 insertions(+), 17 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index dc723c70127496cef84b6667799ee04e4e558326..ddae186679df45c9478e2ccfda70564e5d7b81f5 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -19,8 +19,8 @@ use gpui::{ use indoc::indoc; use language::{ language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent}, - BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry, - Override, Point, + BracketPairConfig, BundledFormatter, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, + LanguageRegistry, Override, Point, }; use parking_lot::Mutex; use project::project_settings::{LspSettings, ProjectSettings}; @@ -7815,6 +7815,68 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: }); } +#[gpui::test] +async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { + init_test(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::Prettier) + }); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + + let _ = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + enabled_formatters: vec![BundledFormatter::Prettier { + parser_name: Some("test_parser"), + plugin_names: vec!["test_plugin"], + }], + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.background()); + fs.insert_file("/file.rs", Default::default()).await; + + // TODO kb have to specify some test node runtime + let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) + .await + .unwrap(); + + let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); + editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); + + let format = editor.update(cx, |editor, cx| { + editor.perform_format(project.clone(), FormatTrigger::Manual, cx) + }); + format.await.unwrap(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + "one, two\nthree\n" + ); + + editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); + let format = editor.update(cx, |editor, cx| { + editor.perform_format(project, FormatTrigger::Manual, cx) + }); + cx.foreground().advance_clock(super::FORMAT_TIMEOUT); + cx.foreground().start_waiting(); + format.await.unwrap(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + "one\ntwo\nthree\n" + ); +} + fn empty_range(row: usize, column: usize) -> Range { let point = DisplayPoint::new(row as u32, column as u32); point..point diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 73906250f38f5847d55a339112db5958c84a0ee7..bd389652a024c6777c34ae30c6952bf318010458 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -498,6 +498,7 @@ pub struct FakeLspAdapter { pub initializer: Option>, pub disk_based_diagnostics_progress_token: Option, pub disk_based_diagnostics_sources: Vec, + pub enabled_formatters: Vec, } #[derive(Clone, Debug, Default)] @@ -1760,6 +1761,7 @@ impl Default for FakeLspAdapter { disk_based_diagnostics_progress_token: None, initialization_options: None, disk_based_diagnostics_sources: Vec::new(), + enabled_formatters: Vec::new(), } } } @@ -1816,6 +1818,10 @@ impl LspAdapter for Arc { async fn initialization_options(&self) -> Option { self.initialization_options.clone() } + + fn enabled_formatters(&self) -> Vec { + self.enabled_formatters.clone() + } } fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option)]) { diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 820a8b6f818f3a2aac1bbf451d566e778fc1d5a4..8bfb26cdadd4829a27fb32405126ad0a7be2ea4f 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -230,19 +230,32 @@ impl FakeNodeRuntime { #[async_trait::async_trait] impl NodeRuntime for FakeNodeRuntime { - async fn binary_path(&self) -> Result { - unreachable!() + async fn binary_path(&self) -> anyhow::Result { + // TODO kb move away into a separate type + a Project's setter (for test code) + Ok(PathBuf::from("prettier_fake_node")) } async fn run_npm_subcommand(&self, _: Option<&Path>, _: &str, _: &[&str]) -> Result { unreachable!() } - async fn npm_package_latest_version(&self, _: &str) -> Result { - unreachable!() + async fn npm_package_latest_version(&self, name: &str) -> anyhow::Result { + if name == "prettier" { + Ok("0.0.1".to_string()) + } else { + unreachable!() + } } - async fn npm_install_packages(&self, _: &Path, _: &[(&str, &str)]) -> Result<()> { - unreachable!() + async fn npm_install_packages( + &self, + _: &Path, + packages: &[(&str, &str)], + ) -> anyhow::Result<()> { + if packages == [("prettier", "0.0.1")] { + Ok(()) + } else { + unreachable!() + } } } diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 586341e66c8d923cf99292dbef5662eab5047dc7..764bf0f07f070cf05e02f6d288645fd26141d3e5 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" [lib] path = "src/prettier.rs" +[features] +test-support = [] + [dependencies] client = { path = "../client" } collections = { path = "../collections"} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 07a7cbd73a357aff809c34f318ea9b6513a59f50..9dfc56f38ce3f91128d6a7d2b9bc02d57af7be7f 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -11,7 +11,7 @@ use language::language_settings::language_settings; use language::proto::deserialize_diff; use language::{Buffer, BundledFormatter, Diff}; use lsp::request::Request; -use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; +use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; @@ -19,6 +19,8 @@ use util::paths::DEFAULT_PRETTIER_DIR; pub enum Prettier { Local(Local), Remote(Remote), + #[cfg(any(test, feature = "test-support"))] + Test(TestPrettier), } pub struct Local { @@ -35,6 +37,13 @@ pub struct Remote { client: Arc, } +#[cfg(any(test, feature = "test-support"))] +pub struct TestPrettier { + worktree_id: Option, + prettier_dir: PathBuf, + default: bool, +} + #[derive(Debug)] pub struct LocateStart { pub worktree_root_path: Arc, @@ -180,6 +189,22 @@ impl Prettier { } } + #[cfg(any(test, feature = "test-support"))] + pub async fn start( + worktree_id: Option, + _: LanguageServerId, + prettier_dir: PathBuf, + _: Arc, + _: AsyncAppContext, + ) -> anyhow::Result { + Ok(Self::Test(TestPrettier { + worktree_id, + default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), + prettier_dir, + })) + } + + #[cfg(not(any(test, feature = "test-support")))] pub async fn start( worktree_id: Option, server_id: LanguageServerId, @@ -400,6 +425,12 @@ impl Prettier { .diff .map(deserialize_diff) .context("missing diff after prettier diff invocation"), + Self::Test(_) => Ok(buffer + .read_with(cx, |buffer, cx| { + let formatted_text = buffer.text() + "\nformatted by test prettier"; + buffer.diff(formatted_text, cx) + }) + .await), } } @@ -427,34 +458,39 @@ impl Prettier { ) }) .context("prettier invoke clear cache"), + Self::Test(_) => Ok(()), } } pub fn server(&self) -> Option<&Arc> { match self { - Prettier::Local(local) => Some(&local.server), - Prettier::Remote(_) => None, + Self::Local(local) => Some(&local.server), + Self::Remote(_) => None, + Self::Test(_) => None, } } pub fn is_default(&self) -> bool { match self { - Prettier::Local(local) => local.default, - Prettier::Remote(_) => false, + Self::Local(local) => local.default, + Self::Remote(_) => false, + Self::Test(test_prettier) => test_prettier.default, } } pub fn prettier_dir(&self) -> &Path { match self { - Prettier::Local(local) => &local.prettier_dir, - Prettier::Remote(remote) => &remote.prettier_dir, + Self::Local(local) => &local.prettier_dir, + Self::Remote(remote) => &remote.prettier_dir, + Self::Test(test_prettier) => &test_prettier.prettier_dir, } } pub fn worktree_id(&self) -> Option { match self { - Prettier::Local(local) => local.worktree_id, - Prettier::Remote(remote) => remote.worktree_id, + Self::Local(local) => local.worktree_id, + Self::Remote(remote) => remote.worktree_id, + Self::Test(test_prettier) => test_prettier.worktree_id, } } } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 9f505c3fd2ddf75a4dbdcb7f0d74bfed51e4eca3..cfa623d53444c06fb981778e075d4fe45a3462cd 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -15,6 +15,7 @@ test-support = [ "language/test-support", "settings/test-support", "text/test-support", + "prettier/test-support", ] [dependencies] @@ -75,6 +76,7 @@ gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } +prettier = { path = "../prettier", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } git2.workspace = true From bdf1731db397516c1fde02dd1f745edb97e92179 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 11 Oct 2023 12:40:57 -0400 Subject: [PATCH 109/180] v0.109.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd36221de00537800e22e3109c1453089ca45244..faf493be09276a16ec5ba63fad1dbffc5389b265 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10081,7 +10081,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.108.0" +version = "0.109.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 7eb14559be8ab64e44eabf55eaf8837338473d06..4174f7d6d54089ffe4250d3a6f80ab0060b1306d 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.108.0" +version = "0.109.0" publish = false [lib] From 0cec0c1c1d82c5b2c5bc0fe5b7afaf9f9073337f Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 11 Oct 2023 13:41:58 -0400 Subject: [PATCH 110/180] Fixup layout --- crates/editor/src/editor.rs | 19 ++++++++++++++++--- crates/theme/src/theme.rs | 4 +++- styles/src/style_tree/editor.ts | 4 +++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 06482dbbc6ff6d0bb8339d1fcffbfa6b0ea29496..3fc47f48e9a92b3b8fd604b4af607af6d4a81d68 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1240,6 +1240,9 @@ impl CompletionsMenu { ) .map(|task| task.detach()); }) + .constrained() + .with_min_width(style.autocomplete.completion_min_width) + .with_max_width(style.autocomplete.completion_max_width) .into_any(), ); } @@ -1250,7 +1253,7 @@ impl CompletionsMenu { enum MultiLineDocumentation {} Flex::row() - .with_child(list) + .with_child(list.flex(1., false)) .with_children({ let mat = &self.matches[selected_item]; let completions = self.completions.read(); @@ -1263,7 +1266,12 @@ impl CompletionsMenu { .scrollable::(0, None, cx) .with_child( Text::new(text.clone(), style.text.clone()).with_soft_wrap(true), - ), + ) + .contained() + .with_style(style.autocomplete.alongside_docs_container) + .constrained() + .with_max_width(style.autocomplete.alongside_docs_max_width) + .flex(1., false), ), Some(Documentation::MultiLineMarkdown(parsed)) => Some( @@ -1271,7 +1279,12 @@ impl CompletionsMenu { .scrollable::(0, None, cx) .with_child(render_parsed_markdown::( parsed, &style, cx, - )), + )) + .contained() + .with_style(style.autocomplete.alongside_docs_container) + .constrained() + .with_max_width(style.autocomplete.alongside_docs_max_width) + .flex(1., false), ), _ => None, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 9f7530ec18da0f3af5016870d916b4edaaf17a0b..f335444b58c2380f2fcec66e9890f32e406db796 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -867,10 +867,12 @@ pub struct AutocompleteStyle { pub selected_item: ContainerStyle, pub hovered_item: ContainerStyle, pub match_highlight: HighlightStyle, + pub completion_min_width: f32, + pub completion_max_width: f32, pub inline_docs_container: ContainerStyle, pub inline_docs_color: Color, pub inline_docs_size_percent: f32, - pub alongside_docs_width: f32, + pub alongside_docs_max_width: f32, pub alongside_docs_container: ContainerStyle, } diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index e7717583a848aec1d0993002fcb2dfcb43b36b87..27a6eaf19579fa9ba891d03d92f7408e83c68ba3 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -206,10 +206,12 @@ export default function editor(): any { match_highlight: foreground(theme.middle, "accent", "active"), background: background(theme.middle, "active"), }, + completion_min_width: 300, + completion_max_width: 700, inline_docs_container: { padding: { left: 40 } }, inline_docs_color: text(theme.middle, "sans", "disabled", {}).color, inline_docs_size_percent: 0.75, - alongside_docs_width: 700, + alongside_docs_max_width: 700, alongside_docs_container: { padding: autocomplete_item.padding } }, diagnostic_header: { From d6fa06b3bee177ff02bc7c987a8c9b1c964eacfd Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 11 Oct 2023 13:51:01 -0400 Subject: [PATCH 111/180] collab 0.24.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index faf493be09276a16ec5ba63fad1dbffc5389b265..72ee771f5d96093685abc019f8d1f3f5d1201c22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1466,7 +1466,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.23.3" +version = "0.24.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 6177c236203d6a2343a5af673d5ac78eadfd7151..8fd1cd438085740a4cac2448903b3d38ce6c0864 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.23.3" +version = "0.24.0" publish = false [[bin]] From a09ee3a41b2a95f2dc016b588970e9b97537e7b8 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 11 Oct 2023 14:39:34 -0400 Subject: [PATCH 112/180] Fire markdown link on mouse down Previously any amount of mouse movement would disqualify the mouse down and up from being a click, being a drag instead, which is a long standing UX issue. We can get away with just firing on mouse down here for now --- crates/editor/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3fc47f48e9a92b3b8fd604b4af607af6d4a81d68..9c1e0b3c1818f30d6676ee5fd3129bc055ff2ad5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -154,7 +154,7 @@ pub fn render_parsed_markdown( }); cx.scene().push_mouse_region( MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { + .on_down::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) }), ); From f6d0934b5d87af4c2e36a14884f498430e14a980 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Oct 2023 15:17:46 -0600 Subject: [PATCH 113/180] deep considered harmful --- script/bundle | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/script/bundle b/script/bundle index e4eb23b21788c76a60bbd0ca46533528482014a2..dc5022bea502da7b8af349d5fe840a931fbf8faa 100755 --- a/script/bundle +++ b/script/bundle @@ -145,7 +145,12 @@ if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTAR security import /tmp/zed-certificate.p12 -k zed.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign rm /tmp/zed-certificate.p12 security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CERTIFICATE_PASSWORD" zed.keychain - /usr/bin/codesign --force --deep --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "Zed Industries, Inc." "${app_path}" -v + + # sequence of codesign commands modeled after this example: https://developer.apple.com/forums/thread/701514 + /usr/bin/codesign --force --timestamp --sign "Zed Industries, Inc." "${app_path}/Contents/Frameworks" -v + /usr/bin/codesign --force --timestamp --options runtime --sign "Zed Industries, Inc." "${app_path}/Contents/MacOS/cli" -v + /usr/bin/codesign --force --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "Zed Industries, Inc." "${app_path}/Contents/MacOS/zed" -v + security default-keychain -s login.keychain else echo "One or more of the following variables are missing: MACOS_CERTIFICATE, MACOS_CERTIFICATE_PASSWORD, APPLE_NOTARIZATION_USERNAME, APPLE_NOTARIZATION_PASSWORD" From 690d9fb971996b17cd58136558118e9e2a02068d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Oct 2023 16:34:11 -0600 Subject: [PATCH 114/180] Add a role column to the database and start using it We cannot yet stop using `admin` because stable will continue writing it. --- .../20221109000000_test_schema.sql | 1 + .../20231011214412_add_guest_role.sql | 4 +++ crates/collab/src/db/ids.rs | 11 ++++++ crates/collab/src/db/queries/channels.rs | 34 +++++++++++++------ crates/collab/src/db/tables/channel_member.rs | 6 ++-- crates/collab/src/db/tests/buffer_tests.rs | 4 +-- crates/collab/src/db/tests/channel_tests.rs | 18 ++++++---- crates/collab/src/db/tests/message_tests.rs | 6 ++-- crates/collab/src/rpc.rs | 18 +++++++--- .../src/tests/random_channel_buffer_tests.rs | 4 ++- 10 files changed, 76 insertions(+), 30 deletions(-) create mode 100644 crates/collab/migrations/20231011214412_add_guest_role.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 5a84bfd796a88e09769004cc40d0cc6a06e3118a..dd6e80150be6034c02368372d2ff6f6936a00ef7 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -226,6 +226,7 @@ CREATE TABLE "channel_members" ( "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE, "user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, "admin" BOOLEAN NOT NULL DEFAULT false, + "role" VARCHAR, "accepted" BOOLEAN NOT NULL DEFAULT false, "updated_at" TIMESTAMP NOT NULL DEFAULT now ); diff --git a/crates/collab/migrations/20231011214412_add_guest_role.sql b/crates/collab/migrations/20231011214412_add_guest_role.sql new file mode 100644 index 0000000000000000000000000000000000000000..378590a0f9702fd63630a32957780096e3d7ba56 --- /dev/null +++ b/crates/collab/migrations/20231011214412_add_guest_role.sql @@ -0,0 +1,4 @@ +-- Add migration script here + +ALTER TABLE channel_members ADD COLUMN role TEXT; +UPDATE channel_members SET role = CASE WHEN admin THEN 'admin' ELSE 'member' END; diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 23bb9e53bf9803ba64693f94605a8e87f904c571..747e3a7d3b17642191a0e1067c11cf1d1c3e205c 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -80,3 +80,14 @@ id_type!(SignupId); id_type!(UserId); id_type!(ChannelBufferCollaboratorId); id_type!(FlagId); + +#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(None)")] +pub enum ChannelRole { + #[sea_orm(string_value = "admin")] + Admin, + #[sea_orm(string_value = "member")] + Member, + #[sea_orm(string_value = "guest")] + Guest, +} diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index c576d2406b81279c38561406c3801c02ddaf4377..0fe78209164be20ba0f2f618a6be7190beed1c26 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -74,11 +74,12 @@ impl Database { } channel_member::ActiveModel { + id: ActiveValue::NotSet, channel_id: ActiveValue::Set(channel.id), user_id: ActiveValue::Set(creator_id), accepted: ActiveValue::Set(true), admin: ActiveValue::Set(true), - ..Default::default() + role: ActiveValue::Set(Some(ChannelRole::Admin)), } .insert(&*tx) .await?; @@ -160,18 +161,19 @@ impl Database { channel_id: ChannelId, invitee_id: UserId, inviter_id: UserId, - is_admin: bool, + role: ChannelRole, ) -> Result<()> { self.transaction(move |tx| async move { self.check_user_is_channel_admin(channel_id, inviter_id, &*tx) .await?; channel_member::ActiveModel { + id: ActiveValue::NotSet, channel_id: ActiveValue::Set(channel_id), user_id: ActiveValue::Set(invitee_id), accepted: ActiveValue::Set(false), - admin: ActiveValue::Set(is_admin), - ..Default::default() + admin: ActiveValue::Set(role == ChannelRole::Admin), + role: ActiveValue::Set(Some(role)), } .insert(&*tx) .await?; @@ -417,7 +419,13 @@ impl Database { let channels_with_admin_privileges = channel_memberships .iter() - .filter_map(|membership| membership.admin.then_some(membership.channel_id)) + .filter_map(|membership| { + if membership.role == Some(ChannelRole::Admin) || membership.admin { + Some(membership.channel_id) + } else { + None + } + }) .collect(); let graph = self @@ -470,12 +478,12 @@ impl Database { .await } - pub async fn set_channel_member_admin( + pub async fn set_channel_member_role( &self, channel_id: ChannelId, from: UserId, for_user: UserId, - admin: bool, + role: ChannelRole, ) -> Result<()> { self.transaction(|tx| async move { self.check_user_is_channel_admin(channel_id, from, &*tx) @@ -488,7 +496,8 @@ impl Database { .and(channel_member::Column::UserId.eq(for_user)), ) .set(channel_member::ActiveModel { - admin: ActiveValue::set(admin), + admin: ActiveValue::set(role == ChannelRole::Admin), + role: ActiveValue::set(Some(role)), ..Default::default() }) .exec(&*tx) @@ -516,6 +525,7 @@ impl Database { enum QueryMemberDetails { UserId, Admin, + Role, IsDirectMember, Accepted, } @@ -528,6 +538,7 @@ impl Database { .select_only() .column(channel_member::Column::UserId) .column(channel_member::Column::Admin) + .column(channel_member::Column::Role) .column_as( channel_member::Column::ChannelId.eq(channel_id), QueryMemberDetails::IsDirectMember, @@ -540,9 +551,10 @@ impl Database { let mut rows = Vec::::new(); while let Some(row) = stream.next().await { - let (user_id, is_admin, is_direct_member, is_invite_accepted): ( + let (user_id, is_admin, channel_role, is_direct_member, is_invite_accepted): ( UserId, bool, + Option, bool, bool, ) = row?; @@ -558,7 +570,7 @@ impl Database { if last_row.user_id == user_id { if is_direct_member { last_row.kind = kind; - last_row.admin = is_admin; + last_row.admin = channel_role == Some(ChannelRole::Admin) || is_admin; } continue; } @@ -566,7 +578,7 @@ impl Database { rows.push(proto::ChannelMember { user_id, kind, - admin: is_admin, + admin: channel_role == Some(ChannelRole::Admin) || is_admin, }); } diff --git a/crates/collab/src/db/tables/channel_member.rs b/crates/collab/src/db/tables/channel_member.rs index ba3db5a15504e60838439cce60e5bef9a02d83bb..e8162bfcbd133fe647dbbfa13cab1136dcbe441e 100644 --- a/crates/collab/src/db/tables/channel_member.rs +++ b/crates/collab/src/db/tables/channel_member.rs @@ -1,7 +1,7 @@ -use crate::db::{channel_member, ChannelId, ChannelMemberId, UserId}; +use crate::db::{channel_member, ChannelId, ChannelMemberId, ChannelRole, UserId}; use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)] +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "channel_members")] pub struct Model { #[sea_orm(primary_key)] @@ -10,6 +10,8 @@ pub struct Model { pub user_id: UserId, pub accepted: bool, pub admin: bool, + // only optional while migrating + pub role: Option, } impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/db/tests/buffer_tests.rs b/crates/collab/src/db/tests/buffer_tests.rs index 0ac41a8b0b4267fdd50e8e2c8392319169194888..51ba9bf655221a5c611ad3fa023631f46151f144 100644 --- a/crates/collab/src/db/tests/buffer_tests.rs +++ b/crates/collab/src/db/tests/buffer_tests.rs @@ -56,7 +56,7 @@ async fn test_channel_buffers(db: &Arc) { let zed_id = db.create_root_channel("zed", a_id).await.unwrap(); - db.invite_channel_member(zed_id, b_id, a_id, false) + db.invite_channel_member(zed_id, b_id, a_id, ChannelRole::Member) .await .unwrap(); @@ -211,7 +211,7 @@ async fn test_channel_buffers_last_operations(db: &Database) { .await .unwrap(); - db.invite_channel_member(channel, observer_id, user_id, false) + db.invite_channel_member(channel, observer_id, user_id, ChannelRole::Member) .await .unwrap(); db.respond_to_channel_invite(channel, observer_id, true) diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 7d2bc04a35aac3adb30ead310705d2ee192ed54a..ed4b9e061b0ee7ab1181431b24ffdb1cd88ba5c2 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -8,7 +8,7 @@ use crate::{ db::{ queries::channels::ChannelGraph, tests::{graph, TEST_RELEASE_CHANNEL}, - ChannelId, Database, NewUserParams, + ChannelId, ChannelRole, Database, NewUserParams, }, test_both_dbs, }; @@ -50,7 +50,7 @@ async fn test_channels(db: &Arc) { // Make sure that people cannot read channels they haven't been invited to assert!(db.get_channel(zed_id, b_id).await.unwrap().is_none()); - db.invite_channel_member(zed_id, b_id, a_id, false) + db.invite_channel_member(zed_id, b_id, a_id, ChannelRole::Member) .await .unwrap(); @@ -125,9 +125,13 @@ async fn test_channels(db: &Arc) { ); // Update member permissions - let set_subchannel_admin = db.set_channel_member_admin(crdb_id, a_id, b_id, true).await; + let set_subchannel_admin = db + .set_channel_member_role(crdb_id, a_id, b_id, ChannelRole::Admin) + .await; assert!(set_subchannel_admin.is_err()); - let set_channel_admin = db.set_channel_member_admin(zed_id, a_id, b_id, true).await; + let set_channel_admin = db + .set_channel_member_role(zed_id, a_id, b_id, ChannelRole::Admin) + .await; assert!(set_channel_admin.is_ok()); let result = db.get_channels_for_user(b_id).await.unwrap(); @@ -284,13 +288,13 @@ async fn test_channel_invites(db: &Arc) { let channel_1_2 = db.create_root_channel("channel_2", user_1).await.unwrap(); - db.invite_channel_member(channel_1_1, user_2, user_1, false) + db.invite_channel_member(channel_1_1, user_2, user_1, ChannelRole::Member) .await .unwrap(); - db.invite_channel_member(channel_1_2, user_2, user_1, false) + db.invite_channel_member(channel_1_2, user_2, user_1, ChannelRole::Member) .await .unwrap(); - db.invite_channel_member(channel_1_1, user_3, user_1, true) + db.invite_channel_member(channel_1_1, user_3, user_1, ChannelRole::Admin) .await .unwrap(); diff --git a/crates/collab/src/db/tests/message_tests.rs b/crates/collab/src/db/tests/message_tests.rs index e758fcfb5d0104a61ad84dd82ce10fad784fdf5a..272d8e01009ac8758e887d2c0ec4f04570464923 100644 --- a/crates/collab/src/db/tests/message_tests.rs +++ b/crates/collab/src/db/tests/message_tests.rs @@ -1,5 +1,5 @@ use crate::{ - db::{Database, MessageId, NewUserParams}, + db::{ChannelRole, Database, MessageId, NewUserParams}, test_both_dbs, }; use std::sync::Arc; @@ -155,7 +155,7 @@ async fn test_channel_message_new_notification(db: &Arc) { let channel_2 = db.create_channel("channel-2", None, user).await.unwrap(); - db.invite_channel_member(channel_1, observer, user, false) + db.invite_channel_member(channel_1, observer, user, ChannelRole::Member) .await .unwrap(); @@ -163,7 +163,7 @@ async fn test_channel_message_new_notification(db: &Arc) { .await .unwrap(); - db.invite_channel_member(channel_2, observer, user, false) + db.invite_channel_member(channel_2, observer, user, ChannelRole::Member) .await .unwrap(); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index e5c6d94ce03b8b3b1d64ed58be4da53f9dcca112..f13f482c2b3b156401fa20a3b8a8a8a7f51c2f5f 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3,8 +3,8 @@ mod connection_pool; use crate::{ auth, db::{ - self, BufferId, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId, - ServerId, User, UserId, + self, BufferId, ChannelId, ChannelRole, ChannelsForUser, Database, MessageId, ProjectId, + RoomId, ServerId, User, UserId, }, executor::Executor, AppState, Result, @@ -2282,7 +2282,12 @@ async fn invite_channel_member( let db = session.db().await; let channel_id = ChannelId::from_proto(request.channel_id); let invitee_id = UserId::from_proto(request.user_id); - db.invite_channel_member(channel_id, invitee_id, session.user_id, request.admin) + let role = if request.admin { + ChannelRole::Admin + } else { + ChannelRole::Member + }; + db.invite_channel_member(channel_id, invitee_id, session.user_id, role) .await?; let (channel, _) = db @@ -2342,7 +2347,12 @@ async fn set_channel_member_admin( let db = session.db().await; let channel_id = ChannelId::from_proto(request.channel_id); let member_id = UserId::from_proto(request.user_id); - db.set_channel_member_admin(channel_id, session.user_id, member_id, request.admin) + let role = if request.admin { + ChannelRole::Admin + } else { + ChannelRole::Member + }; + db.set_channel_member_role(channel_id, session.user_id, member_id, role) .await?; let (channel, has_accepted) = db diff --git a/crates/collab/src/tests/random_channel_buffer_tests.rs b/crates/collab/src/tests/random_channel_buffer_tests.rs index 6e0bef225c9fc2d3bc78faa5ddca6e65e28f5495..1b24c7a3d2f45e5e374c5a70cd54a426b4043753 100644 --- a/crates/collab/src/tests/random_channel_buffer_tests.rs +++ b/crates/collab/src/tests/random_channel_buffer_tests.rs @@ -1,3 +1,5 @@ +use crate::db::ChannelRole; + use super::{run_randomized_test, RandomizedTest, TestClient, TestError, TestServer, UserTestPlan}; use anyhow::Result; use async_trait::async_trait; @@ -50,7 +52,7 @@ impl RandomizedTest for RandomChannelBufferTest { .await .unwrap(); for user in &users[1..] { - db.invite_channel_member(id, user.user_id, users[0].user_id, false) + db.invite_channel_member(id, user.user_id, users[0].user_id, ChannelRole::Member) .await .unwrap(); db.respond_to_channel_invite(id, user.user_id, true) From a528c6c68610ab075ace8aad4bb22a1b6cf46d94 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 12:31:30 +0300 Subject: [PATCH 115/180] Prettier server style fixes --- crates/node_runtime/src/node_runtime.rs | 4 ++-- crates/prettier/src/prettier.rs | 21 ++++++++++++++++----- crates/prettier/src/prettier_server.js | 23 +++++++++++++---------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 8bfb26cdadd4829a27fb32405126ad0a7be2ea4f..125318dd67e0a63eb8e47a1167596ff485830505 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -243,7 +243,7 @@ impl NodeRuntime for FakeNodeRuntime { if name == "prettier" { Ok("0.0.1".to_string()) } else { - unreachable!() + unreachable!("Unexpected package name: {name}") } } @@ -255,7 +255,7 @@ impl NodeRuntime for FakeNodeRuntime { if packages == [("prettier", "0.0.1")] { Ok(()) } else { - unreachable!() + unreachable!("Unexpected packages to install: {packages:?}") } } } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 9dfc56f38ce3f91128d6a7d2b9bc02d57af7be7f..6c1d0665e2c24c4d01025901d3503e57c8252641 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -197,11 +197,14 @@ impl Prettier { _: Arc, _: AsyncAppContext, ) -> anyhow::Result { - Ok(Self::Test(TestPrettier { - worktree_id, - default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), - prettier_dir, - })) + Ok( + #[cfg(any(test, feature = "test-support"))] + Self::Test(TestPrettier { + worktree_id, + default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), + prettier_dir, + }), + ) } #[cfg(not(any(test, feature = "test-support")))] @@ -212,6 +215,8 @@ impl Prettier { node: Arc, cx: AsyncAppContext, ) -> anyhow::Result { + use lsp::LanguageServerBinary; + let backgroud = cx.background(); anyhow::ensure!( prettier_dir.is_dir(), @@ -425,6 +430,7 @@ impl Prettier { .diff .map(deserialize_diff) .context("missing diff after prettier diff invocation"), + #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(buffer .read_with(cx, |buffer, cx| { let formatted_text = buffer.text() + "\nformatted by test prettier"; @@ -458,6 +464,7 @@ impl Prettier { ) }) .context("prettier invoke clear cache"), + #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(()), } } @@ -466,6 +473,7 @@ impl Prettier { match self { Self::Local(local) => Some(&local.server), Self::Remote(_) => None, + #[cfg(any(test, feature = "test-support"))] Self::Test(_) => None, } } @@ -474,6 +482,7 @@ impl Prettier { match self { Self::Local(local) => local.default, Self::Remote(_) => false, + #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.default, } } @@ -482,6 +491,7 @@ impl Prettier { match self { Self::Local(local) => &local.prettier_dir, Self::Remote(remote) => &remote.prettier_dir, + #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => &test_prettier.prettier_dir, } } @@ -490,6 +500,7 @@ impl Prettier { match self { Self::Local(local) => local.worktree_id, Self::Remote(remote) => remote.worktree_id, + #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.worktree_id, } } diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index db3e26ef6dd781a1937199ed1c22ce09b8c02328..a56c220f208607d00d1ef413c8529dd4244008b4 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -39,13 +39,13 @@ class Prettier { process.stderr.write(`Failed to load prettier: ${e}\n`); process.exit(1); } - process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${config}\n`); + process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${JSON.stringify(config)}\n`); process.stdin.resume(); handleBuffer(new Prettier(prettierPath, prettier, config)); })() async function handleBuffer(prettier) { - for await (let messageText of readStdin()) { + for await (const messageText of readStdin()) { let message; try { message = JSON.parse(messageText); @@ -53,6 +53,7 @@ async function handleBuffer(prettier) { sendResponse(makeError(`Failed to parse message '${messageText}': ${e}`)); continue; } + // allow concurrent request handling by not `await`ing the message handling promise (async function) handleMessage(message, prettier).catch(e => { sendResponse({ id: message.id, ...makeError(`error during message handling: ${e}`) }); }); @@ -96,16 +97,18 @@ async function* readStdin() { await once(process.stdin, 'readable'); } const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii'); - const contentLengthHeader = headers.split(headerSeparator).map(header => header.split(':')) + const contentLengthHeader = headers.split(headerSeparator) + .map(header => header.split(':')) .filter(header => header[2] === undefined) .filter(header => (header[1] || '').length > 0) - .find(header => header[0].trim() === contentLengthHeaderName); - if (contentLengthHeader === undefined) { + .find(header => (header[0] || '').trim() === contentLengthHeaderName); + const contentLength = (contentLengthHeader || [])[1]; + if (contentLength === undefined) { await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`); continue main_loop; } headersLength = headers.length + headerSeparator.length * 2; - messageLength = parseInt(contentLengthHeader[1], 10); + messageLength = parseInt(contentLength, 10); } while (buffer.length < (headersLength + messageLength)) { @@ -120,6 +123,7 @@ async function* readStdin() { const messageEnd = headersLength + messageLength; const message = buffer.subarray(headersLength, messageEnd); buffer = buffer.subarray(messageEnd); + headersLength = null; messageLength = null; yield message.toString('utf8'); } @@ -188,13 +192,12 @@ function makeError(message) { } function sendResponse(response) { - let responsePayloadString = JSON.stringify({ + const responsePayloadString = JSON.stringify({ jsonrpc: "2.0", ...response }); - let headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; - let dataToSend = headers + responsePayloadString; - process.stdout.write(dataToSend); + const headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; + process.stdout.write(headers + responsePayloadString); } function loadPrettier(prettierPath) { From 7f4ebf50d3cee3d34733d43e48f19d8c0a0d6122 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 12:40:35 +0300 Subject: [PATCH 116/180] Make the first prettier test pass --- crates/editor/src/editor_tests.rs | 27 +++--- crates/node_runtime/src/node_runtime.rs | 107 +++++++++++++++++++++--- crates/prettier/src/prettier.rs | 5 +- crates/project/src/project.rs | 14 +++- 4 files changed, 124 insertions(+), 29 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index ddae186679df45c9478e2ccfda70564e5d7b81f5..4cb0c009bb12abee25cf78195a202d9559608045 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -7830,11 +7830,12 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { Some(tree_sitter_rust::language()), ); + let test_plugin = "test_plugin"; let _ = language .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { enabled_formatters: vec![BundledFormatter::Prettier { parser_name: Some("test_parser"), - plugin_names: vec!["test_plugin"], + plugin_names: vec![test_plugin], }], ..Default::default() })) @@ -7843,37 +7844,31 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { let fs = FakeFs::new(cx.background()); fs.insert_file("/file.rs", Default::default()).await; - // TODO kb have to specify some test node runtime let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; - project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let prettier_format_suffix = project.update(cx, |project, _| { + let suffix = project.enable_test_prettier(&[test_plugin]); + project.languages().add(Arc::new(language)); + suffix + }); let buffer = project .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) .await .unwrap(); + let buffer_text = "one\ntwo\nthree\n"; let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); - editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); + editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx)); let format = editor.update(cx, |editor, cx| { editor.perform_format(project.clone(), FormatTrigger::Manual, cx) }); format.await.unwrap(); - assert_eq!( - editor.read_with(cx, |editor, cx| editor.text(cx)), - "one, two\nthree\n" - ); - editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); - let format = editor.update(cx, |editor, cx| { - editor.perform_format(project, FormatTrigger::Manual, cx) - }); - cx.foreground().advance_clock(super::FORMAT_TIMEOUT); - cx.foreground().start_waiting(); - format.await.unwrap(); assert_eq!( editor.read_with(cx, |editor, cx| editor.text(cx)), - "one\ntwo\nthree\n" + buffer_text.to_string() + prettier_format_suffix, + "Test prettier formatting was not applied to the original buffer text", ); } diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 125318dd67e0a63eb8e47a1167596ff485830505..dcb8833f8c65a364f5ed4240846f60897e1115c2 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -220,18 +220,83 @@ impl NodeRuntime for RealNodeRuntime { } } -pub struct FakeNodeRuntime; +pub struct FakeNodeRuntime(Option); + +struct PrettierSupport { + plugins: Vec<&'static str>, +} impl FakeNodeRuntime { pub fn new() -> Arc { - Arc::new(FakeNodeRuntime) + Arc::new(FakeNodeRuntime(None)) + } + + pub fn with_prettier_support(plugins: &[&'static str]) -> Arc { + Arc::new(FakeNodeRuntime(Some(PrettierSupport::new(plugins)))) } } #[async_trait::async_trait] impl NodeRuntime for FakeNodeRuntime { async fn binary_path(&self) -> anyhow::Result { - // TODO kb move away into a separate type + a Project's setter (for test code) + if let Some(prettier_support) = &self.0 { + prettier_support.binary_path().await + } else { + unreachable!() + } + } + + async fn run_npm_subcommand( + &self, + directory: Option<&Path>, + subcommand: &str, + args: &[&str], + ) -> anyhow::Result { + if let Some(prettier_support) = &self.0 { + prettier_support + .run_npm_subcommand(directory, subcommand, args) + .await + } else { + unreachable!() + } + } + + async fn npm_package_latest_version(&self, name: &str) -> anyhow::Result { + if let Some(prettier_support) = &self.0 { + prettier_support.npm_package_latest_version(name).await + } else { + unreachable!() + } + } + + async fn npm_install_packages( + &self, + directory: &Path, + packages: &[(&str, &str)], + ) -> anyhow::Result<()> { + if let Some(prettier_support) = &self.0 { + prettier_support + .npm_install_packages(directory, packages) + .await + } else { + unreachable!() + } + } +} + +impl PrettierSupport { + const PACKAGE_VERSION: &str = "0.0.1"; + + fn new(plugins: &[&'static str]) -> Self { + Self { + plugins: plugins.to_vec(), + } + } +} + +#[async_trait::async_trait] +impl NodeRuntime for PrettierSupport { + async fn binary_path(&self) -> anyhow::Result { Ok(PathBuf::from("prettier_fake_node")) } @@ -240,10 +305,10 @@ impl NodeRuntime for FakeNodeRuntime { } async fn npm_package_latest_version(&self, name: &str) -> anyhow::Result { - if name == "prettier" { - Ok("0.0.1".to_string()) + if name == "prettier" || self.plugins.contains(&name) { + Ok(Self::PACKAGE_VERSION.to_string()) } else { - unreachable!("Unexpected package name: {name}") + panic!("Unexpected package name: {name}") } } @@ -252,10 +317,32 @@ impl NodeRuntime for FakeNodeRuntime { _: &Path, packages: &[(&str, &str)], ) -> anyhow::Result<()> { - if packages == [("prettier", "0.0.1")] { - Ok(()) - } else { - unreachable!("Unexpected packages to install: {packages:?}") + assert_eq!( + packages.len(), + self.plugins.len() + 1, + "Unexpected packages length to install: {:?}, expected `prettier` + {:?}", + packages, + self.plugins + ); + for (name, version) in packages { + assert!( + name == &"prettier" || self.plugins.contains(name), + "Unexpected package `{}` to install in packages {:?}, expected {} for `prettier` + {:?}", + name, + packages, + Self::PACKAGE_VERSION, + self.plugins + ); + assert_eq!( + version, + &Self::PACKAGE_VERSION, + "Unexpected package version `{}` to install in packages {:?}, expected {} for `prettier` + {:?}", + version, + packages, + Self::PACKAGE_VERSION, + self.plugins + ); } + Ok(()) } } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 6c1d0665e2c24c4d01025901d3503e57c8252641..656c84b46c6e881d60066d893959c2abe24dbf25 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -71,6 +71,9 @@ impl Prettier { ".editorconfig", ]; + #[cfg(any(test, feature = "test-support"))] + pub const FORMAT_SUFFIX: &str = "\nformatted by test prettier"; + pub fn remote( project_id: u64, worktree_id: Option, @@ -433,7 +436,7 @@ impl Prettier { #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(buffer .read_with(cx, |buffer, cx| { - let formatted_text = buffer.text() + "\nformatted by test prettier"; + let formatted_text = buffer.text() + Self::FORMAT_SUFFIX; buffer.diff(formatted_text, cx) }) .await), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ebc2aaaf604cbc89a14807cdcf7a3dc6778e061f..e31ecca2577243debabf2326917d6e3b1363c406 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -837,6 +837,16 @@ impl Project { project } + /// Enables a prettier mock that avoids interacting with node runtime, prettier LSP wrapper, or any real file changes. + /// Instead, if appends the suffix to every input, this suffix is returned by this method. + #[cfg(any(test, feature = "test-support"))] + pub fn enable_test_prettier(&mut self, plugins: &[&'static str]) -> &'static str { + self.node = Some(node_runtime::FakeNodeRuntime::with_prettier_support( + plugins, + )); + Prettier::FORMAT_SUFFIX + } + fn on_settings_changed(&mut self, cx: &mut ModelContext) { let mut language_servers_to_start = Vec::new(); let mut language_formatters_to_check = Vec::new(); @@ -8442,7 +8452,7 @@ impl Project { return Some(existing_prettier); } - log::info!("Found prettier at {prettier_dir:?}, starting."); + log::info!("Found prettier in {prettier_dir:?}, starting."); let task_prettier_dir = prettier_dir.clone(); let weak_project = this.downgrade(); let new_server_id = @@ -8459,7 +8469,7 @@ impl Project { .await .context("prettier start") .map_err(Arc::new)?; - log::info!("Had started prettier in {:?}", prettier.prettier_dir()); + log::info!("Started prettier in {:?}", prettier.prettier_dir()); if let Some((project, prettier_server)) = weak_project.upgrade(&mut cx).zip(prettier.server()) From 1bfde4bfa254ca151ad2f918cb2102fefb68055e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 14:45:56 +0300 Subject: [PATCH 117/180] Add more tests --- crates/collab/src/tests/integration_tests.rs | 138 ++++++++++++++++++- crates/editor/src/editor_tests.rs | 33 ++++- 2 files changed, 162 insertions(+), 9 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 4008a941dd2e76be691e8a9d54b5cb66f1f8c5a2..d6d449fd476b0d6f967641268bf1798a21ccf81d 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -15,12 +15,14 @@ use gpui::{executor::Deterministic, test::EmptyView, AppContext, ModelHandle, Te use indoc::indoc; use language::{ language_settings::{AllLanguageSettings, Formatter, InlayHintSettings}, - tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, - LanguageConfig, LineEnding, OffsetRangeExt, Point, Rope, + tree_sitter_rust, Anchor, BundledFormatter, Diagnostic, DiagnosticEntry, FakeLspAdapter, + Language, LanguageConfig, LineEnding, OffsetRangeExt, Point, Rope, }; use live_kit_client::MacOSDisplay; use lsp::LanguageServerId; -use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath}; +use project::{ + search::SearchQuery, DiagnosticSummary, FormatTrigger, HoverBlockKind, Project, ProjectPath, +}; use rand::prelude::*; use serde_json::json; use settings::SettingsStore; @@ -4407,8 +4409,6 @@ async fn test_formatting_buffer( cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, ) { - use project::FormatTrigger; - let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -4511,6 +4511,134 @@ async fn test_formatting_buffer( ); } +#[gpui::test(iterations = 10)] +async fn test_prettier_formatting_buffer( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(&deterministic).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + + // Set up a fake language server. + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let test_plugin = "test_plugin"; + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + enabled_formatters: vec![BundledFormatter::Prettier { + parser_name: Some("test_parser"), + plugin_names: vec![test_plugin], + }], + ..Default::default() + })) + .await; + let language = Arc::new(language); + client_a.language_registry().add(Arc::clone(&language)); + + // Here we insert a fake tree with a directory that exists on disk. This is needed + // because later we'll invoke a command, which requires passing a working directory + // that points to a valid location on disk. + let directory = env::current_dir().unwrap(); + let buffer_text = "let one = \"two\""; + client_a + .fs() + .insert_tree(&directory, json!({ "a.rs": buffer_text })) + .await; + let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await; + let prettier_format_suffix = project_a.update(cx_a, |project, _| { + let suffix = project.enable_test_prettier(&[test_plugin]); + project.languages().add(language); + suffix + }); + let buffer_a = cx_a + .background() + .spawn(project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))) + .await + .unwrap(); + + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + let project_b = client_b.build_remote_project(project_id, cx_b).await; + let buffer_b = cx_b + .background() + .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))) + .await + .unwrap(); + + cx_a.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |file| { + file.defaults.formatter = Some(Formatter::Auto); + }); + }); + }); + cx_b.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |file| { + file.defaults.formatter = Some(Formatter::LanguageServer); + }); + }); + }); + let fake_language_server = fake_language_servers.next().await.unwrap(); + fake_language_server.handle_request::(|_, _| async move { + panic!( + "Unexpected: prettier should be preferred since it's enabled and language supports it" + ) + }); + + project_b + .update(cx_b, |project, cx| { + project.format( + HashSet::from_iter([buffer_b.clone()]), + true, + FormatTrigger::Save, + cx, + ) + }) + .await + .unwrap(); + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + assert_eq!( + buffer_b.read_with(cx_b, |buffer, _| buffer.text()), + buffer_text.to_string() + "\n" + prettier_format_suffix, + "Prettier formatting was not applied to client buffer after client's request" + ); + + project_a + .update(cx_a, |project, cx| { + project.format( + HashSet::from_iter([buffer_a.clone()]), + true, + FormatTrigger::Manual, + cx, + ) + }) + .await + .unwrap(); + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + assert_eq!( + buffer_b.read_with(cx_b, |buffer, _| buffer.text()), + buffer_text.to_string() + "\n" + prettier_format_suffix + "\n" + prettier_format_suffix, + "Prettier formatting was not applied to client buffer after host's request" + ); +} + #[gpui::test(iterations = 10)] async fn test_definition( deterministic: Arc, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 4cb0c009bb12abee25cf78195a202d9559608045..c68f72d16f13c550500d41ad63f76bcec29c2cf1 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5076,7 +5076,9 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { - init_test(cx, |_| {}); + init_test(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer) + }); let mut language = Language::new( LanguageConfig { @@ -5092,6 +5094,12 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { document_formatting_provider: Some(lsp::OneOf::Left(true)), ..Default::default() }, + // Enable Prettier formatting for the same buffer, and ensure + // LSP is called instead of Prettier. + enabled_formatters: vec![BundledFormatter::Prettier { + parser_name: Some("test_parser"), + plugin_names: Vec::new(), + }], ..Default::default() })) .await; @@ -5100,7 +5108,10 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { fs.insert_file("/file.rs", Default::default()).await; let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; - project.update(cx, |project, _| project.languages().add(Arc::new(language))); + project.update(cx, |project, _| { + project.enable_test_prettier(&[]); + project.languages().add(Arc::new(language)); + }); let buffer = project .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) .await @@ -5218,7 +5229,9 @@ async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) { - init_test(cx, |_| {}); + init_test(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::Auto) + }); let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { @@ -7864,12 +7877,24 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { editor.perform_format(project.clone(), FormatTrigger::Manual, cx) }); format.await.unwrap(); - assert_eq!( editor.read_with(cx, |editor, cx| editor.text(cx)), buffer_text.to_string() + prettier_format_suffix, "Test prettier formatting was not applied to the original buffer text", ); + + update_test_language_settings(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::Auto) + }); + let format = editor.update(cx, |editor, cx| { + editor.perform_format(project.clone(), FormatTrigger::Manual, cx) + }); + format.await.unwrap(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix, + "Autoformatting (via test prettier) was not applied to the original buffer text", + ); } fn empty_range(row: usize, column: usize) -> Range { From 12d7d8db0a85f77c41b2b05944293e83eafe9f1f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 15:26:47 +0300 Subject: [PATCH 118/180] Make all formatting to happen on the client's buffers, as needed --- crates/language/src/proto.rs | 41 +---------- crates/prettier/src/prettier.rs | 104 +++------------------------ crates/project/src/project.rs | 122 +------------------------------- crates/rpc/proto/zed.proto | 39 +--------- crates/rpc/src/proto.rs | 8 --- 5 files changed, 14 insertions(+), 300 deletions(-) diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 5da1d5713fd2c63719f915d9930e7df7bd3cfa53..c4abe39d4782aafbe90594e3a0bc5de70787fa03 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -1,6 +1,6 @@ use crate::{ diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, - Diff, Language, + Language, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -587,42 +587,3 @@ pub fn serialize_version(version: &clock::Global) -> Vec proto::Diff { - proto::Diff { - version: serialize_version(&diff.base_version), - line_ending: serialize_line_ending(diff.line_ending) as i32, - edits: diff - .edits - .into_iter() - .map(|(range, edit)| proto::DiffEdit { - range: Some(proto::Range { - start: range.start as u64, - end: range.end as u64, - }), - edit: edit.to_string(), - }) - .collect(), - } -} - -pub fn deserialize_diff(diff: proto::Diff) -> Diff { - Diff { - base_version: deserialize_version(&diff.version), - line_ending: deserialize_line_ending( - rpc::proto::LineEnding::from_i32(diff.line_ending) - .unwrap_or_else(|| panic!("invalid line ending {}", diff.line_ending)), - ), - edits: diff - .edits - .into_iter() - .map(|edit| { - let range = edit.range.expect("incorrect edit without a range"); - ( - range.start as usize..range.end as usize, - Arc::from(edit.edit.as_str()), - ) - }) - .collect(), - } -} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 656c84b46c6e881d60066d893959c2abe24dbf25..240ee1e8959949a9c481b8613bf1e3b5befaef4b 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,40 +3,29 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; -use client::{proto, Client}; use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; -use language::proto::deserialize_diff; use language::{Buffer, BundledFormatter, Diff}; -use lsp::request::Request; use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; pub enum Prettier { - Local(Local), - Remote(Remote), + Real(RealPrettier), #[cfg(any(test, feature = "test-support"))] Test(TestPrettier), } -pub struct Local { +pub struct RealPrettier { worktree_id: Option, default: bool, prettier_dir: PathBuf, server: Arc, } -pub struct Remote { - project_id: u64, - worktree_id: Option, - prettier_dir: PathBuf, - client: Arc, -} - #[cfg(any(test, feature = "test-support"))] pub struct TestPrettier { worktree_id: Option, @@ -74,20 +63,6 @@ impl Prettier { #[cfg(any(test, feature = "test-support"))] pub const FORMAT_SUFFIX: &str = "\nformatted by test prettier"; - pub fn remote( - project_id: u64, - worktree_id: Option, - prettier_dir: PathBuf, - client: Arc, - ) -> Self { - Self::Remote(Remote { - project_id, - worktree_id, - prettier_dir, - client, - }) - } - pub async fn locate( starting_path: Option, fs: Arc, @@ -249,7 +224,7 @@ impl Prettier { .spawn(server.initialize(None)) .await .context("prettier server initialization")?; - Ok(Self::Local(Local { + Ok(Self::Real(RealPrettier { worktree_id, server, default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), @@ -257,31 +232,6 @@ impl Prettier { })) } - pub async fn invoke( - &self, - buffer: Option<&ModelHandle>, - buffer_path: Option, - method: &str, - cx: &AsyncAppContext, - ) -> anyhow::Result> { - match method { - Format::METHOD => self - .format( - buffer.expect("missing buffer for format invocation"), - buffer_path, - cx, - ) - .await - .context("invoke method") - .map(Some), - ClearCache::METHOD => { - self.clear_cache().await.context("invoke method")?; - Ok(None) - } - unknown => anyhow::bail!("Unknown method {unknown}"), - } - } - pub async fn format( &self, buffer: &ModelHandle, @@ -289,7 +239,7 @@ impl Prettier { cx: &AsyncAppContext, ) -> anyhow::Result { match self { - Self::Local(local) => { + Self::Real(local) => { let params = buffer.read_with(cx, |buffer, cx| { let buffer_language = buffer.language(); let parsers_with_plugins = buffer_language @@ -418,21 +368,6 @@ impl Prettier { let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); Ok(diff_task.await) } - Self::Remote(remote) => buffer - .read_with(cx, |buffer, _| { - remote.client.request(proto::InvokePrettierForBuffer { - buffer_id: Some(buffer.remote_id()), - worktree_id: self.worktree_id().map(|id| id as u64), - method: Format::METHOD.to_string(), - project_id: remote.project_id, - prettier_path: remote.prettier_dir.to_string_lossy().to_string(), - }) - }) - .await - .context("prettier diff invoke")? - .diff - .map(deserialize_diff) - .context("missing diff after prettier diff invocation"), #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(buffer .read_with(cx, |buffer, cx| { @@ -445,28 +380,11 @@ impl Prettier { pub async fn clear_cache(&self) -> anyhow::Result<()> { match self { - Self::Local(local) => local + Self::Real(local) => local .server .request::(()) .await .context("prettier clear cache"), - Self::Remote(remote) => remote - .client - .request(proto::InvokePrettierForBuffer { - buffer_id: None, - worktree_id: self.worktree_id().map(|id| id as u64), - method: ClearCache::METHOD.to_string(), - project_id: remote.project_id, - prettier_path: remote.prettier_dir.to_string_lossy().to_string(), - }) - .await - .map(|response| { - debug_assert!( - response.diff.is_none(), - "Cleare cache invocation returned diff data" - ) - }) - .context("prettier invoke clear cache"), #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(()), } @@ -474,8 +392,7 @@ impl Prettier { pub fn server(&self) -> Option<&Arc> { match self { - Self::Local(local) => Some(&local.server), - Self::Remote(_) => None, + Self::Real(local) => Some(&local.server), #[cfg(any(test, feature = "test-support"))] Self::Test(_) => None, } @@ -483,8 +400,7 @@ impl Prettier { pub fn is_default(&self) -> bool { match self { - Self::Local(local) => local.default, - Self::Remote(_) => false, + Self::Real(local) => local.default, #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.default, } @@ -492,8 +408,7 @@ impl Prettier { pub fn prettier_dir(&self) -> &Path { match self { - Self::Local(local) => &local.prettier_dir, - Self::Remote(remote) => &remote.prettier_dir, + Self::Real(local) => &local.prettier_dir, #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => &test_prettier.prettier_dir, } @@ -501,8 +416,7 @@ impl Prettier { pub fn worktree_id(&self) -> Option { match self { - Self::Local(local) => local.worktree_id, - Self::Remote(remote) => remote.worktree_id, + Self::Real(local) => local.worktree_id, #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.worktree_id, } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e31ecca2577243debabf2326917d6e3b1363c406..f9e1b1ce9607993d4e4cf4db0fe90a82447a542e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -37,7 +37,7 @@ use language::{ point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, - serialize_anchor, serialize_diff, serialize_version, split_operations, + serialize_anchor, serialize_version, split_operations, }, range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, @@ -613,8 +613,6 @@ impl Project { client.add_model_request_handler(Self::handle_open_buffer_by_path); client.add_model_request_handler(Self::handle_save_buffer); client.add_model_message_handler(Self::handle_update_diff_base); - client.add_model_request_handler(Self::handle_prettier_instance_for_buffer); - client.add_model_request_handler(Self::handle_invoke_prettier); } pub fn local( @@ -8310,84 +8308,6 @@ impl Project { } } - async fn handle_prettier_instance_for_buffer( - this: ModelHandle, - envelope: TypedEnvelope, - _: Arc, - mut cx: AsyncAppContext, - ) -> anyhow::Result { - let prettier_instance_for_buffer_task = this.update(&mut cx, |this, cx| { - let buffer = this - .opened_buffers - .get(&envelope.payload.buffer_id) - .and_then(|buffer| buffer.upgrade(cx)) - .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; - anyhow::Ok(this.prettier_instance_for_buffer(&buffer, cx)) - })?; - - let prettier_path = match prettier_instance_for_buffer_task.await { - Some(prettier) => match prettier.await { - Ok(prettier) => Some(prettier.prettier_dir().display().to_string()), - Err(e) => { - anyhow::bail!("Failed to create prettier instance for remote request: {e:#}") - } - }, - None => None, - }; - Ok(proto::PrettierInstanceForBufferResponse { prettier_path }) - } - - async fn handle_invoke_prettier( - this: ModelHandle, - envelope: TypedEnvelope, - _: Arc, - mut cx: AsyncAppContext, - ) -> anyhow::Result { - let prettier = this - .read_with(&cx, |this, _| { - this.prettier_instances - .get(&( - envelope.payload.worktree_id.map(WorktreeId::from_proto), - PathBuf::from(&envelope.payload.prettier_path), - )) - .cloned() - }) - .with_context(|| { - format!( - "Missing prettier for worktree {:?} and path {:?}", - envelope.payload.worktree_id, envelope.payload.prettier_path, - ) - })? - .await; - let prettier = match prettier { - Ok(prettier) => prettier, - Err(e) => anyhow::bail!("Prettier instance failed to start: {e:#}"), - }; - - let buffer = this.update(&mut cx, |this, cx| { - envelope - .payload - .buffer_id - .and_then(|id| this.opened_buffers.get(&id)) - .and_then(|buffer| buffer.upgrade(cx)) - }); - - let buffer_path = buffer.as_ref().and_then(|buffer| { - buffer.read_with(&cx, |buffer, cx| { - File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) - }) - }); - - let diff = prettier - .invoke(buffer.as_ref(), buffer_path, &envelope.payload.method, &cx) - .await - .with_context(|| format!("prettier invoke method {}", &envelope.payload.method))?; - - Ok(proto::InvokePrettierForBufferResponse { - diff: diff.map(serialize_diff), - }) - } - fn prettier_instance_for_buffer( &mut self, buffer: &ModelHandle, @@ -8531,44 +8451,8 @@ impl Project { }); Some(new_prettier_task) }) - } else if let Some(project_id) = self.remote_id() { - let client = self.client.clone(); - let request = proto::PrettierInstanceForBuffer { - project_id, - buffer_id: buffer.remote_id(), - }; - cx.spawn(|this, mut cx| async move { - match client.request(request).await { - Ok(response) => { - response - .prettier_path - .map(PathBuf::from) - .map(|prettier_path| { - let prettier_task = Task::ready( - Ok(Arc::new(Prettier::remote( - project_id, - worktree_id.map(|id| id.to_usize()), - prettier_path.clone(), - client, - ))) - .map_err(Arc::new), - ) - .shared(); - this.update(&mut cx, |project, _| { - project.prettier_instances.insert( - (worktree_id, prettier_path), - prettier_task.clone(), - ); - }); - prettier_task - }) - } - Err(e) => { - log::error!("Prettier init remote request failed: {e:#}"); - None - } - } - }) + } else if self.remote_id().is_some() { + return Task::ready(None); } else { Task::ready(Some( Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(), diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index aae207f41cc68e695ce08c2831e7ded0b93334b6..302014ab8e57b4b5c7889c2dfa30835404103282 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -170,12 +170,7 @@ message Envelope { LinkChannel link_channel = 140; UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; - - PrettierInstanceForBuffer prettier_instance_for_buffer = 145; - PrettierInstanceForBufferResponse prettier_instance_for_buffer_response = 146; - InvokePrettierForBuffer invoke_prettier_for_buffer = 147; - InvokePrettierForBufferResponse invoke_prettier_for_buffer_response = 148; // Current max: 148 + MoveChannel move_channel = 142; // Current max: 144 } } @@ -1562,35 +1557,3 @@ message UpdateDiffBase { uint64 buffer_id = 2; optional string diff_base = 3; } - -message PrettierInstanceForBuffer { - uint64 project_id = 1; - uint64 buffer_id = 2; -} - -message PrettierInstanceForBufferResponse { - optional string prettier_path = 1; -} - -message InvokePrettierForBuffer { - uint64 project_id = 1; - string prettier_path = 2; - optional uint64 buffer_id = 3; - optional uint64 worktree_id = 4; - string method = 5; -} - -message InvokePrettierForBufferResponse { - optional Diff diff = 1; -} - -message Diff { - repeated VectorClockEntry version = 1; - LineEnding line_ending = 2; - repeated DiffEdit edits = 3; -} - -message DiffEdit { - Range range = 1; - string edit = 2; -} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index b51f1d8ae99c369188db2bd89748036c6f8f0f4f..e976a3d0b56d87e909a0a271f41e051a06d07618 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -273,10 +273,6 @@ messages!( (UpdateChannelBufferCollaborators, Foreground), (AckBufferOperation, Background), (AckChannelMessage, Background), - (PrettierInstanceForBuffer, Background), - (InvokePrettierForBuffer, Background), - (PrettierInstanceForBufferResponse, Background), - (InvokePrettierForBufferResponse, Background), ); request_messages!( @@ -354,8 +350,6 @@ request_messages!( (UpdateWorktree, Ack), (JoinChannelBuffer, JoinChannelBufferResponse), (LeaveChannelBuffer, Ack), - (PrettierInstanceForBuffer, PrettierInstanceForBufferResponse), - (InvokePrettierForBuffer, InvokePrettierForBufferResponse), ); entity_messages!( @@ -407,8 +401,6 @@ entity_messages!( UpdateWorktree, UpdateWorktreeSettings, UpdateDiffBase, - PrettierInstanceForBuffer, - InvokePrettierForBuffer, ); entity_messages!( From 09ef3ccf6745c4246af964845d72e9269bebab65 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 15:58:00 +0300 Subject: [PATCH 119/180] Fix tailwind prettier plugin discovery --- crates/prettier/src/prettier.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 240ee1e8959949a9c481b8613bf1e3b5befaef4b..c3811b567b590c632fd3f33ccf0df3c7b9d1b0f3 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -283,9 +283,11 @@ impl Prettier { let prettier_plugin_dir = prettier_node_modules.join(plugin_name); for possible_plugin_path in [ prettier_plugin_dir.join("dist").join("index.mjs"), + prettier_plugin_dir.join("dist").join("index.js"), + prettier_plugin_dir.join("dist").join("plugin.js"), prettier_plugin_dir.join("index.mjs"), - prettier_plugin_dir.join("plugin.js"), prettier_plugin_dir.join("index.js"), + prettier_plugin_dir.join("plugin.js"), prettier_plugin_dir, ] { if possible_plugin_path.is_file() { From 7aea95704eb53b35f2d956c30b829a1dc4e91829 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 16:15:42 +0300 Subject: [PATCH 120/180] Revert unnecessary style changes --- crates/fs/src/fs.rs | 3 +-- crates/language/src/buffer.rs | 4 ++-- crates/rpc/proto/zed.proto | 2 +- crates/rpc/src/proto.rs | 4 ++-- crates/zed/src/languages/php.rs | 1 - 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index bb5d6387e03a69db37c17248494e1edf9d2f4a5b..1bc8fa9a241b8d2bf2a1da48b23760fdca0c66a0 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -229,12 +229,11 @@ impl Fs for RealFs { } else { symlink_metadata }; - let file_type_metadata = metadata.file_type(); Ok(Some(Metadata { inode: metadata.ino(), mtime: metadata.modified().unwrap(), is_symlink, - is_dir: file_type_metadata.is_dir(), + is_dir: metadata.file_type().is_dir(), })) } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index eccfb0c05902c283b2f2acc06391e0cda8ca1724..207c41e7cdf21a8ec4d61232e071296184b52a5f 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -317,8 +317,8 @@ pub struct Chunk<'a> { pub struct Diff { pub(crate) base_version: clock::Global, - pub(crate) line_ending: LineEnding, - pub(crate) edits: Vec<(Range, Arc)>, + line_ending: LineEnding, + edits: Vec<(Range, Arc)>, } #[derive(Clone, Copy)] diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 302014ab8e57b4b5c7889c2dfa30835404103282..3501e70e6ac4191c5fe3141620e18cd8f7b8c32d 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -170,7 +170,7 @@ message Envelope { LinkChannel link_channel = 140; UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; // Current max: 144 + MoveChannel move_channel = 142; // current max: 144 } } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index e976a3d0b56d87e909a0a271f41e051a06d07618..f0d7937f6f9592b2574bff7b93d3097ceba24d94 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -349,7 +349,7 @@ request_messages!( (UpdateProject, Ack), (UpdateWorktree, Ack), (JoinChannelBuffer, JoinChannelBufferResponse), - (LeaveChannelBuffer, Ack), + (LeaveChannelBuffer, Ack) ); entity_messages!( @@ -400,7 +400,7 @@ entity_messages!( UpdateProjectCollaborator, UpdateWorktree, UpdateWorktreeSettings, - UpdateDiffBase, + UpdateDiffBase ); entity_messages!( diff --git a/crates/zed/src/languages/php.rs b/crates/zed/src/languages/php.rs index bf65deb642d0ca39f1806acdab13d98e0d5195a8..3096fd16e6b87764a0f9dd127a0dec6eaba0a77a 100644 --- a/crates/zed/src/languages/php.rs +++ b/crates/zed/src/languages/php.rs @@ -100,7 +100,6 @@ impl LspAdapter for IntelephenseLspAdapter { async fn initialization_options(&self) -> Option { None } - async fn language_ids(&self) -> HashMap { HashMap::from_iter([("PHP".into(), "php".into())]) } From ef73bf799c68425ea9f1a8ec2d6f9cecb367fba0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 16:26:28 +0300 Subject: [PATCH 121/180] Fix license issue --- crates/prettier/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 764bf0f07f070cf05e02f6d288645fd26141d3e5..997fa87126502575b83dcbd18d15c355f68999f3 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -2,9 +2,11 @@ name = "prettier" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/prettier.rs" +doctest = false [features] test-support = [] From 4688a94a54501d4604b8aad6de77aecc8d556c7d Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 12:11:27 -0400 Subject: [PATCH 122/180] Allow file links in markdown & filter links a bit aggressively --- crates/editor/src/editor.rs | 38 ++++++++++++++++----- crates/editor/src/element.rs | 10 ++++-- crates/editor/src/hover_popover.rs | 8 +++-- crates/language/src/markdown.rs | 41 ++++++++++++++++++----- crates/terminal_view/src/terminal_view.rs | 7 ++++ 5 files changed, 81 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9c1e0b3c1818f30d6676ee5fd3129bc055ff2ad5..0748a0fcf4683d9eb966139d9fd73cdd2b106738 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -122,6 +122,7 @@ pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); pub fn render_parsed_markdown( parsed: &language::ParsedMarkdown, editor_style: &EditorStyle, + workspace: Option>, cx: &mut ViewContext, ) -> Text { enum RenderedMarkdown {} @@ -147,15 +148,22 @@ pub fn render_parsed_markdown( region_id += 1; let region = parsed.regions[ix].clone(); - if let Some(url) = region.link_url { + if let Some(link) = region.link { cx.scene().push_cursor_region(CursorRegion { bounds, style: CursorStyle::PointingHand, }); cx.scene().push_mouse_region( MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds) - .on_down::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) + .on_down::(MouseButton::Left, move |_, _, cx| match &link { + markdown::Link::Web { url } => cx.platform().open_url(url), + markdown::Link::Path { path } => { + if let Some(workspace) = &workspace { + _ = workspace.update(cx, |workspace, cx| { + workspace.open_abs_path(path.clone(), false, cx).detach(); + }); + } + } }), ); } @@ -916,10 +924,11 @@ impl ContextMenu { &self, cursor_position: DisplayPoint, style: EditorStyle, + workspace: Option>, cx: &mut ViewContext, ) -> (DisplayPoint, AnyElement) { match self { - ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), + ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)), ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), } } @@ -1105,7 +1114,12 @@ impl CompletionsMenu { !self.matches.is_empty() } - fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { + fn render( + &self, + style: EditorStyle, + workspace: Option>, + cx: &mut ViewContext, + ) -> AnyElement { enum CompletionTag {} let widest_completion_ix = self @@ -1278,7 +1292,7 @@ impl CompletionsMenu { Flex::column() .scrollable::(0, None, cx) .with_child(render_parsed_markdown::( - parsed, &style, cx, + parsed, &style, workspace, cx, )) .contained() .with_style(style.autocomplete.alongside_docs_container) @@ -3140,6 +3154,7 @@ impl Editor { false }); } + fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option { let offset = position.to_offset(buffer); let (word_range, kind) = buffer.surrounding_word(offset); @@ -4215,9 +4230,14 @@ impl Editor { style: EditorStyle, cx: &mut ViewContext, ) -> Option<(DisplayPoint, AnyElement)> { - self.context_menu - .as_ref() - .map(|menu| menu.render(cursor_position, style, cx)) + self.context_menu.as_ref().map(|menu| { + menu.render( + cursor_position, + style, + self.workspace.as_ref().map(|(w, _)| w.clone()), + cx, + ) + }) } fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 924d66c21c5532fa79a2abb0af5ac1116cde463e..316e143413c648721420ea218adaf634a406ab48 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2439,9 +2439,13 @@ impl Element for EditorElement { } let visible_rows = start_row..start_row + line_layouts.len() as u32; - let mut hover = editor - .hover_state - .render(&snapshot, &style, visible_rows, cx); + let mut hover = editor.hover_state.render( + &snapshot, + &style, + visible_rows, + editor.workspace.as_ref().map(|(w, _)| w.clone()), + cx, + ); let mode = editor.mode; let mut fold_indicators = editor.render_fold_indicators( diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index e8901ad6c1678e199a5909992bf4ea9d5fb89809..00a307df68ab69cd5e4b188642e7182a0e6e3bee 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -9,7 +9,7 @@ use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, + AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, WeakViewHandle, }; use language::{ markdown, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, ParsedMarkdown, @@ -17,6 +17,7 @@ use language::{ use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; +use workspace::Workspace; pub const HOVER_DELAY_MILLIS: u64 = 350; pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; @@ -422,6 +423,7 @@ impl HoverState { snapshot: &EditorSnapshot, style: &EditorStyle, visible_rows: Range, + workspace: Option>, cx: &mut ViewContext, ) -> Option<(DisplayPoint, Vec>)> { // If there is a diagnostic, position the popovers based on that. @@ -451,7 +453,7 @@ impl HoverState { elements.push(diagnostic_popover.render(style, cx)); } if let Some(info_popover) = self.info_popover.as_mut() { - elements.push(info_popover.render(style, cx)); + elements.push(info_popover.render(style, workspace, cx)); } Some((point, elements)) @@ -470,6 +472,7 @@ impl InfoPopover { pub fn render( &mut self, style: &EditorStyle, + workspace: Option>, cx: &mut ViewContext, ) -> AnyElement { MouseEventHandler::new::(0, cx, |_, cx| { @@ -478,6 +481,7 @@ impl InfoPopover { .with_child(crate::render_parsed_markdown::( &self.parsed_content, style, + workspace, cx, )) .contained() diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index 8be15e81f614d699eaefd88e10c75e6e270cb53f..7f57eba3091b21faf482adc4eb2a595d9e855693 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -1,5 +1,5 @@ -use std::ops::Range; use std::sync::Arc; +use std::{ops::Range, path::PathBuf}; use crate::{HighlightId, Language, LanguageRegistry}; use gpui::fonts::{self, HighlightStyle, Weight}; @@ -58,7 +58,28 @@ pub struct MarkdownHighlightStyle { #[derive(Debug, Clone)] pub struct ParsedRegion { pub code: bool, - pub link_url: Option, + pub link: Option, +} + +#[derive(Debug, Clone)] +pub enum Link { + Web { url: String }, + Path { path: PathBuf }, +} + +impl Link { + fn identify(text: String) -> Option { + if text.starts_with("http") { + return Some(Link::Web { url: text }); + } + + let path = PathBuf::from(text); + if path.is_absolute() { + return Some(Link::Path { path }); + } + + None + } } pub async fn parse_markdown( @@ -115,17 +136,20 @@ pub async fn parse_markdown_block( text.push_str(t.as_ref()); let mut style = MarkdownHighlightStyle::default(); + if bold_depth > 0 { style.weight = Weight::BOLD; } + if italic_depth > 0 { style.italic = true; } - if let Some(link_url) = link_url.clone() { + + if let Some(link) = link_url.clone().and_then(|u| Link::identify(u)) { region_ranges.push(prev_len..text.len()); regions.push(ParsedRegion { - link_url: Some(link_url), code: false, + link: Some(link), }); style.underline = true; } @@ -151,7 +175,9 @@ pub async fn parse_markdown_block( Event::Code(t) => { text.push_str(t.as_ref()); region_ranges.push(prev_len..text.len()); - if link_url.is_some() { + + let link = link_url.clone().and_then(|u| Link::identify(u)); + if link.is_some() { highlights.push(( prev_len..text.len(), MarkdownHighlight::Style(MarkdownHighlightStyle { @@ -160,10 +186,7 @@ pub async fn parse_markdown_block( }), )); } - regions.push(ParsedRegion { - code: true, - link_url: link_url.clone(), - }); + regions.push(ParsedRegion { code: true, link }); } Event::Start(tag) => match tag { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index cd939b5604716a1b6c0f523db53acce735cc9ac1..5a13efd07a0da4b8490444b3322bfc4600b70e17 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -150,11 +150,14 @@ impl TerminalView { cx.notify(); cx.emit(Event::Wakeup); } + Event::Bell => { this.has_bell = true; cx.emit(Event::Wakeup); } + Event::BlinkChanged => this.blinking_on = !this.blinking_on, + Event::TitleChanged => { if let Some(foreground_info) = &this.terminal().read(cx).foreground_process_info { let cwd = foreground_info.cwd.clone(); @@ -171,6 +174,7 @@ impl TerminalView { .detach(); } } + Event::NewNavigationTarget(maybe_navigation_target) => { this.can_navigate_to_selected_word = match maybe_navigation_target { Some(MaybeNavigationTarget::Url(_)) => true, @@ -180,8 +184,10 @@ impl TerminalView { None => false, } } + Event::Open(maybe_navigation_target) => match maybe_navigation_target { MaybeNavigationTarget::Url(url) => cx.platform().open_url(url), + MaybeNavigationTarget::PathLike(maybe_path) => { if !this.can_navigate_to_selected_word { return; @@ -246,6 +252,7 @@ impl TerminalView { } } }, + _ => cx.emit(event.clone()), }) .detach(); From 85332eacbd861847582526cec0642f2d76e88944 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 13:23:26 -0400 Subject: [PATCH 123/180] Race completion filter w/completion request & make not block UI --- crates/editor/src/editor.rs | 107 +++++++++++++++++++----------- crates/editor/src/editor_tests.rs | 14 ++-- crates/editor/src/element.rs | 2 +- 3 files changed, 76 insertions(+), 47 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0748a0fcf4683d9eb966139d9fd73cdd2b106738..d7ef82da3613999a61a07c4a6c66313f3434e25e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -656,7 +656,7 @@ pub struct Editor { background_highlights: BTreeMap, inlay_background_highlights: TreeMap, InlayBackgroundHighlight>, nav_history: Option, - context_menu: Option, + context_menu: RwLock>, mouse_context_menu: ViewHandle, completion_tasks: Vec<(CompletionId, Task>)>, next_completion_id: CompletionId, @@ -934,12 +934,13 @@ impl ContextMenu { } } +#[derive(Clone)] struct CompletionsMenu { id: CompletionId, initial_position: Anchor, buffer: ModelHandle, completions: Arc>>, - match_candidates: Vec, + match_candidates: Arc<[StringMatchCandidate]>, matches: Arc<[StringMatch]>, selected_item: usize, list: UniformListState, @@ -1333,13 +1334,13 @@ impl CompletionsMenu { .collect() }; - //Remove all candidates where the query's start does not match the start of any word in the candidate + // Remove all candidates where the query's start does not match the start of any word in the candidate if let Some(query) = query { if let Some(query_start) = query.chars().next() { matches.retain(|string_match| { split_words(&string_match.string).any(|word| { - //Check that the first codepoint of the word as lowercase matches the first - //codepoint of the query as lowercase + // Check that the first codepoint of the word as lowercase matches the first + // codepoint of the query as lowercase word.chars() .flat_map(|codepoint| codepoint.to_lowercase()) .zip(query_start.to_lowercase()) @@ -1805,7 +1806,7 @@ impl Editor { background_highlights: Default::default(), inlay_background_highlights: Default::default(), nav_history: None, - context_menu: None, + context_menu: RwLock::new(None), mouse_context_menu: cx .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)), completion_tasks: Default::default(), @@ -2100,10 +2101,12 @@ impl Editor { if local { let new_cursor_position = self.selections.newest_anchor().head(); - let completion_menu = match self.context_menu.as_mut() { + let mut context_menu = self.context_menu.write(); + let completion_menu = match context_menu.as_ref() { Some(ContextMenu::Completions(menu)) => Some(menu), + _ => { - self.context_menu.take(); + *context_menu = None; None } }; @@ -2115,13 +2118,39 @@ impl Editor { if kind == Some(CharKind::Word) && word_range.to_inclusive().contains(&cursor_position) { + let mut completion_menu = completion_menu.clone(); + drop(context_menu); + let query = Self::completion_query(buffer, cursor_position); - cx.background() - .block(completion_menu.filter(query.as_deref(), cx.background().clone())); + cx.spawn(move |this, mut cx| async move { + completion_menu + .filter(query.as_deref(), cx.background().clone()) + .await; + + this.update(&mut cx, |this, cx| { + let mut context_menu = this.context_menu.write(); + let Some(ContextMenu::Completions(menu)) = context_menu.as_ref() else { + return; + }; + + if menu.id > completion_menu.id { + return; + } + + *context_menu = Some(ContextMenu::Completions(completion_menu)); + drop(context_menu); + cx.notify(); + }) + }) + .detach(); + self.show_completions(&ShowCompletions, cx); } else { + drop(context_menu); self.hide_context_menu(cx); } + } else { + drop(context_menu); } hide_hover(self, cx); @@ -3432,23 +3461,31 @@ impl Editor { this.update(&mut cx, |this, cx| { this.completion_tasks.retain(|(task_id, _)| *task_id > id); - match this.context_menu.as_ref() { + let mut context_menu = this.context_menu.write(); + match context_menu.as_ref() { None => {} + Some(ContextMenu::Completions(prev_menu)) => { if prev_menu.id > id { return; } } + _ => return, } if this.focused && menu.is_some() { let menu = menu.unwrap(); - this.show_context_menu(ContextMenu::Completions(menu), cx); + *context_menu = Some(ContextMenu::Completions(menu)); + drop(context_menu); + this.completion_tasks.clear(); + this.discard_copilot_suggestion(cx); + cx.notify(); } else if this.completion_tasks.is_empty() { // If there are no more completion tasks and the last menu was // empty, we should hide it. If it was already hidden, we should // also show the copilot suggestion when available. + drop(context_menu); if this.hide_context_menu(cx).is_none() { this.update_visible_copilot_suggestion(cx); } @@ -3593,14 +3630,13 @@ impl Editor { } pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext) { - if matches!( - self.context_menu.as_ref(), - Some(ContextMenu::CodeActions(_)) - ) { - self.context_menu.take(); + let mut context_menu = self.context_menu.write(); + if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) { + *context_menu = None; cx.notify(); return; } + drop(context_menu); let deployed_from_indicator = action.deployed_from_indicator; let mut task = self.code_actions_task.take(); @@ -3613,16 +3649,16 @@ impl Editor { this.update(&mut cx, |this, cx| { if this.focused { if let Some((buffer, actions)) = this.available_code_actions.clone() { - this.show_context_menu( - ContextMenu::CodeActions(CodeActionsMenu { + this.completion_tasks.clear(); + this.discard_copilot_suggestion(cx); + *this.context_menu.write() = + Some(ContextMenu::CodeActions(CodeActionsMenu { buffer, actions, selected_item: Default::default(), list: Default::default(), deployed_from_indicator, - }), - cx, - ); + })); } } })?; @@ -4086,7 +4122,7 @@ impl Editor { let selection = self.selections.newest_anchor(); let cursor = selection.head(); - if self.context_menu.is_some() + if self.context_menu.read().is_some() || !self.completion_tasks.is_empty() || selection.start != selection.end { @@ -4220,6 +4256,7 @@ impl Editor { pub fn context_menu_visible(&self) -> bool { self.context_menu + .read() .as_ref() .map_or(false, |menu| menu.visible()) } @@ -4230,7 +4267,7 @@ impl Editor { style: EditorStyle, cx: &mut ViewContext, ) -> Option<(DisplayPoint, AnyElement)> { - self.context_menu.as_ref().map(|menu| { + self.context_menu.read().as_ref().map(|menu| { menu.render( cursor_position, style, @@ -4240,19 +4277,10 @@ impl Editor { }) } - fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { - if !matches!(menu, ContextMenu::Completions(_)) { - self.completion_tasks.clear(); - } - self.context_menu = Some(menu); - self.discard_copilot_suggestion(cx); - cx.notify(); - } - fn hide_context_menu(&mut self, cx: &mut ViewContext) -> Option { cx.notify(); self.completion_tasks.clear(); - let context_menu = self.context_menu.take(); + let context_menu = self.context_menu.write().take(); if context_menu.is_some() { self.update_visible_copilot_suggestion(cx); } @@ -5604,6 +5632,7 @@ impl Editor { if self .context_menu + .write() .as_mut() .map(|menu| menu.select_last(self.project.as_ref(), cx)) .unwrap_or(false) @@ -5648,25 +5677,25 @@ impl Editor { } pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_first(self.project.as_ref(), cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_prev(self.project.as_ref(), cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_next(self.project.as_ref(), cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_last(self.project.as_ref(), cx); } } @@ -9164,7 +9193,7 @@ impl View for Editor { keymap.add_identifier("renaming"); } if self.context_menu_visible() { - match self.context_menu.as_ref() { + match self.context_menu.read().as_ref() { Some(ContextMenu::Completions(_)) => { keymap.add_identifier("menu"); keymap.add_identifier("showing_completions") diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index dee27e0121256edaa38a90bb175d436eba768f96..4be29ea084b7d81edb1bf766823d4b386e5ac694 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5430,9 +5430,9 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { additional edit "}); cx.simulate_keystroke(" "); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.simulate_keystroke("s"); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.assert_editor_state(indoc! {" one.second_completion @@ -5494,12 +5494,12 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { }); cx.set_state("editorˇ"); cx.simulate_keystroke("."); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.simulate_keystroke("c"); cx.simulate_keystroke("l"); cx.simulate_keystroke("o"); cx.assert_editor_state("editor.cloˇ"); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.update_editor(|editor, cx| { editor.show_completions(&ShowCompletions, cx); }); @@ -7788,7 +7788,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: cx.simulate_keystroke("-"); cx.foreground().run_until_parked(); cx.update_editor(|editor, _| { - if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { assert_eq!( menu.matches.iter().map(|m| &m.string).collect::>(), &["bg-red", "bg-blue", "bg-yellow"] @@ -7801,7 +7801,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: cx.simulate_keystroke("l"); cx.foreground().run_until_parked(); cx.update_editor(|editor, _| { - if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { assert_eq!( menu.matches.iter().map(|m| &m.string).collect::>(), &["bg-blue", "bg-yellow"] @@ -7817,7 +7817,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: cx.simulate_keystroke("l"); cx.foreground().run_until_parked(); cx.update_editor(|editor, _| { - if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { assert_eq!( menu.matches.iter().map(|m| &m.string).collect::>(), &["bg-yellow"] diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 316e143413c648721420ea218adaf634a406ab48..00c8508b6c3800662baf4728bf94c3e167c76dbf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2428,7 +2428,7 @@ impl Element for EditorElement { } let active = matches!( - editor.context_menu, + editor.context_menu.read().as_ref(), Some(crate::ContextMenu::CodeActions(_)) ); From 2e5461ee4de8f296c116e00a0c70efaacd376227 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Oct 2023 11:55:39 -0700 Subject: [PATCH 124/180] Exclude disconnected channel views from following messages --- crates/collab_ui/src/channel_view.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index b2e65eb2fa1bd5e92ddf5436115b938958f1383e..817e9fcb4ed208333dedbb46ec756922b6999c6a 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -285,10 +285,14 @@ impl FollowableItem for ChannelView { } fn to_state_proto(&self, cx: &AppContext) -> Option { - let channel = self.channel_buffer.read(cx).channel(); + let channel_buffer = self.channel_buffer.read(cx); + if !channel_buffer.is_connected() { + return None; + } + Some(proto::view::Variant::ChannelView( proto::view::ChannelView { - channel_id: channel.id, + channel_id: channel_buffer.channel().id, editor: if let Some(proto::view::Variant::Editor(proto)) = self.editor.read(cx).to_state_proto(cx) { From 540436a1f9892b9f3c6aafbf21d525335880d8bc Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Oct 2023 21:57:46 -0600 Subject: [PATCH 125/180] Push role refactoring through RPC/client --- .cargo/config.toml | 2 +- crates/channel/src/channel_store.rs | 24 ++++---- crates/channel/src/channel_store_tests.rs | 4 +- crates/collab/src/db/ids.rs | 28 +++++++++ crates/collab/src/db/queries/channels.rs | 18 ++++-- crates/collab/src/db/tests/channel_tests.rs | 10 ++-- crates/collab/src/rpc.rs | 46 +++++++-------- crates/collab/src/tests/channel_tests.rs | 43 +++++++++++--- crates/collab/src/tests/test_server.rs | 11 +++- .../src/collab_panel/channel_modal.rs | 57 ++++++++++++------- crates/rpc/proto/zed.proto | 20 ++++--- crates/rpc/src/proto.rs | 4 +- 12 files changed, 178 insertions(+), 89 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 9da6b3be080072d89d16a199e2d60d527eeacd07..e22bdb0f2c70a1ffda714674253cc533e9e7c1d1 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0"] +rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 2a2fa454f2b4435a806d90304940a4ce61450d09..64c76a0a390c3dc5ae6ba1ef402b16d8b58efdb8 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -9,7 +9,7 @@ use db::RELEASE_CHANNEL; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle}; use rpc::{ - proto::{self, ChannelEdge, ChannelPermission}, + proto::{self, ChannelEdge, ChannelPermission, ChannelRole}, TypedEnvelope, }; use serde_derive::{Deserialize, Serialize}; @@ -79,7 +79,7 @@ pub struct ChannelPath(Arc<[ChannelId]>); pub struct ChannelMembership { pub user: Arc, pub kind: proto::channel_member::Kind, - pub admin: bool, + pub role: proto::ChannelRole, } pub enum ChannelEvent { @@ -436,7 +436,7 @@ impl ChannelStore { insert_edge: parent_edge, channel_permissions: vec![ChannelPermission { channel_id, - is_admin: true, + role: ChannelRole::Admin.into(), }], ..Default::default() }, @@ -512,7 +512,7 @@ impl ChannelStore { &mut self, channel_id: ChannelId, user_id: UserId, - admin: bool, + role: proto::ChannelRole, cx: &mut ModelContext, ) -> Task> { if !self.outgoing_invites.insert((channel_id, user_id)) { @@ -526,7 +526,7 @@ impl ChannelStore { .request(proto::InviteChannelMember { channel_id, user_id, - admin, + role: role.into(), }) .await; @@ -570,11 +570,11 @@ impl ChannelStore { }) } - pub fn set_member_admin( + pub fn set_member_role( &mut self, channel_id: ChannelId, user_id: UserId, - admin: bool, + role: proto::ChannelRole, cx: &mut ModelContext, ) -> Task> { if !self.outgoing_invites.insert((channel_id, user_id)) { @@ -585,10 +585,10 @@ impl ChannelStore { let client = self.client.clone(); cx.spawn(|this, mut cx| async move { let result = client - .request(proto::SetChannelMemberAdmin { + .request(proto::SetChannelMemberRole { channel_id, user_id, - admin, + role: role.into(), }) .await; @@ -676,8 +676,8 @@ impl ChannelStore { .filter_map(|(user, member)| { Some(ChannelMembership { user, - admin: member.admin, - kind: proto::channel_member::Kind::from_i32(member.kind)?, + role: member.role(), + kind: member.kind(), }) }) .collect()) @@ -935,7 +935,7 @@ impl ChannelStore { } for permission in payload.channel_permissions { - if permission.is_admin { + if permission.role() == proto::ChannelRole::Admin { self.channels_with_admin_privileges .insert(permission.channel_id); } else { diff --git a/crates/channel/src/channel_store_tests.rs b/crates/channel/src/channel_store_tests.rs index 9303a52092e13a1592e4a5786c4ed636b969cb73..f8828159bdc5049adc5b789383ee72c3eca8629c 100644 --- a/crates/channel/src/channel_store_tests.rs +++ b/crates/channel/src/channel_store_tests.rs @@ -26,7 +26,7 @@ fn test_update_channels(cx: &mut AppContext) { ], channel_permissions: vec![proto::ChannelPermission { channel_id: 1, - is_admin: true, + role: proto::ChannelRole::Admin.into(), }], ..Default::default() }, @@ -114,7 +114,7 @@ fn test_dangling_channel_paths(cx: &mut AppContext) { ], channel_permissions: vec![proto::ChannelPermission { channel_id: 0, - is_admin: true, + role: proto::ChannelRole::Admin.into(), }], ..Default::default() }, diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 747e3a7d3b17642191a0e1067c11cf1d1c3e205c..946702f36ce5eee521741d4556ea201d0966ab34 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -1,4 +1,5 @@ use crate::Result; +use rpc::proto; use sea_orm::{entity::prelude::*, DbErr}; use serde::{Deserialize, Serialize}; @@ -91,3 +92,30 @@ pub enum ChannelRole { #[sea_orm(string_value = "guest")] Guest, } + +impl From for ChannelRole { + fn from(value: proto::ChannelRole) -> Self { + match value { + proto::ChannelRole::Admin => ChannelRole::Admin, + proto::ChannelRole::Member => ChannelRole::Member, + proto::ChannelRole::Guest => ChannelRole::Guest, + } + } +} + +impl Into for ChannelRole { + fn into(self) -> proto::ChannelRole { + match self { + ChannelRole::Admin => proto::ChannelRole::Admin, + ChannelRole::Member => proto::ChannelRole::Member, + ChannelRole::Guest => proto::ChannelRole::Guest, + } + } +} + +impl Into for ChannelRole { + fn into(self) -> i32 { + let proto: proto::ChannelRole = self.into(); + proto.into() + } +} diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 0fe78209164be20ba0f2f618a6be7190beed1c26..5c96955eba5cb88d55abe35506c6867466029eee 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -564,13 +564,18 @@ impl Database { (false, true) => proto::channel_member::Kind::AncestorMember, (false, false) => continue, }; + let channel_role = channel_role.unwrap_or(if is_admin { + ChannelRole::Admin + } else { + ChannelRole::Member + }); let user_id = user_id.to_proto(); let kind = kind.into(); if let Some(last_row) = rows.last_mut() { if last_row.user_id == user_id { if is_direct_member { last_row.kind = kind; - last_row.admin = channel_role == Some(ChannelRole::Admin) || is_admin; + last_row.role = channel_role.into() } continue; } @@ -578,7 +583,7 @@ impl Database { rows.push(proto::ChannelMember { user_id, kind, - admin: channel_role == Some(ChannelRole::Admin) || is_admin, + role: channel_role.into(), }); } @@ -851,10 +856,11 @@ impl Database { &self, user: UserId, channel: ChannelId, - to: ChannelId, + new_parent: ChannelId, tx: &DatabaseTransaction, ) -> Result { - self.check_user_is_channel_admin(to, user, &*tx).await?; + self.check_user_is_channel_admin(new_parent, user, &*tx) + .await?; let paths = channel_path::Entity::find() .filter(channel_path::Column::IdPath.like(&format!("%/{}/%", channel))) @@ -872,7 +878,7 @@ impl Database { } let paths_to_new_parent = channel_path::Entity::find() - .filter(channel_path::Column::ChannelId.eq(to)) + .filter(channel_path::Column::ChannelId.eq(new_parent)) .all(tx) .await?; @@ -906,7 +912,7 @@ impl Database { if let Some(channel) = channel_descendants.get_mut(&channel) { // Remove the other parents channel.clear(); - channel.insert(to); + channel.insert(new_parent); } let channels = self diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index ed4b9e061b0ee7ab1181431b24ffdb1cd88ba5c2..90b3a0cd2e6e8f98c1b258cb0d603d7c9cfddd68 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -328,17 +328,17 @@ async fn test_channel_invites(db: &Arc) { proto::ChannelMember { user_id: user_1.to_proto(), kind: proto::channel_member::Kind::Member.into(), - admin: true, + role: proto::ChannelRole::Admin.into(), }, proto::ChannelMember { user_id: user_2.to_proto(), kind: proto::channel_member::Kind::Invitee.into(), - admin: false, + role: proto::ChannelRole::Member.into(), }, proto::ChannelMember { user_id: user_3.to_proto(), kind: proto::channel_member::Kind::Invitee.into(), - admin: true, + role: proto::ChannelRole::Admin.into(), }, ] ); @@ -362,12 +362,12 @@ async fn test_channel_invites(db: &Arc) { proto::ChannelMember { user_id: user_1.to_proto(), kind: proto::channel_member::Kind::Member.into(), - admin: true, + role: proto::ChannelRole::Admin.into(), }, proto::ChannelMember { user_id: user_2.to_proto(), kind: proto::channel_member::Kind::AncestorMember.into(), - admin: false, + role: proto::ChannelRole::Member.into(), }, ] ); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index f13f482c2b3b156401fa20a3b8a8a8a7f51c2f5f..b05421e9601f293501448b935db357e94ebc2dee 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3,8 +3,8 @@ mod connection_pool; use crate::{ auth, db::{ - self, BufferId, ChannelId, ChannelRole, ChannelsForUser, Database, MessageId, ProjectId, - RoomId, ServerId, User, UserId, + self, BufferId, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId, + ServerId, User, UserId, }, executor::Executor, AppState, Result, @@ -254,7 +254,7 @@ impl Server { .add_request_handler(delete_channel) .add_request_handler(invite_channel_member) .add_request_handler(remove_channel_member) - .add_request_handler(set_channel_member_admin) + .add_request_handler(set_channel_member_role) .add_request_handler(rename_channel) .add_request_handler(join_channel_buffer) .add_request_handler(leave_channel_buffer) @@ -2282,13 +2282,13 @@ async fn invite_channel_member( let db = session.db().await; let channel_id = ChannelId::from_proto(request.channel_id); let invitee_id = UserId::from_proto(request.user_id); - let role = if request.admin { - ChannelRole::Admin - } else { - ChannelRole::Member - }; - db.invite_channel_member(channel_id, invitee_id, session.user_id, role) - .await?; + db.invite_channel_member( + channel_id, + invitee_id, + session.user_id, + request.role().into(), + ) + .await?; let (channel, _) = db .get_channel(channel_id, session.user_id) @@ -2339,21 +2339,21 @@ async fn remove_channel_member( Ok(()) } -async fn set_channel_member_admin( - request: proto::SetChannelMemberAdmin, - response: Response, +async fn set_channel_member_role( + request: proto::SetChannelMemberRole, + response: Response, session: Session, ) -> Result<()> { let db = session.db().await; let channel_id = ChannelId::from_proto(request.channel_id); let member_id = UserId::from_proto(request.user_id); - let role = if request.admin { - ChannelRole::Admin - } else { - ChannelRole::Member - }; - db.set_channel_member_role(channel_id, session.user_id, member_id, role) - .await?; + db.set_channel_member_role( + channel_id, + session.user_id, + member_id, + request.role().into(), + ) + .await?; let (channel, has_accepted) = db .get_channel(channel_id, member_id) @@ -2364,7 +2364,7 @@ async fn set_channel_member_admin( if has_accepted { update.channel_permissions.push(proto::ChannelPermission { channel_id: channel.id.to_proto(), - is_admin: request.admin, + role: request.role, }); } @@ -2603,7 +2603,7 @@ async fn respond_to_channel_invite( .into_iter() .map(|channel_id| proto::ChannelPermission { channel_id: channel_id.to_proto(), - is_admin: true, + role: proto::ChannelRole::Admin.into(), }), ); } @@ -3106,7 +3106,7 @@ fn build_initial_channels_update( .into_iter() .map(|id| proto::ChannelPermission { channel_id: id.to_proto(), - is_admin: true, + role: proto::ChannelRole::Admin.into(), }), ); diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 7cfcce832b4cd4e05a953156828e517ef85af9fe..bc814d06a26721963fd82b345cb7dc85aee6771a 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -68,7 +68,12 @@ async fn test_core_channels( .update(cx_a, |store, cx| { assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap())); - let invite = store.invite_member(channel_a_id, client_b.user_id().unwrap(), false, cx); + let invite = store.invite_member( + channel_a_id, + client_b.user_id().unwrap(), + proto::ChannelRole::Member, + cx, + ); // Make sure we're synchronously storing the pending invite assert!(store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap())); @@ -103,12 +108,12 @@ async fn test_core_channels( &[ ( client_a.user_id().unwrap(), - true, + proto::ChannelRole::Admin, proto::channel_member::Kind::Member, ), ( client_b.user_id().unwrap(), - false, + proto::ChannelRole::Member, proto::channel_member::Kind::Invitee, ), ], @@ -183,7 +188,12 @@ async fn test_core_channels( client_a .channel_store() .update(cx_a, |store, cx| { - store.set_member_admin(channel_a_id, client_b.user_id().unwrap(), true, cx) + store.set_member_role( + channel_a_id, + client_b.user_id().unwrap(), + proto::ChannelRole::Admin, + cx, + ) }) .await .unwrap(); @@ -305,12 +315,12 @@ fn assert_participants_eq(participants: &[Arc], expected_partitipants: &[u #[track_caller] fn assert_members_eq( members: &[ChannelMembership], - expected_members: &[(u64, bool, proto::channel_member::Kind)], + expected_members: &[(u64, proto::ChannelRole, proto::channel_member::Kind)], ) { assert_eq!( members .iter() - .map(|member| (member.user.id, member.admin, member.kind)) + .map(|member| (member.user.id, member.role, member.kind)) .collect::>(), expected_members ); @@ -611,7 +621,12 @@ async fn test_permissions_update_while_invited( client_a .channel_store() .update(cx_a, |channel_store, cx| { - channel_store.invite_member(rust_id, client_b.user_id().unwrap(), false, cx) + channel_store.invite_member( + rust_id, + client_b.user_id().unwrap(), + proto::ChannelRole::Member, + cx, + ) }) .await .unwrap(); @@ -634,7 +649,12 @@ async fn test_permissions_update_while_invited( client_a .channel_store() .update(cx_a, |channel_store, cx| { - channel_store.set_member_admin(rust_id, client_b.user_id().unwrap(), true, cx) + channel_store.set_member_role( + rust_id, + client_b.user_id().unwrap(), + proto::ChannelRole::Admin, + cx, + ) }) .await .unwrap(); @@ -803,7 +823,12 @@ async fn test_lost_channel_creation( client_a .channel_store() .update(cx_a, |channel_store, cx| { - channel_store.invite_member(channel_id, client_b.user_id().unwrap(), false, cx) + channel_store.invite_member( + channel_id, + client_b.user_id().unwrap(), + proto::ChannelRole::Member, + cx, + ) }) .await .unwrap(); diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 2e13874125472cd53b68d4d688c90ca02569615a..54a59c0c000011a329f64ed5fab36a1a091d309c 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -17,7 +17,7 @@ use gpui::{executor::Deterministic, ModelHandle, Task, TestAppContext, WindowHan use language::LanguageRegistry; use parking_lot::Mutex; use project::{Project, WorktreeId}; -use rpc::RECEIVE_TIMEOUT; +use rpc::{proto::ChannelRole, RECEIVE_TIMEOUT}; use settings::SettingsStore; use std::{ cell::{Ref, RefCell, RefMut}, @@ -325,7 +325,7 @@ impl TestServer { channel_store.invite_member( channel_id, member_client.user_id().unwrap(), - false, + ChannelRole::Member, cx, ) }) @@ -613,7 +613,12 @@ impl TestClient { cx_self .read(ChannelStore::global) .update(cx_self, |channel_store, cx| { - channel_store.invite_member(channel, other_client.user_id().unwrap(), true, cx) + channel_store.invite_member( + channel, + other_client.user_id().unwrap(), + ChannelRole::Admin, + cx, + ) }) .await .unwrap(); diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 4c811a2df547dc78e0a602ae2002a4e9dbeb4e46..16d5e48f452b7acd4b220076ccb73ac7c8308854 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -1,5 +1,8 @@ use channel::{ChannelId, ChannelMembership, ChannelStore}; -use client::{proto, User, UserId, UserStore}; +use client::{ + proto::{self, ChannelRole}, + User, UserId, UserStore, +}; use context_menu::{ContextMenu, ContextMenuItem}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ @@ -343,9 +346,11 @@ impl PickerDelegate for ChannelModalDelegate { } fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { - if let Some((selected_user, admin)) = self.user_at_index(self.selected_index) { + if let Some((selected_user, role)) = self.user_at_index(self.selected_index) { match self.mode { - Mode::ManageMembers => self.show_context_menu(admin.unwrap_or(false), cx), + Mode::ManageMembers => { + self.show_context_menu(role.unwrap_or(ChannelRole::Member), cx) + } Mode::InviteMembers => match self.member_status(selected_user.id, cx) { Some(proto::channel_member::Kind::Invitee) => { self.remove_selected_member(cx); @@ -373,7 +378,7 @@ impl PickerDelegate for ChannelModalDelegate { let full_theme = &theme::current(cx); let theme = &full_theme.collab_panel.channel_modal; let tabbed_modal = &full_theme.collab_panel.tabbed_modal; - let (user, admin) = self.user_at_index(ix).unwrap(); + let (user, role) = self.user_at_index(ix).unwrap(); let request_status = self.member_status(user.id, cx); let style = tabbed_modal @@ -409,15 +414,25 @@ impl PickerDelegate for ChannelModalDelegate { }, ) }) - .with_children(admin.and_then(|admin| { - (in_manage && admin).then(|| { + .with_children(if in_manage && role == Some(ChannelRole::Admin) { + Some( Label::new("Admin", theme.member_tag.text.clone()) .contained() .with_style(theme.member_tag.container) .aligned() - .left() - }) - })) + .left(), + ) + } else if in_manage && role == Some(ChannelRole::Guest) { + Some( + Label::new("Guest", theme.member_tag.text.clone()) + .contained() + .with_style(theme.member_tag.container) + .aligned() + .left(), + ) + } else { + None + }) .with_children({ let svg = match self.mode { Mode::ManageMembers => Some( @@ -502,13 +517,13 @@ impl ChannelModalDelegate { }) } - fn user_at_index(&self, ix: usize) -> Option<(Arc, Option)> { + fn user_at_index(&self, ix: usize) -> Option<(Arc, Option)> { match self.mode { Mode::ManageMembers => self.matching_member_indices.get(ix).and_then(|ix| { let channel_membership = self.members.get(*ix)?; Some(( channel_membership.user.clone(), - Some(channel_membership.admin), + Some(channel_membership.role), )) }), Mode::InviteMembers => Some((self.matching_users.get(ix).cloned()?, None)), @@ -516,17 +531,21 @@ impl ChannelModalDelegate { } fn toggle_selected_member_admin(&mut self, cx: &mut ViewContext>) -> Option<()> { - let (user, admin) = self.user_at_index(self.selected_index)?; - let admin = !admin.unwrap_or(false); + let (user, role) = self.user_at_index(self.selected_index)?; + let new_role = if role == Some(ChannelRole::Admin) { + ChannelRole::Member + } else { + ChannelRole::Admin + }; let update = self.channel_store.update(cx, |store, cx| { - store.set_member_admin(self.channel_id, user.id, admin, cx) + store.set_member_role(self.channel_id, user.id, new_role, cx) }); cx.spawn(|picker, mut cx| async move { update.await?; picker.update(&mut cx, |picker, cx| { let this = picker.delegate_mut(); if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user.id) { - member.admin = admin; + member.role = new_role; } cx.focus_self(); cx.notify(); @@ -572,7 +591,7 @@ impl ChannelModalDelegate { fn invite_member(&mut self, user: Arc, cx: &mut ViewContext>) { let invite_member = self.channel_store.update(cx, |store, cx| { - store.invite_member(self.channel_id, user.id, false, cx) + store.invite_member(self.channel_id, user.id, ChannelRole::Member, cx) }); cx.spawn(|this, mut cx| async move { @@ -582,7 +601,7 @@ impl ChannelModalDelegate { this.delegate_mut().members.push(ChannelMembership { user, kind: proto::channel_member::Kind::Invitee, - admin: false, + role: ChannelRole::Member, }); cx.notify(); }) @@ -590,7 +609,7 @@ impl ChannelModalDelegate { .detach_and_log_err(cx); } - fn show_context_menu(&mut self, user_is_admin: bool, cx: &mut ViewContext>) { + fn show_context_menu(&mut self, role: ChannelRole, cx: &mut ViewContext>) { self.context_menu.update(cx, |context_menu, cx| { context_menu.show( Default::default(), @@ -598,7 +617,7 @@ impl ChannelModalDelegate { vec![ ContextMenuItem::action("Remove", RemoveMember), ContextMenuItem::action( - if user_is_admin { + if role == ChannelRole::Admin { "Make non-admin" } else { "Make admin" diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 3501e70e6ac4191c5fe3141620e18cd8f7b8c32d..dbd28bcf5de29b39a5d9eb2b94f97109ef200c76 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -144,7 +144,7 @@ message Envelope { DeleteChannel delete_channel = 118; GetChannelMembers get_channel_members = 119; GetChannelMembersResponse get_channel_members_response = 120; - SetChannelMemberAdmin set_channel_member_admin = 121; + SetChannelMemberRole set_channel_member_role = 145; RenameChannel rename_channel = 122; RenameChannelResponse rename_channel_response = 123; @@ -170,7 +170,7 @@ message Envelope { LinkChannel link_channel = 140; UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; // current max: 144 + MoveChannel move_channel = 142; // current max: 145 } } @@ -979,7 +979,7 @@ message ChannelEdge { message ChannelPermission { uint64 channel_id = 1; - bool is_admin = 2; + ChannelRole role = 3; } message ChannelParticipants { @@ -1005,8 +1005,8 @@ message GetChannelMembersResponse { message ChannelMember { uint64 user_id = 1; - bool admin = 2; Kind kind = 3; + ChannelRole role = 4; enum Kind { Member = 0; @@ -1028,7 +1028,7 @@ message CreateChannelResponse { message InviteChannelMember { uint64 channel_id = 1; uint64 user_id = 2; - bool admin = 3; + ChannelRole role = 4; } message RemoveChannelMember { @@ -1036,10 +1036,16 @@ message RemoveChannelMember { uint64 user_id = 2; } -message SetChannelMemberAdmin { +enum ChannelRole { + Admin = 0; + Member = 1; + Guest = 2; +} + +message SetChannelMemberRole { uint64 channel_id = 1; uint64 user_id = 2; - bool admin = 3; + ChannelRole role = 3; } message RenameChannel { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index f0d7937f6f9592b2574bff7b93d3097ceba24d94..57292a52ca21db846ca426945b40b2dd08738370 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -230,7 +230,7 @@ messages!( (SaveBuffer, Foreground), (RenameChannel, Foreground), (RenameChannelResponse, Foreground), - (SetChannelMemberAdmin, Foreground), + (SetChannelMemberRole, Foreground), (SearchProject, Background), (SearchProjectResponse, Background), (ShareProject, Foreground), @@ -326,7 +326,7 @@ request_messages!( (RemoveContact, Ack), (RespondToContactRequest, Ack), (RespondToChannelInvite, Ack), - (SetChannelMemberAdmin, Ack), + (SetChannelMemberRole, Ack), (SendChannelMessage, SendChannelMessageResponse), (GetChannelMessages, GetChannelMessagesResponse), (GetChannelMembers, GetChannelMembersResponse), From 78432d08ca7c120e246ec854ca34ff224374dab8 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 12 Oct 2023 12:21:09 -0700 Subject: [PATCH 126/180] Add channel visibility columns and protos --- crates/channel/src/channel_store_tests.rs | 10 +++++- .../20231011214412_add_guest_role.sql | 4 +-- crates/collab/src/db/ids.rs | 35 +++++++++++++++++++ crates/collab/src/db/tables/channel.rs | 3 +- crates/collab/src/rpc.rs | 20 +++++++++-- crates/rpc/proto/zed.proto | 6 ++++ 6 files changed, 72 insertions(+), 6 deletions(-) diff --git a/crates/channel/src/channel_store_tests.rs b/crates/channel/src/channel_store_tests.rs index f8828159bdc5049adc5b789383ee72c3eca8629c..faa0ade51d215f3ef5a78d001fd5779405a52c48 100644 --- a/crates/channel/src/channel_store_tests.rs +++ b/crates/channel/src/channel_store_tests.rs @@ -3,7 +3,7 @@ use crate::channel_chat::ChannelChatEvent; use super::*; use client::{test::FakeServer, Client, UserStore}; use gpui::{AppContext, ModelHandle, TestAppContext}; -use rpc::proto; +use rpc::proto::{self, ChannelRole}; use settings::SettingsStore; use util::http::FakeHttpClient; @@ -18,10 +18,12 @@ fn test_update_channels(cx: &mut AppContext) { proto::Channel { id: 1, name: "b".to_string(), + visibility: proto::ChannelVisibility::ChannelMembers as i32, }, proto::Channel { id: 2, name: "a".to_string(), + visibility: proto::ChannelVisibility::ChannelMembers as i32, }, ], channel_permissions: vec![proto::ChannelPermission { @@ -49,10 +51,12 @@ fn test_update_channels(cx: &mut AppContext) { proto::Channel { id: 3, name: "x".to_string(), + visibility: proto::ChannelVisibility::ChannelMembers as i32, }, proto::Channel { id: 4, name: "y".to_string(), + visibility: proto::ChannelVisibility::ChannelMembers as i32, }, ], insert_edge: vec![ @@ -92,14 +96,17 @@ fn test_dangling_channel_paths(cx: &mut AppContext) { proto::Channel { id: 0, name: "a".to_string(), + visibility: proto::ChannelVisibility::ChannelMembers as i32, }, proto::Channel { id: 1, name: "b".to_string(), + visibility: proto::ChannelVisibility::ChannelMembers as i32, }, proto::Channel { id: 2, name: "c".to_string(), + visibility: proto::ChannelVisibility::ChannelMembers as i32, }, ], insert_edge: vec![ @@ -158,6 +165,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) { channels: vec![proto::Channel { id: channel_id, name: "the-channel".to_string(), + visibility: proto::ChannelVisibility::ChannelMembers as i32, }], ..Default::default() }); diff --git a/crates/collab/migrations/20231011214412_add_guest_role.sql b/crates/collab/migrations/20231011214412_add_guest_role.sql index 378590a0f9702fd63630a32957780096e3d7ba56..bd178ec63d4723a2f16bec51bdc71ab22fa13a3a 100644 --- a/crates/collab/migrations/20231011214412_add_guest_role.sql +++ b/crates/collab/migrations/20231011214412_add_guest_role.sql @@ -1,4 +1,4 @@ --- Add migration script here - ALTER TABLE channel_members ADD COLUMN role TEXT; UPDATE channel_members SET role = CASE WHEN admin THEN 'admin' ELSE 'member' END; + +ALTER TABLE channels ADD COLUMN visibility TEXT NOT NULL DEFAULT 'channel_members'; diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 946702f36ce5eee521741d4556ea201d0966ab34..d2e990a6409429307d03130e4bf1cc19093b0e57 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -119,3 +119,38 @@ impl Into for ChannelRole { proto.into() } } + +#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default)] +#[sea_orm(rs_type = "String", db_type = "String(None)")] +pub enum ChannelVisibility { + #[sea_orm(string_value = "public")] + Public, + #[sea_orm(string_value = "channel_members")] + #[default] + ChannelMembers, +} + +impl From for ChannelVisibility { + fn from(value: proto::ChannelVisibility) -> Self { + match value { + proto::ChannelVisibility::Public => ChannelVisibility::Public, + proto::ChannelVisibility::ChannelMembers => ChannelVisibility::ChannelMembers, + } + } +} + +impl Into for ChannelVisibility { + fn into(self) -> proto::ChannelVisibility { + match self { + ChannelVisibility::Public => proto::ChannelVisibility::Public, + ChannelVisibility::ChannelMembers => proto::ChannelVisibility::ChannelMembers, + } + } +} + +impl Into for ChannelVisibility { + fn into(self) -> i32 { + let proto: proto::ChannelVisibility = self.into(); + proto.into() + } +} diff --git a/crates/collab/src/db/tables/channel.rs b/crates/collab/src/db/tables/channel.rs index 54f12defc1b56570a0629e2e92a896ad167aa6d6..efda02ec43e18fe582637c60e3c362077b4dd9af 100644 --- a/crates/collab/src/db/tables/channel.rs +++ b/crates/collab/src/db/tables/channel.rs @@ -1,4 +1,4 @@ -use crate::db::ChannelId; +use crate::db::{ChannelId, ChannelVisibility}; use sea_orm::entity::prelude::*; #[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)] @@ -7,6 +7,7 @@ pub struct Model { #[sea_orm(primary_key)] pub id: ChannelId, pub name: String, + pub visbility: ChannelVisibility, } impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index b05421e9601f293501448b935db357e94ebc2dee..962a032ece1a8cdb33fe1a0ecf391c7514a88baa 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -38,8 +38,8 @@ use lazy_static::lazy_static; use prometheus::{register_int_gauge, IntGauge}; use rpc::{ proto::{ - self, Ack, AnyTypedEnvelope, ChannelEdge, EntityMessage, EnvelopedMessage, - LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators, + self, Ack, AnyTypedEnvelope, ChannelEdge, ChannelVisibility, EntityMessage, + EnvelopedMessage, LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators, }, Connection, ConnectionId, Peer, Receipt, TypedEnvelope, }; @@ -2210,6 +2210,8 @@ async fn create_channel( let channel = proto::Channel { id: id.to_proto(), name: request.name, + // TODO: Visibility + visibility: proto::ChannelVisibility::ChannelMembers as i32, }; response.send(proto::CreateChannelResponse { @@ -2299,6 +2301,8 @@ async fn invite_channel_member( update.channel_invitations.push(proto::Channel { id: channel.id.to_proto(), name: channel.name, + // TODO: Visibility + visibility: proto::ChannelVisibility::ChannelMembers as i32, }); for connection_id in session .connection_pool() @@ -2394,6 +2398,8 @@ async fn rename_channel( let channel = proto::Channel { id: request.channel_id, name: new_name, + // TODO: Visibility + visibility: proto::ChannelVisibility::ChannelMembers as i32, }; response.send(proto::RenameChannelResponse { channel: Some(channel.clone()), @@ -2432,6 +2438,8 @@ async fn link_channel( .map(|channel| proto::Channel { id: channel.id.to_proto(), name: channel.name, + // TODO: Visibility + visibility: proto::ChannelVisibility::ChannelMembers as i32, }) .collect(), insert_edge: channels_to_send.edges, @@ -2523,6 +2531,8 @@ async fn move_channel( .map(|channel| proto::Channel { id: channel.id.to_proto(), name: channel.name, + // TODO: Visibility + visibility: proto::ChannelVisibility::ChannelMembers as i32, }) .collect(), insert_edge: channels_to_send.edges, @@ -2579,6 +2589,8 @@ async fn respond_to_channel_invite( .map(|channel| proto::Channel { id: channel.id.to_proto(), name: channel.name, + // TODO: Visibility + visibility: ChannelVisibility::ChannelMembers.into(), }), ); update.unseen_channel_messages = result.channel_messages; @@ -3082,6 +3094,8 @@ fn build_initial_channels_update( update.channels.push(proto::Channel { id: channel.id.to_proto(), name: channel.name, + // TODO: Visibility + visibility: ChannelVisibility::Public.into(), }); } @@ -3114,6 +3128,8 @@ fn build_initial_channels_update( update.channel_invitations.push(proto::Channel { id: channel.id.to_proto(), name: channel.name, + // TODO: Visibility + visibility: ChannelVisibility::Public.into(), }); } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index dbd28bcf5de29b39a5d9eb2b94f97109ef200c76..fec56ad9dc6377006efb84869d4d08a2114afe09 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1539,9 +1539,15 @@ message Nonce { uint64 lower_half = 2; } +enum ChannelVisibility { + Public = 0; + ChannelMembers = 1; +} + message Channel { uint64 id = 1; string name = 2; + ChannelVisibility visibility = 3; } message Contact { From 85fe11ff117f076ae5dc92a7a99607d14e0d6e7e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Oct 2023 12:38:23 -0700 Subject: [PATCH 127/180] Replace disconnected channel notes views when re-opening the notes --- crates/channel/src/channel_buffer.rs | 4 +++ crates/collab_ui/src/channel_view.rs | 41 +++++++++++++++++++++------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/channel/src/channel_buffer.rs b/crates/channel/src/channel_buffer.rs index 7de8b956f1e897b4293274441689570f63780dcb..ab7ea78ac1b70e5c5b0c028289f2b9f55fe924f9 100644 --- a/crates/channel/src/channel_buffer.rs +++ b/crates/channel/src/channel_buffer.rs @@ -99,6 +99,10 @@ impl ChannelBuffer { })) } + pub fn remote_id(&self, cx: &AppContext) -> u64 { + self.buffer.read(cx).remote_id() + } + pub fn user_store(&self) -> &ModelHandle { &self.user_store } diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 817e9fcb4ed208333dedbb46ec756922b6999c6a..e62ee8ef4b7b0c091000cdfd5b272e62c6fee7f7 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -24,7 +24,7 @@ use workspace::{ item::{FollowableItem, Item, ItemHandle}, register_followable_item, searchable::SearchableItemHandle, - ItemNavHistory, Pane, ViewId, Workspace, WorkspaceId, + ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId, }; actions!(channel_view, [Deploy]); @@ -93,15 +93,36 @@ impl ChannelView { } pane.update(&mut cx, |pane, cx| { - pane.items_of_type::() - .find(|channel_view| channel_view.read(cx).channel_buffer == channel_buffer) - .unwrap_or_else(|| { - cx.add_view(|cx| { - let mut this = Self::new(project, channel_store, channel_buffer, cx); - this.acknowledge_buffer_version(cx); - this - }) - }) + let buffer_id = channel_buffer.read(cx).remote_id(cx); + + let existing_view = pane + .items_of_type::() + .find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id); + + // If this channel buffer is already open in this pane, just return it. + if let Some(existing_view) = existing_view.clone() { + if existing_view.read(cx).channel_buffer == channel_buffer { + return existing_view; + } + } + + let view = cx.add_view(|cx| { + let mut this = Self::new(project, channel_store, channel_buffer, cx); + this.acknowledge_buffer_version(cx); + this + }); + + // If the pane contained a disconnected view for this channel buffer, + // replace that. + if let Some(existing_item) = existing_view { + if let Some(ix) = pane.index_for_item(&existing_item) { + pane.close_item_by_id(existing_item.id(), SaveIntent::Skip, cx) + .detach(); + pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx); + } + } + + view }) .ok_or_else(|| anyhow!("pane was dropped")) }) From f5d6d7caca2ce1d9f2bf4db75bd8cc461c5567f2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Oct 2023 12:39:02 -0700 Subject: [PATCH 128/180] Mark channel notes as disconnected immediately upon explicitly signing out --- crates/channel/src/channel_store.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 2a2fa454f2b4435a806d90304940a4ce61450d09..bceb2c094d5b7d8e0f6b58702a593d70fc139e0d 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -114,12 +114,21 @@ impl ChannelStore { let watch_connection_status = cx.spawn_weak(|this, mut cx| async move { while let Some(status) = connection_status.next().await { let this = this.upgrade(&cx)?; + match status { + client::Status::Connected { .. } => { + this.update(&mut cx, |this, cx| this.handle_connect(cx)) + .await + .log_err()?; + } + client::Status::SignedOut | client::Status::UpgradeRequired => { + this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx)); + } + _ => { + this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx)); + } + } if status.is_connected() { - this.update(&mut cx, |this, cx| this.handle_connect(cx)) - .await - .log_err()?; } else { - this.update(&mut cx, |this, cx| this.handle_disconnect(cx)); } } Some(()) @@ -823,7 +832,7 @@ impl ChannelStore { }) } - fn handle_disconnect(&mut self, cx: &mut ModelContext) { + fn handle_disconnect(&mut self, wait_for_reconnect: bool, cx: &mut ModelContext) { self.channel_index.clear(); self.channel_invitations.clear(); self.channel_participants.clear(); @@ -834,7 +843,10 @@ impl ChannelStore { self.disconnect_channel_buffers_task.get_or_insert_with(|| { cx.spawn_weak(|this, mut cx| async move { - cx.background().timer(RECONNECT_TIMEOUT).await; + if wait_for_reconnect { + cx.background().timer(RECONNECT_TIMEOUT).await; + } + if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { for (_, buffer) in this.opened_buffers.drain() { From d23bb3b05da84c29ce9626f4d7a68461f2e19c93 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 16:18:54 -0400 Subject: [PATCH 129/180] Unbork markdown parse test by making links match --- crates/editor/src/hover_popover.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 00a307df68ab69cd5e4b188642e7182a0e6e3bee..5b3985edf933b75deaaa3ba49803a6151442be37 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -911,7 +911,7 @@ mod tests { // Links Row { blocks: vec![HoverBlock { - text: "one [two](the-url) three".to_string(), + text: "one [two](https://the-url) three".to_string(), kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), @@ -932,7 +932,7 @@ mod tests { - a - b * two - - [c](the-url) + - [c](https://the-url) - d" .unindent(), kind: HoverBlockKind::Markdown, From 45f3a9835972b42763bc95d3759703d2f4f5f379 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 12 Oct 2023 17:40:20 -0400 Subject: [PATCH 130/180] Remove old `ui` and `storybook` crates (#3125) This PR deletes the old `ui` and `storybook` crates in favor of their newer variants that we'll be landing to `main` in the near future. ### Motivation These crates are based off the old version of GPUI 2 (the `gpui2` crate). At this point we have since transitioned to the new version of GPUI 2 (the `gpui3` crate, currently still on the `gpui2` branch). Having both copies around is confusing, so the old ones are going the way of the dinosaurs. Release Notes: - N/A --- Cargo.lock | 59 - Cargo.toml | 2 - crates/storybook/Cargo.lock | 2919 ----------------- crates/storybook/Cargo.toml | 30 - crates/storybook/docs/thoughts.md | 72 - crates/storybook/src/stories.rs | 3 - crates/storybook/src/stories/components.rs | 22 - .../src/stories/components/assistant_panel.rs | 16 - .../src/stories/components/breadcrumb.rs | 45 - .../src/stories/components/buffer.rs | 36 - .../src/stories/components/chat_panel.rs | 46 - .../src/stories/components/collab_panel.rs | 16 - .../src/stories/components/context_menu.rs | 21 - .../src/stories/components/facepile.rs | 25 - .../src/stories/components/keybinding.rs | 64 - .../stories/components/language_selector.rs | 16 - .../src/stories/components/multi_buffer.rs | 24 - .../src/stories/components/palette.rs | 53 - .../storybook/src/stories/components/panel.rs | 25 - .../src/stories/components/project_panel.rs | 20 - .../src/stories/components/recent_projects.rs | 16 - .../src/stories/components/status_bar.rs | 16 - .../storybook/src/stories/components/tab.rs | 91 - .../src/stories/components/tab_bar.rs | 46 - .../src/stories/components/terminal.rs | 16 - .../src/stories/components/theme_selector.rs | 16 - .../src/stories/components/title_bar.rs | 16 - .../src/stories/components/toolbar.rs | 70 - .../src/stories/components/traffic_lights.rs | 18 - crates/storybook/src/stories/elements.rs | 5 - .../storybook/src/stories/elements/avatar.rs | 23 - .../storybook/src/stories/elements/button.rs | 192 -- crates/storybook/src/stories/elements/icon.rs | 19 - .../storybook/src/stories/elements/input.rs | 16 - .../storybook/src/stories/elements/label.rs | 18 - crates/storybook/src/stories/kitchen_sink.rs | 26 - crates/storybook/src/story.rs | 44 - crates/storybook/src/story_selector.rs | 178 - crates/storybook/src/storybook.rs | 198 -- crates/ui/Cargo.toml | 16 - crates/ui/docs/_project.md | 13 - crates/ui/docs/elevation.md | 57 - crates/ui/src/children.rs | 7 - crates/ui/src/components.rs | 163 - crates/ui/src/components/assistant_panel.rs | 91 - crates/ui/src/components/breadcrumb.rs | 71 - crates/ui/src/components/buffer.rs | 233 -- crates/ui/src/components/chat_panel.rs | 108 - crates/ui/src/components/collab_panel.rs | 161 - crates/ui/src/components/command_palette.rs | 29 - crates/ui/src/components/context_menu.rs | 65 - crates/ui/src/components/editor_pane.rs | 60 - crates/ui/src/components/facepile.rs | 28 - crates/ui/src/components/icon_button.rs | 67 - crates/ui/src/components/keybinding.rs | 158 - crates/ui/src/components/language_selector.rs | 36 - crates/ui/src/components/list.rs | 512 --- crates/ui/src/components/multi_buffer.rs | 42 - crates/ui/src/components/palette.rs | 152 - crates/ui/src/components/panel.rs | 142 - crates/ui/src/components/panes.rs | 132 - crates/ui/src/components/player_stack.rs | 65 - crates/ui/src/components/project_panel.rs | 58 - crates/ui/src/components/recent_projects.rs | 32 - crates/ui/src/components/status_bar.rs | 144 - crates/ui/src/components/tab.rs | 131 - crates/ui/src/components/tab_bar.rs | 85 - crates/ui/src/components/terminal.rs | 84 - crates/ui/src/components/theme_selector.rs | 37 - crates/ui/src/components/title_bar.rs | 117 - crates/ui/src/components/toast.rs | 66 - crates/ui/src/components/toolbar.rs | 49 - crates/ui/src/components/traffic_lights.rs | 78 - crates/ui/src/components/workspace.rs | 186 -- crates/ui/src/element_ext.rs | 24 - crates/ui/src/elements.rs | 19 - crates/ui/src/elements/avatar.rs | 41 - crates/ui/src/elements/button.rs | 203 -- crates/ui/src/elements/details.rs | 33 - crates/ui/src/elements/icon.rs | 185 -- crates/ui/src/elements/input.rs | 106 - crates/ui/src/elements/label.rs | 161 - crates/ui/src/elements/player.rs | 133 - crates/ui/src/elements/stack.rs | 31 - crates/ui/src/elements/tool_divider.rs | 17 - crates/ui/src/lib.rs | 20 - crates/ui/src/prelude.rs | 274 -- crates/ui/src/static_data.rs | 966 ------ crates/ui/src/theme.rs | 196 -- crates/ui/src/tokens.rs | 25 - crates/ui/tracker.md | 133 - 91 files changed, 10580 deletions(-) delete mode 100644 crates/storybook/Cargo.lock delete mode 100644 crates/storybook/Cargo.toml delete mode 100644 crates/storybook/docs/thoughts.md delete mode 100644 crates/storybook/src/stories.rs delete mode 100644 crates/storybook/src/stories/components.rs delete mode 100644 crates/storybook/src/stories/components/assistant_panel.rs delete mode 100644 crates/storybook/src/stories/components/breadcrumb.rs delete mode 100644 crates/storybook/src/stories/components/buffer.rs delete mode 100644 crates/storybook/src/stories/components/chat_panel.rs delete mode 100644 crates/storybook/src/stories/components/collab_panel.rs delete mode 100644 crates/storybook/src/stories/components/context_menu.rs delete mode 100644 crates/storybook/src/stories/components/facepile.rs delete mode 100644 crates/storybook/src/stories/components/keybinding.rs delete mode 100644 crates/storybook/src/stories/components/language_selector.rs delete mode 100644 crates/storybook/src/stories/components/multi_buffer.rs delete mode 100644 crates/storybook/src/stories/components/palette.rs delete mode 100644 crates/storybook/src/stories/components/panel.rs delete mode 100644 crates/storybook/src/stories/components/project_panel.rs delete mode 100644 crates/storybook/src/stories/components/recent_projects.rs delete mode 100644 crates/storybook/src/stories/components/status_bar.rs delete mode 100644 crates/storybook/src/stories/components/tab.rs delete mode 100644 crates/storybook/src/stories/components/tab_bar.rs delete mode 100644 crates/storybook/src/stories/components/terminal.rs delete mode 100644 crates/storybook/src/stories/components/theme_selector.rs delete mode 100644 crates/storybook/src/stories/components/title_bar.rs delete mode 100644 crates/storybook/src/stories/components/toolbar.rs delete mode 100644 crates/storybook/src/stories/components/traffic_lights.rs delete mode 100644 crates/storybook/src/stories/elements.rs delete mode 100644 crates/storybook/src/stories/elements/avatar.rs delete mode 100644 crates/storybook/src/stories/elements/button.rs delete mode 100644 crates/storybook/src/stories/elements/icon.rs delete mode 100644 crates/storybook/src/stories/elements/input.rs delete mode 100644 crates/storybook/src/stories/elements/label.rs delete mode 100644 crates/storybook/src/stories/kitchen_sink.rs delete mode 100644 crates/storybook/src/story.rs delete mode 100644 crates/storybook/src/story_selector.rs delete mode 100644 crates/storybook/src/storybook.rs delete mode 100644 crates/ui/Cargo.toml delete mode 100644 crates/ui/docs/_project.md delete mode 100644 crates/ui/docs/elevation.md delete mode 100644 crates/ui/src/children.rs delete mode 100644 crates/ui/src/components.rs delete mode 100644 crates/ui/src/components/assistant_panel.rs delete mode 100644 crates/ui/src/components/breadcrumb.rs delete mode 100644 crates/ui/src/components/buffer.rs delete mode 100644 crates/ui/src/components/chat_panel.rs delete mode 100644 crates/ui/src/components/collab_panel.rs delete mode 100644 crates/ui/src/components/command_palette.rs delete mode 100644 crates/ui/src/components/context_menu.rs delete mode 100644 crates/ui/src/components/editor_pane.rs delete mode 100644 crates/ui/src/components/facepile.rs delete mode 100644 crates/ui/src/components/icon_button.rs delete mode 100644 crates/ui/src/components/keybinding.rs delete mode 100644 crates/ui/src/components/language_selector.rs delete mode 100644 crates/ui/src/components/list.rs delete mode 100644 crates/ui/src/components/multi_buffer.rs delete mode 100644 crates/ui/src/components/palette.rs delete mode 100644 crates/ui/src/components/panel.rs delete mode 100644 crates/ui/src/components/panes.rs delete mode 100644 crates/ui/src/components/player_stack.rs delete mode 100644 crates/ui/src/components/project_panel.rs delete mode 100644 crates/ui/src/components/recent_projects.rs delete mode 100644 crates/ui/src/components/status_bar.rs delete mode 100644 crates/ui/src/components/tab.rs delete mode 100644 crates/ui/src/components/tab_bar.rs delete mode 100644 crates/ui/src/components/terminal.rs delete mode 100644 crates/ui/src/components/theme_selector.rs delete mode 100644 crates/ui/src/components/title_bar.rs delete mode 100644 crates/ui/src/components/toast.rs delete mode 100644 crates/ui/src/components/toolbar.rs delete mode 100644 crates/ui/src/components/traffic_lights.rs delete mode 100644 crates/ui/src/components/workspace.rs delete mode 100644 crates/ui/src/element_ext.rs delete mode 100644 crates/ui/src/elements.rs delete mode 100644 crates/ui/src/elements/avatar.rs delete mode 100644 crates/ui/src/elements/button.rs delete mode 100644 crates/ui/src/elements/details.rs delete mode 100644 crates/ui/src/elements/icon.rs delete mode 100644 crates/ui/src/elements/input.rs delete mode 100644 crates/ui/src/elements/label.rs delete mode 100644 crates/ui/src/elements/player.rs delete mode 100644 crates/ui/src/elements/stack.rs delete mode 100644 crates/ui/src/elements/tool_divider.rs delete mode 100644 crates/ui/src/lib.rs delete mode 100644 crates/ui/src/prelude.rs delete mode 100644 crates/ui/src/static_data.rs delete mode 100644 crates/ui/src/theme.rs delete mode 100644 crates/ui/src/tokens.rs delete mode 100644 crates/ui/tracker.md diff --git a/Cargo.lock b/Cargo.lock index f95db3d354870d878915207ee56dd163a3579884..01153ca0f8222e341ee77287518dca45ed53048d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6623,12 +6623,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - [[package]] name = "rustybuzz" version = "0.3.0" @@ -7682,28 +7676,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "storybook" -version = "0.1.0" -dependencies = [ - "anyhow", - "chrono", - "clap 4.4.4", - "fs", - "futures 0.3.28", - "gpui2", - "itertools 0.11.0", - "log", - "rust-embed", - "serde", - "settings", - "simplelog", - "strum", - "theme", - "ui", - "util", -] - [[package]] name = "stringprep" version = "0.1.4" @@ -7726,22 +7698,6 @@ name = "strum" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.37", -] [[package]] name = "subtle" @@ -8908,21 +8864,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ui" -version = "0.1.0" -dependencies = [ - "anyhow", - "chrono", - "gpui2", - "rand 0.8.5", - "serde", - "settings", - "smallvec", - "strum", - "theme", -] - [[package]] name = "unicase" version = "2.7.0" diff --git a/Cargo.toml b/Cargo.toml index 25aec39cdd822c90e018cc4504b15075b9eb7ad2..532610efd631edb05fee1040b2c2800e9c256a42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,13 +66,11 @@ members = [ "crates/sqlez_macros", "crates/feature_flags", "crates/rich_text", - "crates/storybook", "crates/sum_tree", "crates/terminal", "crates/text", "crates/theme", "crates/theme_selector", - "crates/ui", "crates/util", "crates/semantic_index", "crates/vim", diff --git a/crates/storybook/Cargo.lock b/crates/storybook/Cargo.lock deleted file mode 100644 index b652dbbd132575197b77521df34d1df0785ffa09..0000000000000000000000000000000000000000 --- a/crates/storybook/Cargo.lock +++ /dev/null @@ -1,2919 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - -[[package]] -name = "aho-corasick" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" -dependencies = [ - "memchr", -] - -[[package]] -name = "anyhow" -version = "1.0.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" -dependencies = [ - "async-lock", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock", - "autocfg", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite", - "log", - "parking", - "polling", - "rustix", - "slab", - "socket2", - "waker-fn", -] - -[[package]] -name = "async-lock" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-net" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f" -dependencies = [ - "async-io", - "autocfg", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-process" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" -dependencies = [ - "async-io", - "async-lock", - "autocfg", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", - "signal-hook", - "windows-sys", -] - -[[package]] -name = "async-task" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" - -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic-waker" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide 0.7.1", - "object", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "bindgen" -version = "0.65.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.25", - "which", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blocking" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" -dependencies = [ - "async-channel", - "async-lock", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "log", -] - -[[package]] -name = "bstr" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" - -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clang-sys" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" -dependencies = [ - "glob", - "libc", - "libloading 0.7.4", -] - -[[package]] -name = "cmake" -version = "0.1.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" -dependencies = [ - "cc", -] - -[[package]] -name = "cocoa" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" -dependencies = [ - "bitflags", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" -dependencies = [ - "bitflags", - "block", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "collections" -version = "0.1.0" - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "concurrent-queue" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-cstr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" - -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", - "uuid 0.5.1", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" -dependencies = [ - "bitflags", - "core-foundation", - "libc", -] - -[[package]] -name = "core-text" -version = "19.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" -dependencies = [ - "core-foundation", - "core-graphics", - "foreign-types", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "curl" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi", -] - -[[package]] -name = "curl-sys" -version = "0.4.63+curl-8.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi", -] - -[[package]] -name = "data-url" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" -dependencies = [ - "matches", -] - -[[package]] -name = "deflate" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" -dependencies = [ - "adler32", - "byteorder", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading 0.8.0", -] - -[[package]] -name = "dwrote" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" -dependencies = [ - "lazy_static", - "libc", - "winapi", - "wio", -] - -[[package]] -name = "dyn-clone" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "erased-serde" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f94c0e13118e7d7533271f754a168ae8400e6a1cc043f2bfd53cc7290f1a1de3" -dependencies = [ - "serde", -] - -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "etagere" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf22f748754352918e082e0039335ee92454a5d62bcaf69b5e8daf5907d9644" -dependencies = [ - "euclid", - "svg_fmt", -] - -[[package]] -name = "euclid" -version = "0.22.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" -dependencies = [ - "num-traits", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "flate2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" -dependencies = [ - "crc32fast", - "miniz_oxide 0.7.1", -] - -[[package]] -name = "float-cmp" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" - -[[package]] -name = "float-ord" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "font-kit" -version = "0.11.0" -source = "git+https://github.com/zed-industries/font-kit?rev=b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18#b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" -dependencies = [ - "bitflags", - "byteorder", - "core-foundation", - "core-graphics", - "core-text", - "dirs-next", - "dwrote", - "float-ord", - "freetype", - "lazy_static", - "libc", - "log", - "pathfinder_geometry", - "pathfinder_simd", - "walkdir", - "winapi", - "yeslogic-fontconfig-sys", -] - -[[package]] -name = "fontdb" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58903f4f8d5b58c7d300908e4ebe5289c1bfdf5587964330f12023b8ff17fd1" -dependencies = [ - "log", - "memmap2", - "ttf-parser 0.12.3", -] - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "freetype" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6" -dependencies = [ - "freetype-sys", - "libc", -] - -[[package]] -name = "freetype-sys" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" -dependencies = [ - "cmake", - "libc", - "pkg-config", -] - -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-executor" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gif" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "gimli" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "globset" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - -[[package]] -name = "gpui" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-task", - "bindgen", - "block", - "cc", - "cocoa", - "collections", - "core-foundation", - "core-graphics", - "core-text", - "ctor", - "etagere", - "font-kit", - "foreign-types", - "futures", - "gpui_macros", - "image", - "itertools", - "lazy_static", - "log", - "media", - "metal", - "num_cpus", - "objc", - "ordered-float", - "parking", - "parking_lot 0.11.2", - "pathfinder_color", - "pathfinder_geometry", - "postage", - "rand", - "resvg", - "schemars", - "seahash", - "serde", - "serde_derive", - "serde_json", - "smallvec", - "smol", - "sqlez", - "sum_tree", - "time", - "tiny-skia", - "usvg", - "util", - "uuid 1.4.0", - "waker-fn", -] - -[[package]] -name = "gpui_macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - -[[package]] -name = "hermit-abi" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - -[[package]] -name = "http" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "image" -version = "0.23.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "gif", - "jpeg-decoder", - "num-iter", - "num-rational", - "num-traits", - "png", - "scoped_threadpool", - "tiff", -] - -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "indoc" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", -] - -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener", - "futures-lite", - "http", - "log", - "mime", - "once_cell", - "polling", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" - -[[package]] -name = "jpeg-decoder" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" -dependencies = [ - "rayon", -] - -[[package]] -name = "kurbo" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" -dependencies = [ - "arrayvec 0.7.4", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libloading" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" -dependencies = [ - "cfg-if", - "windows-sys", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "lock_api" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" -dependencies = [ - "serde", - "value-bag", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "media" -version = "0.1.0" -dependencies = [ - "anyhow", - "bindgen", - "block", - "bytes", - "core-foundation", - "foreign-types", - "metal", - "objc", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memmap2" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "metal" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4598d719460ade24c7d91f335daf055bf2a7eec030728ce751814c50cdd6a26c" -dependencies = [ - "bitflags", - "block", - "cocoa-foundation", - "foreign-types", - "log", - "objc", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", - "objc_exception", -] - -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - -[[package]] -name = "object" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-float" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" -dependencies = [ - "num-traits", -] - -[[package]] -name = "parking" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.8", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.3.5", - "smallvec", - "windows-targets", -] - -[[package]] -name = "pathfinder_color" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69bdc0d277d559e35e1b374de56df9262a6b71e091ca04a8831a239f8c7f0c62" -dependencies = [ - "pathfinder_simd", -] - -[[package]] -name = "pathfinder_geometry" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" -dependencies = [ - "log", - "pathfinder_simd", -] - -[[package]] -name = "pathfinder_simd" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - -[[package]] -name = "percent-encoding" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - -[[package]] -name = "pest" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9" -dependencies = [ - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pico-args" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" - -[[package]] -name = "pin-project" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] -name = "storybook" -version = "0.1.0" -dependencies = [ - "gpui", -] - -[[package]] -name = "png" -version = "0.16.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" -dependencies = [ - "bitflags", - "crc32fast", - "deflate", - "miniz_oxide 0.3.7", -] - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys", -] - -[[package]] -name = "pollster" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" - -[[package]] -name = "postage" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" -dependencies = [ - "atomic", - "crossbeam-queue", - "futures", - "log", - "parking_lot 0.12.1", - "pin-project", - "pollster", - "static_assertions", - "thiserror", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "prettyplease" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387" -dependencies = [ - "proc-macro2", - "syn 2.0.25", -] - -[[package]] -name = "proc-macro2" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "rctree" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9e29cb19c8fe84169fcb07f8f11e66bc9e6e0280efd4715c54818296f8a4a8" - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall 0.2.16", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" - -[[package]] -name = "resvg" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09697862c5c3f940cbaffef91969c62188b5c8ed385b0aef43a5ff01ddc8000f" -dependencies = [ - "jpeg-decoder", - "log", - "pico-args", - "png", - "rgb", - "svgfilters", - "tiny-skia", - "usvg", -] - -[[package]] -name = "rgb" -version = "0.8.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "roxmltree" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" -dependencies = [ - "xmlparser", -] - -[[package]] -name = "rust-embed" -version = "6.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "6.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 2.0.25", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "7.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" -dependencies = [ - "globset", - "sha2", - "walkdir", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc_version" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.37.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "rustybuzz" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab463a295d00f3692e0974a0bfd83c7a9bcd119e27e07c2beecdb1b44a09d10" -dependencies = [ - "bitflags", - "bytemuck", - "smallvec", - "ttf-parser 0.9.0", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-general-category", - "unicode-script", -] - -[[package]] -name = "ryu" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" - -[[package]] -name = "safe_arch" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "schemars" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 1.0.109", -] - -[[package]] -name = "scoped_threadpool" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - -[[package]] -name = "serde" -version = "1.0.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "serde_fmt" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_json" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - -[[package]] -name = "signal-hook" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "simplecss" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" -dependencies = [ - "log", -] - -[[package]] -name = "siphasher" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" - -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - -[[package]] -name = "smallvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" - -[[package]] -name = "smol" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" -dependencies = [ - "async-channel", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-net", - "async-process", - "blocking", - "futures-lite", -] - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "sqlez" -version = "0.1.0" -dependencies = [ - "anyhow", - "futures", - "indoc", - "lazy_static", - "libsqlite3-sys", - "parking_lot 0.11.2", - "smol", - "thread_local", - "uuid 1.4.0", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "sum_tree" -version = "0.1.0" -dependencies = [ - "arrayvec 0.7.4", - "log", -] - -[[package]] -name = "sval" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b031320a434d3e9477ccf9b5756d57d4272937b8d22cb88af80b7633a1b78b1" - -[[package]] -name = "sval_buffer" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf7e9412af26b342f3f2cc5cc4122b0105e9d16eb76046cd14ed10106cf6028" -dependencies = [ - "sval", - "sval_ref", -] - -[[package]] -name = "sval_dynamic" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ef628e8a77a46ed3338db8d1b08af77495123cc229453084e47cd716d403cf" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_fmt" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dc09e9364c2045ab5fa38f7b04d077b3359d30c4c2b3ec4bae67a358bd64326" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_json" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada6f627e38cbb8860283649509d87bc4a5771141daa41c78fd31f2b9485888d" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_ref" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703ca1942a984bd0d9b5a4c0a65ab8b4b794038d080af4eb303c71bc6bf22d7c" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_serde" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830926cd0581f7c3e5d51efae4d35c6b6fc4db583842652891ba2f1bed8db046" -dependencies = [ - "serde", - "sval", - "sval_buffer", - "sval_fmt", -] - -[[package]] -name = "svg_fmt" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2" - -[[package]] -name = "svgfilters" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0dce2fee79ac40c21dafba48565ff7a5fa275e23ffe9ce047a40c9574ba34e" -dependencies = [ - "float-cmp", - "rgb", -] - -[[package]] -name = "svgtypes" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff" -dependencies = [ - "float-cmp", - "siphasher", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "take-until" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" - -[[package]] -name = "thiserror" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "tiff" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" -dependencies = [ - "jpeg-decoder", - "miniz_oxide 0.4.4", - "weezl", -] - -[[package]] -name = "time" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" -dependencies = [ - "itoa", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" - -[[package]] -name = "time-macros" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" -dependencies = [ - "time-core", -] - -[[package]] -name = "tiny-skia" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf81f2900d2e235220e6f31ec9f63ade6a7f59090c556d74fe949bb3b15e9fe" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "bytemuck", - "cfg-if", - "png", - "safe_arch", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "tracing-core" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - -[[package]] -name = "ttf-parser" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ddb402ac6c2af6f7a2844243887631c4e94b51585b229fcfddb43958cd55ca" - -[[package]] -name = "ttf-parser" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - -[[package]] -name = "unicode-bidi-mirroring" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" - -[[package]] -name = "unicode-ccc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" - -[[package]] -name = "unicode-general-category" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9af028e052a610d99e066b33304625dea9613170a2563314490a4e6ec5cf7f" - -[[package]] -name = "unicode-ident" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-script" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" - -[[package]] -name = "unicode-vo" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" - -[[package]] -name = "url" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "usvg" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8352f317d8f9a918ba5154797fb2a93e2730244041cf7d5be35148266adfa5" -dependencies = [ - "base64", - "data-url", - "flate2", - "fontdb", - "kurbo", - "log", - "memmap2", - "pico-args", - "rctree", - "roxmltree", - "rustybuzz", - "simplecss", - "siphasher", - "svgtypes", - "ttf-parser 0.12.3", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", -] - -[[package]] -name = "util" -version = "0.1.0" -dependencies = [ - "anyhow", - "backtrace", - "dirs", - "futures", - "isahc", - "lazy_static", - "log", - "rand", - "rust-embed", - "serde", - "serde_json", - "smol", - "take-until", - "url", -] - -[[package]] -name = "uuid" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" - -[[package]] -name = "uuid" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" -dependencies = [ - "getrandom", -] - -[[package]] -name = "value-bag" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" -dependencies = [ - "value-bag-serde1", - "value-bag-sval2", -] - -[[package]] -name = "value-bag-serde1" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b9f3feef403a50d4d67e9741a6d8fc688bcbb4e4f31bd4aab72cc690284394" -dependencies = [ - "erased-serde", - "serde", - "serde_fmt", -] - -[[package]] -name = "value-bag-sval2" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b24f4146b6f3361e91cbf527d1fb35e9376c3c0cef72ca5ec5af6d640fad7d" -dependencies = [ - "sval", - "sval_buffer", - "sval_dynamic", - "sval_fmt", - "sval_json", - "sval_ref", - "sval_serde", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - -[[package]] -name = "walkdir" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "weezl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" - -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "wio" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" -dependencies = [ - "winapi", -] - -[[package]] -name = "xmlparser" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" - -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - -[[package]] -name = "yeslogic-fontconfig-sys" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386" -dependencies = [ - "const-cstr", - "dlib", - "once_cell", - "pkg-config", -] diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml deleted file mode 100644 index 43890dd01a78f1b34ab7cb5f65ae67a17c3f3d23..0000000000000000000000000000000000000000 --- a/crates/storybook/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "storybook" -version = "0.1.0" -edition = "2021" -publish = false - -[[bin]] -name = "storybook" -path = "src/storybook.rs" - -[dependencies] -anyhow.workspace = true -clap = { version = "4.4", features = ["derive", "string"] } -chrono = "0.4" -fs = { path = "../fs" } -futures.workspace = true -gpui2 = { path = "../gpui2" } -itertools = "0.11.0" -log.workspace = true -rust-embed.workspace = true -serde.workspace = true -settings = { path = "../settings" } -simplelog = "0.9" -strum = { version = "0.25.0", features = ["derive"] } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } - -[dev-dependencies] -gpui2 = { path = "../gpui2", features = ["test-support"] } diff --git a/crates/storybook/docs/thoughts.md b/crates/storybook/docs/thoughts.md deleted file mode 100644 index 9416f2593313b973ada409770b41b1f1f074ba09..0000000000000000000000000000000000000000 --- a/crates/storybook/docs/thoughts.md +++ /dev/null @@ -1,72 +0,0 @@ -Much of element styling is now handled by an external engine. - - -How do I make an element hover. - -There's a hover style. - -Hoverable needs to wrap another element. That element can be styled. - -```rs -struct Hoverable { - -} - -impl Element for Hoverable { - -} - -``` - - - -```rs -#[derive(Styled, Interactive)] -pub struct Div { - declared_style: StyleRefinement, - interactions: Interactions -} - -pub trait Styled { - fn declared_style(&mut self) -> &mut StyleRefinement; - fn compute_style(&mut self) -> Style { - Style::default().refine(self.declared_style()) - } - - // All the tailwind classes, modifying self.declared_style() -} - -impl Style { - pub fn paint_background(layout: Layout, cx: &mut PaintContext); - pub fn paint_foreground(layout: Layout, cx: &mut PaintContext); -} - -pub trait Interactive { - fn interactions(&mut self) -> &mut Interactions; - - fn on_click(self, ) -} - -struct Interactions { - click: SmallVec<[; 1]>, -} - - -``` - - -```rs - - -trait Stylable { - type Style; - - fn with_style(self, style: Self::Style) -> Self; -} - - - - - - -``` diff --git a/crates/storybook/src/stories.rs b/crates/storybook/src/stories.rs deleted file mode 100644 index 95b8844157c8ab72383cb10ea7a7c6dbe38efc06..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod components; -pub mod elements; -pub mod kitchen_sink; diff --git a/crates/storybook/src/stories/components.rs b/crates/storybook/src/stories/components.rs deleted file mode 100644 index 85d5ce088f05dc7410cff289b5d8a67aedbfd4c8..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub mod assistant_panel; -pub mod breadcrumb; -pub mod buffer; -pub mod chat_panel; -pub mod collab_panel; -pub mod context_menu; -pub mod facepile; -pub mod keybinding; -pub mod language_selector; -pub mod multi_buffer; -pub mod palette; -pub mod panel; -pub mod project_panel; -pub mod recent_projects; -pub mod status_bar; -pub mod tab; -pub mod tab_bar; -pub mod terminal; -pub mod theme_selector; -pub mod title_bar; -pub mod toolbar; -pub mod traffic_lights; diff --git a/crates/storybook/src/stories/components/assistant_panel.rs b/crates/storybook/src/stories/components/assistant_panel.rs deleted file mode 100644 index 09f964756ea07e74173861a9f391587f8974ccef..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/assistant_panel.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::AssistantPanel; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct AssistantPanelStory {} - -impl AssistantPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, AssistantPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(AssistantPanel::new()) - } -} diff --git a/crates/storybook/src/stories/components/breadcrumb.rs b/crates/storybook/src/stories/components/breadcrumb.rs deleted file mode 100644 index 002b6140e13e98731ff40be7ddefcdc11ab4bf67..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/breadcrumb.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::path::PathBuf; -use std::str::FromStr; - -use ui::prelude::*; -use ui::{Breadcrumb, HighlightedText, Symbol}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct BreadcrumbStory {} - -impl BreadcrumbStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - Story::container(cx) - .child(Story::title_for::<_, Breadcrumb>(cx)) - .child(Story::label(cx, "Default")) - .child(Breadcrumb::new( - PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(), - vec![ - Symbol(vec![ - HighlightedText { - text: "impl ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "BreadcrumbStory".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "render".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - ], - )) - } -} diff --git a/crates/storybook/src/stories/components/buffer.rs b/crates/storybook/src/stories/components/buffer.rs deleted file mode 100644 index 0b3268421bf3ace9a429c68fc23ff9f9baa16b47..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/buffer.rs +++ /dev/null @@ -1,36 +0,0 @@ -use gpui2::geometry::rems; -use ui::prelude::*; -use ui::{ - empty_buffer_example, hello_world_rust_buffer_example, - hello_world_rust_buffer_with_status_example, Buffer, -}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct BufferStory {} - -impl BufferStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - Story::container(cx) - .child(Story::title_for::<_, Buffer>(cx)) - .child(Story::label(cx, "Default")) - .child(div().w(rems(64.)).h_96().child(empty_buffer_example())) - .child(Story::label(cx, "Hello World (Rust)")) - .child( - div() - .w(rems(64.)) - .h_96() - .child(hello_world_rust_buffer_example(&theme)), - ) - .child(Story::label(cx, "Hello World (Rust) with Status")) - .child( - div() - .w(rems(64.)) - .h_96() - .child(hello_world_rust_buffer_with_status_example(&theme)), - ) - } -} diff --git a/crates/storybook/src/stories/components/chat_panel.rs b/crates/storybook/src/stories/components/chat_panel.rs deleted file mode 100644 index e87ac0afa29cc60fbb61a674517d8e4bb1381b29..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/chat_panel.rs +++ /dev/null @@ -1,46 +0,0 @@ -use chrono::DateTime; -use ui::prelude::*; -use ui::{ChatMessage, ChatPanel, Panel}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ChatPanelStory {} - -impl ChatPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, ChatPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(Panel::new( - ScrollState::default(), - |_, _| vec![ChatPanel::new(ScrollState::default()).into_any()], - Box::new(()), - )) - .child(Story::label(cx, "With Mesages")) - .child(Panel::new( - ScrollState::default(), - |_, _| { - vec![ChatPanel::new(ScrollState::default()) - .with_messages(vec![ - ChatMessage::new( - "osiewicz".to_string(), - "is this thing on?".to_string(), - DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z") - .unwrap() - .naive_local(), - ), - ChatMessage::new( - "maxdeviant".to_string(), - "Reading you loud and clear!".to_string(), - DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z") - .unwrap() - .naive_local(), - ), - ]) - .into_any()] - }, - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/collab_panel.rs b/crates/storybook/src/stories/components/collab_panel.rs deleted file mode 100644 index 6a66f0d47f6671ff0edcaf492bfd92506fdf2097..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/collab_panel.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::CollabPanel; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct CollabPanelStory {} - -impl CollabPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, CollabPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(CollabPanel::new(ScrollState::default())) - } -} diff --git a/crates/storybook/src/stories/components/context_menu.rs b/crates/storybook/src/stories/components/context_menu.rs deleted file mode 100644 index 71776ca66a20eafcc551d44e458d0ea17cdbac38..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/context_menu.rs +++ /dev/null @@ -1,21 +0,0 @@ -use ui::prelude::*; -use ui::{ContextMenu, ContextMenuItem, Label}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ContextMenuStory {} - -impl ContextMenuStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - //.fill(theme.middle.base.default.background) - .child(Story::title_for::<_, ContextMenu>(cx)) - .child(Story::label(cx, "Default")) - .child(ContextMenu::new([ - ContextMenuItem::header("Section header"), - ContextMenuItem::Separator, - ContextMenuItem::entry(Label::new("Some entry")), - ])) - } -} diff --git a/crates/storybook/src/stories/components/facepile.rs b/crates/storybook/src/stories/components/facepile.rs deleted file mode 100644 index bbd08ae984eb972fdb16134795fb458f5348a92f..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/facepile.rs +++ /dev/null @@ -1,25 +0,0 @@ -use ui::prelude::*; -use ui::{static_players, Facepile}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct FacepileStory {} - -impl FacepileStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let players = static_players(); - - Story::container(cx) - .child(Story::title_for::<_, Facepile>(cx)) - .child(Story::label(cx, "Default")) - .child( - div() - .flex() - .gap_3() - .child(Facepile::new(players.clone().into_iter().take(1))) - .child(Facepile::new(players.clone().into_iter().take(2))) - .child(Facepile::new(players.clone().into_iter().take(3))), - ) - } -} diff --git a/crates/storybook/src/stories/components/keybinding.rs b/crates/storybook/src/stories/components/keybinding.rs deleted file mode 100644 index 1acf59fe3bc17ef53d7fef586f3e40e4edf20ef0..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/keybinding.rs +++ /dev/null @@ -1,64 +0,0 @@ -use itertools::Itertools; -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{Keybinding, ModifierKey, ModifierKeys}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct KeybindingStory {} - -impl KeybindingStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let all_modifier_permutations = ModifierKey::iter().permutations(2); - - Story::container(cx) - .child(Story::title_for::<_, Keybinding>(cx)) - .child(Story::label(cx, "Single Key")) - .child(Keybinding::new("Z".to_string(), ModifierKeys::new())) - .child(Story::label(cx, "Single Key with Modifier")) - .child( - div() - .flex() - .gap_3() - .children(ModifierKey::iter().map(|modifier| { - Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier)) - })), - ) - .child(Story::label(cx, "Single Key with Modifier (Permuted)")) - .child( - div().flex().flex_col().children( - all_modifier_permutations - .chunks(4) - .into_iter() - .map(|chunk| { - div() - .flex() - .gap_4() - .py_3() - .children(chunk.map(|permutation| { - let mut modifiers = ModifierKeys::new(); - - for modifier in permutation { - modifiers = modifiers.add(modifier); - } - - Keybinding::new("X".to_string(), modifiers) - })) - }), - ), - ) - .child(Story::label(cx, "Single Key with All Modifiers")) - .child(Keybinding::new("Z".to_string(), ModifierKeys::all())) - .child(Story::label(cx, "Chord")) - .child(Keybinding::new_chord( - ("A".to_string(), ModifierKeys::new()), - ("Z".to_string(), ModifierKeys::new()), - )) - .child(Story::label(cx, "Chord with Modifier")) - .child(Keybinding::new_chord( - ("A".to_string(), ModifierKeys::new().control(true)), - ("Z".to_string(), ModifierKeys::new().shift(true)), - )) - } -} diff --git a/crates/storybook/src/stories/components/language_selector.rs b/crates/storybook/src/stories/components/language_selector.rs deleted file mode 100644 index c6dbd13d3fec2434e83483b71b512c8f270d5395..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/language_selector.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::LanguageSelector; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct LanguageSelectorStory {} - -impl LanguageSelectorStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, LanguageSelector>(cx)) - .child(Story::label(cx, "Default")) - .child(LanguageSelector::new()) - } -} diff --git a/crates/storybook/src/stories/components/multi_buffer.rs b/crates/storybook/src/stories/components/multi_buffer.rs deleted file mode 100644 index cd760c54dcaeb04e5d917947d00e9cfb91768c76..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/multi_buffer.rs +++ /dev/null @@ -1,24 +0,0 @@ -use ui::prelude::*; -use ui::{hello_world_rust_buffer_example, MultiBuffer}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct MultiBufferStory {} - -impl MultiBufferStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - Story::container(cx) - .child(Story::title_for::<_, MultiBuffer>(cx)) - .child(Story::label(cx, "Default")) - .child(MultiBuffer::new(vec![ - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - ])) - } -} diff --git a/crates/storybook/src/stories/components/palette.rs b/crates/storybook/src/stories/components/palette.rs deleted file mode 100644 index d14fac66975f87757e741d87eb92096802f5eeeb..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/palette.rs +++ /dev/null @@ -1,53 +0,0 @@ -use ui::prelude::*; -use ui::{Keybinding, ModifierKeys, Palette, PaletteItem}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct PaletteStory {} - -impl PaletteStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Palette>(cx)) - .child(Story::label(cx, "Default")) - .child(Palette::new(ScrollState::default())) - .child(Story::label(cx, "With Items")) - .child( - Palette::new(ScrollState::default()) - .placeholder("Execute a command...") - .items(vec![ - PaletteItem::new("theme selector: toggle").keybinding( - Keybinding::new_chord( - ("k".to_string(), ModifierKeys::new().command(true)), - ("t".to_string(), ModifierKeys::new().command(true)), - ), - ), - PaletteItem::new("assistant: inline assist").keybinding(Keybinding::new( - "enter".to_string(), - ModifierKeys::new().command(true), - )), - PaletteItem::new("assistant: quote selection").keybinding(Keybinding::new( - ">".to_string(), - ModifierKeys::new().command(true), - )), - PaletteItem::new("assistant: toggle focus").keybinding(Keybinding::new( - "?".to_string(), - ModifierKeys::new().command(true), - )), - PaletteItem::new("auto update: check"), - PaletteItem::new("auto update: view release notes"), - PaletteItem::new("branches: open recent").keybinding(Keybinding::new( - "b".to_string(), - ModifierKeys::new().command(true).alt(true), - )), - PaletteItem::new("chat panel: toggle focus"), - PaletteItem::new("cli: install"), - PaletteItem::new("client: sign in"), - PaletteItem::new("client: sign out"), - PaletteItem::new("editor: cancel") - .keybinding(Keybinding::new("escape".to_string(), ModifierKeys::new())), - ]), - ) - } -} diff --git a/crates/storybook/src/stories/components/panel.rs b/crates/storybook/src/stories/components/panel.rs deleted file mode 100644 index 39a5ceafa26107822b4d36005818bf620b39efbb..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/panel.rs +++ /dev/null @@ -1,25 +0,0 @@ -use ui::prelude::*; -use ui::{Label, Panel}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct PanelStory {} - -impl PanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Panel>(cx)) - .child(Story::label(cx, "Default")) - .child(Panel::new( - ScrollState::default(), - |_, _| { - vec![div() - .overflow_y_scroll(ScrollState::default()) - .children((0..100).map(|ix| Label::new(format!("Item {}", ix + 1)))) - .into_any()] - }, - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/project_panel.rs b/crates/storybook/src/stories/components/project_panel.rs deleted file mode 100644 index cba71cd21a58695075738679a5626cff5db2f59e..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/project_panel.rs +++ /dev/null @@ -1,20 +0,0 @@ -use ui::prelude::*; -use ui::{Panel, ProjectPanel}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ProjectPanelStory {} - -impl ProjectPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, ProjectPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(Panel::new( - ScrollState::default(), - |_, _| vec![ProjectPanel::new(ScrollState::default()).into_any()], - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/recent_projects.rs b/crates/storybook/src/stories/components/recent_projects.rs deleted file mode 100644 index f9246546955a12d85ec693a98d2a52741ea4f279..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/recent_projects.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::RecentProjects; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct RecentProjectsStory {} - -impl RecentProjectsStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, RecentProjects>(cx)) - .child(Story::label(cx, "Default")) - .child(RecentProjects::new()) - } -} diff --git a/crates/storybook/src/stories/components/status_bar.rs b/crates/storybook/src/stories/components/status_bar.rs deleted file mode 100644 index ed3d047c6d8f0c8adb5546f097b31bb2f57786fd..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/status_bar.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::StatusBar; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct StatusBarStory {} - -impl StatusBarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, StatusBar>(cx)) - .child(Story::label(cx, "Default")) - .child(StatusBar::new()) - } -} diff --git a/crates/storybook/src/stories/components/tab.rs b/crates/storybook/src/stories/components/tab.rs deleted file mode 100644 index 6a154ce644e554eef226fb1aaea21b990f2e6a9e..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/tab.rs +++ /dev/null @@ -1,91 +0,0 @@ -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{h_stack, v_stack, Tab}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TabStory {} - -impl TabStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let git_statuses = GitStatus::iter(); - let fs_statuses = FileSystemStatus::iter(); - - Story::container(cx) - .child(Story::title_for::<_, Tab>(cx)) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "Default")) - .child(Tab::new()), - ), - ) - .child( - h_stack().child( - v_stack().gap_2().child(Story::label(cx, "Current")).child( - h_stack() - .gap_4() - .child(Tab::new().title("Current".to_string()).current(true)) - .child(Tab::new().title("Not Current".to_string()).current(false)), - ), - ), - ) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "Titled")) - .child(Tab::new().title("label".to_string())), - ), - ) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "With Icon")) - .child( - Tab::new() - .title("label".to_string()) - .icon(Some(ui::Icon::Envelope)), - ), - ), - ) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "Close Side")) - .child( - h_stack() - .gap_4() - .child( - Tab::new() - .title("Left".to_string()) - .close_side(IconSide::Left), - ) - .child(Tab::new().title("Right".to_string())), - ), - ), - ) - .child( - v_stack() - .gap_2() - .child(Story::label(cx, "Git Status")) - .child(h_stack().gap_4().children(git_statuses.map(|git_status| { - Tab::new() - .title(git_status.to_string()) - .git_status(git_status) - }))), - ) - .child( - v_stack() - .gap_2() - .child(Story::label(cx, "File System Status")) - .child(h_stack().gap_4().children(fs_statuses.map(|fs_status| { - Tab::new().title(fs_status.to_string()).fs_status(fs_status) - }))), - ) - } -} diff --git a/crates/storybook/src/stories/components/tab_bar.rs b/crates/storybook/src/stories/components/tab_bar.rs deleted file mode 100644 index b5fa45dfd6009b941cddba9a2cb139cb55cd810b..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/tab_bar.rs +++ /dev/null @@ -1,46 +0,0 @@ -use ui::prelude::*; -use ui::{Tab, TabBar}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TabBarStory {} - -impl TabBarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, TabBar>(cx)) - .child(Story::label(cx, "Default")) - .child(TabBar::new(vec![ - Tab::new() - .title("Cargo.toml".to_string()) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("Channels Panel".to_string()) - .current(false), - Tab::new() - .title("channels_panel.rs".to_string()) - .current(true) - .git_status(GitStatus::Modified), - Tab::new() - .title("workspace.rs".to_string()) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("icon_button.rs".to_string()) - .current(false), - Tab::new() - .title("storybook.rs".to_string()) - .current(false) - .git_status(GitStatus::Created), - Tab::new().title("theme.rs".to_string()).current(false), - Tab::new() - .title("theme_registry.rs".to_string()) - .current(false), - Tab::new() - .title("styleable_helpers.rs".to_string()) - .current(false), - ])) - } -} diff --git a/crates/storybook/src/stories/components/terminal.rs b/crates/storybook/src/stories/components/terminal.rs deleted file mode 100644 index 2bce2e27e53c1919aa510f1b9cc0def4578ca3f5..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/terminal.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::Terminal; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TerminalStory {} - -impl TerminalStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Terminal>(cx)) - .child(Story::label(cx, "Default")) - .child(Terminal::new()) - } -} diff --git a/crates/storybook/src/stories/components/theme_selector.rs b/crates/storybook/src/stories/components/theme_selector.rs deleted file mode 100644 index 43e2a704e701e240a02b1bc18cb4bc8dbc575d10..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/theme_selector.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::ThemeSelector; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ThemeSelectorStory {} - -impl ThemeSelectorStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, ThemeSelector>(cx)) - .child(Story::label(cx, "Default")) - .child(ThemeSelector::new()) - } -} diff --git a/crates/storybook/src/stories/components/title_bar.rs b/crates/storybook/src/stories/components/title_bar.rs deleted file mode 100644 index 3c4682b3ba0745ed1d8476d306878a683f22cb40..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/title_bar.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::TitleBar; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TitleBarStory {} - -impl TitleBarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, TitleBar>(cx)) - .child(Story::label(cx, "Default")) - .child(TitleBar::new(cx)) - } -} diff --git a/crates/storybook/src/stories/components/toolbar.rs b/crates/storybook/src/stories/components/toolbar.rs deleted file mode 100644 index 1413c463b4366a2326b195fd9e3472b7acc29d37..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/toolbar.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::path::PathBuf; -use std::str::FromStr; -use std::sync::Arc; - -use ui::prelude::*; -use ui::{theme, Breadcrumb, HighlightColor, HighlightedText, Icon, IconButton, Symbol, Toolbar}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ToolbarStory {} - -impl ToolbarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - struct LeftItemsPayload { - pub theme: Arc, - } - - Story::container(cx) - .child(Story::title_for::<_, Toolbar>(cx)) - .child(Story::label(cx, "Default")) - .child(Toolbar::new( - |_, payload| { - let payload = payload.downcast_ref::().unwrap(); - - let theme = payload.theme.clone(); - - vec![Breadcrumb::new( - PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(), - vec![ - Symbol(vec![ - HighlightedText { - text: "impl ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "ToolbarStory".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "render".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - ], - ) - .into_any()] - }, - Box::new(LeftItemsPayload { - theme: theme.clone(), - }), - |_, _| { - vec![ - IconButton::new(Icon::InlayHint).into_any(), - IconButton::new(Icon::MagnifyingGlass).into_any(), - IconButton::new(Icon::MagicWand).into_any(), - ] - }, - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/traffic_lights.rs b/crates/storybook/src/stories/components/traffic_lights.rs deleted file mode 100644 index 3b759a43a37d3219e15f7a77ad5aa4c93d238b3a..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/components/traffic_lights.rs +++ /dev/null @@ -1,18 +0,0 @@ -use ui::prelude::*; -use ui::TrafficLights; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TrafficLightsStory {} - -impl TrafficLightsStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, TrafficLights>(cx)) - .child(Story::label(cx, "Default")) - .child(TrafficLights::new()) - .child(Story::label(cx, "Unfocused")) - .child(TrafficLights::new().window_has_focus(false)) - } -} diff --git a/crates/storybook/src/stories/elements.rs b/crates/storybook/src/stories/elements.rs deleted file mode 100644 index f7afec4d883ce12d62df968862f9de3265cbad54..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/elements.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod avatar; -pub mod button; -pub mod icon; -pub mod input; -pub mod label; diff --git a/crates/storybook/src/stories/elements/avatar.rs b/crates/storybook/src/stories/elements/avatar.rs deleted file mode 100644 index a277fa6a1e8d5d43f1ee904afa0fe7585a276139..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/elements/avatar.rs +++ /dev/null @@ -1,23 +0,0 @@ -use ui::prelude::*; -use ui::Avatar; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct AvatarStory {} - -impl AvatarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Avatar>(cx)) - .child(Story::label(cx, "Default")) - .child(Avatar::new( - "https://avatars.githubusercontent.com/u/1714999?v=4", - )) - .child(Story::label(cx, "Rounded rectangle")) - .child( - Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4") - .shape(Shape::RoundedRectangle), - ) - } -} diff --git a/crates/storybook/src/stories/elements/button.rs b/crates/storybook/src/stories/elements/button.rs deleted file mode 100644 index 7ff3fdd30c55bf2b9ac7c1713f814c31b659efd5..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/elements/button.rs +++ /dev/null @@ -1,192 +0,0 @@ -use gpui2::elements::div; -use gpui2::geometry::rems; -use gpui2::{Element, IntoElement, ViewContext}; -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{h_stack, v_stack, Button, Icon, IconPosition, Label}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ButtonStory {} - -impl ButtonStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let states = InteractionState::iter(); - - Story::container(cx) - .child(Story::title_for::<_, Button>(cx)) - .child( - div() - .flex() - .gap_8() - .child( - div() - .child(Story::label(cx, "Ghost (Default)")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - .state(state), - ) - }))) - .child(Story::label(cx, "Ghost – Left Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - .icon(Icon::Plus) - .icon_position(IconPosition::Left) - .state(state), - ) - }))) - .child(Story::label(cx, "Ghost – Right Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - .icon(Icon::Plus) - .icon_position(IconPosition::Right) - .state(state), - ) - }))), - ) - .child( - div() - .child(Story::label(cx, "Filled")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state), - ) - }))) - .child(Story::label(cx, "Filled – Left Button")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .icon(Icon::Plus) - .icon_position(IconPosition::Left) - .state(state), - ) - }))) - .child(Story::label(cx, "Filled – Right Button")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .icon(Icon::Plus) - .icon_position(IconPosition::Right) - .state(state), - ) - }))), - ) - .child( - div() - .child(Story::label(cx, "Fixed With")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state) - .width(Some(rems(6.).into())), - ) - }))) - .child(Story::label(cx, "Fixed With – Left Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state) - .icon(Icon::Plus) - .icon_position(IconPosition::Left) - .width(Some(rems(6.).into())), - ) - }))) - .child(Story::label(cx, "Fixed With – Right Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state) - .icon(Icon::Plus) - .icon_position(IconPosition::Right) - .width(Some(rems(6.).into())), - ) - }))), - ), - ) - .child(Story::label(cx, "Button with `on_click`")) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - // NOTE: There currently appears to be a bug in GPUI2 where only the last event handler will fire. - // So adding additional buttons with `on_click`s after this one will cause this `on_click` to not fire. - .on_click(|_view, _cx| println!("Button clicked.")), - ) - } -} diff --git a/crates/storybook/src/stories/elements/icon.rs b/crates/storybook/src/stories/elements/icon.rs deleted file mode 100644 index 66d3abc0b34bf53d77959ad1371f2ffbc4b3e5b0..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/elements/icon.rs +++ /dev/null @@ -1,19 +0,0 @@ -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{Icon, IconElement}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct IconStory {} - -impl IconStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let icons = Icon::iter(); - - Story::container(cx) - .child(Story::title_for::<_, IconElement>(cx)) - .child(Story::label(cx, "All Icons")) - .child(div().flex().gap_3().children(icons.map(IconElement::new))) - } -} diff --git a/crates/storybook/src/stories/elements/input.rs b/crates/storybook/src/stories/elements/input.rs deleted file mode 100644 index 8617b7daaf54f76292619752ce123ea990416731..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/elements/input.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::Input; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct InputStory {} - -impl InputStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Input>(cx)) - .child(Story::label(cx, "Default")) - .child(div().flex().child(Input::new("Search"))) - } -} diff --git a/crates/storybook/src/stories/elements/label.rs b/crates/storybook/src/stories/elements/label.rs deleted file mode 100644 index 1b63cb3a3a41103449555132f2e6bfe8c4a98a34..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/elements/label.rs +++ /dev/null @@ -1,18 +0,0 @@ -use ui::prelude::*; -use ui::Label; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct LabelStory {} - -impl LabelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Label>(cx)) - .child(Story::label(cx, "Default")) - .child(Label::new("Hello, world!")) - .child(Story::label(cx, "Highlighted")) - .child(Label::new("Hello, world!").with_highlights(vec![0, 1, 2, 7, 8, 12])) - } -} diff --git a/crates/storybook/src/stories/kitchen_sink.rs b/crates/storybook/src/stories/kitchen_sink.rs deleted file mode 100644 index ae826f934e8fac4b211086c848a94acff0f35128..0000000000000000000000000000000000000000 --- a/crates/storybook/src/stories/kitchen_sink.rs +++ /dev/null @@ -1,26 +0,0 @@ -use strum::IntoEnumIterator; -use ui::prelude::*; - -use crate::story::Story; -use crate::story_selector::{ComponentStory, ElementStory}; - -#[derive(Element, Default)] -pub struct KitchenSinkStory {} - -impl KitchenSinkStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let element_stories = ElementStory::iter().map(|selector| selector.story()); - let component_stories = ComponentStory::iter().map(|selector| selector.story()); - - Story::container(cx) - .overflow_y_scroll(ScrollState::default()) - .child(Story::title(cx, "Kitchen Sink")) - .child(Story::label(cx, "Elements")) - .child(div().flex().flex_col().children_any(element_stories)) - .child(Story::label(cx, "Components")) - .child(div().flex().flex_col().children_any(component_stories)) - // Add a bit of space at the bottom of the kitchen sink so elements - // don't end up squished right up against the bottom of the screen. - .child(div().p_4()) - } -} diff --git a/crates/storybook/src/story.rs b/crates/storybook/src/story.rs deleted file mode 100644 index 16eae50f889277f5a84e7602c588f33963e73271..0000000000000000000000000000000000000000 --- a/crates/storybook/src/story.rs +++ /dev/null @@ -1,44 +0,0 @@ -use gpui2::elements::div::Div; -use ui::prelude::*; -use ui::theme; - -pub struct Story {} - -impl Story { - pub fn container(cx: &mut ViewContext) -> Div { - let theme = theme(cx); - - div() - .size_full() - .flex() - .flex_col() - .pt_2() - .px_4() - .font("Zed Mono Extended") - .fill(theme.lowest.base.default.background) - } - - pub fn title(cx: &mut ViewContext, title: &str) -> impl Element { - let theme = theme(cx); - - div() - .text_xl() - .text_color(theme.lowest.base.default.foreground) - .child(title.to_owned()) - } - - pub fn title_for(cx: &mut ViewContext) -> impl Element { - Self::title(cx, std::any::type_name::()) - } - - pub fn label(cx: &mut ViewContext, label: &str) -> impl Element { - let theme = theme(cx); - - div() - .mt_4() - .mb_2() - .text_xs() - .text_color(theme.lowest.base.default.foreground) - .child(label.to_owned()) - } -} diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs deleted file mode 100644 index 6b0a9ac78dfeee992ee2ac7592f9102d340a28f1..0000000000000000000000000000000000000000 --- a/crates/storybook/src/story_selector.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::str::FromStr; -use std::sync::OnceLock; - -use anyhow::{anyhow, Context}; -use clap::builder::PossibleValue; -use clap::ValueEnum; -use gpui2::{AnyElement, Element}; -use strum::{EnumIter, EnumString, IntoEnumIterator}; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)] -#[strum(serialize_all = "snake_case")] -pub enum ElementStory { - Avatar, - Button, - Icon, - Input, - Label, -} - -impl ElementStory { - pub fn story(&self) -> AnyElement { - use crate::stories::elements; - - match self { - Self::Avatar => elements::avatar::AvatarStory::default().into_any(), - Self::Button => elements::button::ButtonStory::default().into_any(), - Self::Icon => elements::icon::IconStory::default().into_any(), - Self::Input => elements::input::InputStory::default().into_any(), - Self::Label => elements::label::LabelStory::default().into_any(), - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)] -#[strum(serialize_all = "snake_case")] -pub enum ComponentStory { - AssistantPanel, - Breadcrumb, - Buffer, - ContextMenu, - ChatPanel, - CollabPanel, - Facepile, - Keybinding, - LanguageSelector, - MultiBuffer, - Palette, - Panel, - ProjectPanel, - RecentProjects, - StatusBar, - Tab, - TabBar, - Terminal, - ThemeSelector, - TitleBar, - Toolbar, - TrafficLights, -} - -impl ComponentStory { - pub fn story(&self) -> AnyElement { - use crate::stories::components; - - match self { - Self::AssistantPanel => { - components::assistant_panel::AssistantPanelStory::default().into_any() - } - Self::Breadcrumb => components::breadcrumb::BreadcrumbStory::default().into_any(), - Self::Buffer => components::buffer::BufferStory::default().into_any(), - Self::ContextMenu => components::context_menu::ContextMenuStory::default().into_any(), - Self::ChatPanel => components::chat_panel::ChatPanelStory::default().into_any(), - Self::CollabPanel => components::collab_panel::CollabPanelStory::default().into_any(), - Self::Facepile => components::facepile::FacepileStory::default().into_any(), - Self::Keybinding => components::keybinding::KeybindingStory::default().into_any(), - Self::LanguageSelector => { - components::language_selector::LanguageSelectorStory::default().into_any() - } - Self::MultiBuffer => components::multi_buffer::MultiBufferStory::default().into_any(), - Self::Palette => components::palette::PaletteStory::default().into_any(), - Self::Panel => components::panel::PanelStory::default().into_any(), - Self::ProjectPanel => { - components::project_panel::ProjectPanelStory::default().into_any() - } - Self::RecentProjects => { - components::recent_projects::RecentProjectsStory::default().into_any() - } - Self::StatusBar => components::status_bar::StatusBarStory::default().into_any(), - Self::Tab => components::tab::TabStory::default().into_any(), - Self::TabBar => components::tab_bar::TabBarStory::default().into_any(), - Self::Terminal => components::terminal::TerminalStory::default().into_any(), - Self::ThemeSelector => { - components::theme_selector::ThemeSelectorStory::default().into_any() - } - Self::TitleBar => components::title_bar::TitleBarStory::default().into_any(), - Self::Toolbar => components::toolbar::ToolbarStory::default().into_any(), - Self::TrafficLights => { - components::traffic_lights::TrafficLightsStory::default().into_any() - } - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum StorySelector { - Element(ElementStory), - Component(ComponentStory), - KitchenSink, -} - -impl FromStr for StorySelector { - type Err = anyhow::Error; - - fn from_str(raw_story_name: &str) -> std::result::Result { - let story = raw_story_name.to_ascii_lowercase(); - - if story == "kitchen_sink" { - return Ok(Self::KitchenSink); - } - - if let Some((_, story)) = story.split_once("elements/") { - let element_story = ElementStory::from_str(story) - .with_context(|| format!("story not found for element '{story}'"))?; - - return Ok(Self::Element(element_story)); - } - - if let Some((_, story)) = story.split_once("components/") { - let component_story = ComponentStory::from_str(story) - .with_context(|| format!("story not found for component '{story}'"))?; - - return Ok(Self::Component(component_story)); - } - - Err(anyhow!("story not found for '{raw_story_name}'")) - } -} - -impl StorySelector { - pub fn story(&self) -> AnyElement { - match self { - Self::Element(element_story) => element_story.story(), - Self::Component(component_story) => component_story.story(), - Self::KitchenSink => { - crate::stories::kitchen_sink::KitchenSinkStory::default().into_any() - } - } - } -} - -/// The list of all stories available in the storybook. -static ALL_STORY_SELECTORS: OnceLock> = OnceLock::new(); - -impl ValueEnum for StorySelector { - fn value_variants<'a>() -> &'a [Self] { - let stories = ALL_STORY_SELECTORS.get_or_init(|| { - let element_stories = ElementStory::iter().map(StorySelector::Element); - let component_stories = ComponentStory::iter().map(StorySelector::Component); - - element_stories - .chain(component_stories) - .chain(std::iter::once(StorySelector::KitchenSink)) - .collect::>() - }); - - stories - } - - fn to_possible_value(&self) -> Option { - let value = match self { - Self::Element(story) => format!("elements/{story}"), - Self::Component(story) => format!("components/{story}"), - Self::KitchenSink => "kitchen_sink".to_string(), - }; - - Some(PossibleValue::new(value)) - } -} diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs deleted file mode 100644 index afae0d5ebe0a82f6f8b2dd8f8934c032ad61ddd6..0000000000000000000000000000000000000000 --- a/crates/storybook/src/storybook.rs +++ /dev/null @@ -1,198 +0,0 @@ -#![allow(dead_code, unused_variables)] - -mod stories; -mod story; -mod story_selector; - -use std::{process::Command, sync::Arc}; - -use ::theme as legacy_theme; -use clap::Parser; -use gpui2::{ - serde_json, vec2f, view, Element, IntoElement, ParentElement, RectF, ViewContext, WindowBounds, -}; -use legacy_theme::{ThemeRegistry, ThemeSettings}; -use log::LevelFilter; -use settings::{default_settings, SettingsStore}; -use simplelog::SimpleLogger; -use ui::prelude::*; -use ui::{ElementExt, Theme, WorkspaceElement}; - -use crate::story_selector::StorySelector; - -gpui2::actions! { - storybook, - [ToggleInspector] -} - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Args { - #[arg(value_enum)] - story: Option, - - /// The name of the theme to use in the storybook. - /// - /// If not provided, the default theme will be used. - #[arg(long)] - theme: Option, -} - -async fn watch_zed_changes(fs: Arc) -> Option<()> { - if std::env::var("ZED_HOT_RELOAD").is_err() { - return None; - } - use futures::StreamExt; - let mut events = fs - .watch(".".as_ref(), std::time::Duration::from_millis(100)) - .await; - let mut current_child: Option = None; - while let Some(events) = events.next().await { - if !events.iter().any(|event| { - event - .path - .to_str() - .map(|path| path.contains("/crates/")) - .unwrap_or_default() - }) { - continue; - } - let child = current_child.take().map(|mut child| child.kill()); - log::info!("Storybook changed, rebuilding..."); - current_child = Some( - Command::new("cargo") - .args(["run", "-p", "storybook"]) - .spawn() - .ok()?, - ); - } - Some(()) -} - -fn main() { - SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); - - let args = Args::parse(); - - let fs = Arc::new(fs::RealFs); - - gpui2::App::new(Assets).unwrap().run(move |cx| { - let mut store = SettingsStore::default(); - store - .set_default_settings(default_settings().as_ref(), cx) - .unwrap(); - cx.set_global(store); - legacy_theme::init(Assets, cx); - // load_embedded_fonts(cx.platform().as_ref()); - - let theme_registry = cx.global::>(); - - let theme_override = args - .theme - .and_then(|theme| { - theme_registry - .list_names(true) - .find(|known_theme| theme == *known_theme) - }) - .and_then(|theme_name| theme_registry.get(&theme_name).ok()); - - cx.spawn(|_| async move { - watch_zed_changes(fs).await; - }) - .detach(); - cx.add_window( - gpui2::WindowOptions { - bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1700., 980.))), - center: true, - ..Default::default() - }, - |cx| match args.story { - Some(selector) => view(move |cx| { - render_story( - &mut ViewContext::new(cx), - theme_override.clone(), - div().flex().flex_col().h_full().child_any(selector.story()), - ) - }), - None => view(move |cx| { - render_story( - &mut ViewContext::new(cx), - theme_override.clone(), - WorkspaceElement::default(), - ) - }), - }, - ); - cx.platform().activate(true); - }); -} - -fn render_story>( - cx: &mut ViewContext, - theme_override: Option>, - story: S, -) -> impl Element { - let theme = current_theme(cx, theme_override); - - story.into_element().themed(theme) -} - -fn current_theme( - cx: &mut ViewContext, - theme_override: Option>, -) -> Theme { - let legacy_theme = - theme_override.unwrap_or_else(|| settings::get::(cx).theme.clone()); - - let new_theme: Theme = serde_json::from_value(legacy_theme.base_theme.clone()).unwrap(); - - add_base_theme_to_legacy_theme(&legacy_theme, new_theme) -} - -// Nathan: During the transition to gpui2, we will include the base theme on the legacy Theme struct. -fn add_base_theme_to_legacy_theme(legacy_theme: &legacy_theme::Theme, new_theme: Theme) -> Theme { - legacy_theme - .deserialized_base_theme - .lock() - .get_or_insert_with(|| Box::new(new_theme)) - .downcast_ref::() - .unwrap() - .clone() -} - -use anyhow::{anyhow, Result}; -use gpui2::AssetSource; -use rust_embed::RustEmbed; - -#[derive(RustEmbed)] -#[folder = "../../assets"] -#[include = "themes/**/*"] -#[include = "fonts/**/*"] -#[include = "icons/**/*"] -#[exclude = "*.DS_Store"] -pub struct Assets; - -impl AssetSource for Assets { - fn load(&self, path: &str) -> Result> { - Self::get(path) - .map(|f| f.data) - .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) - } - - fn list(&self, path: &str) -> Vec> { - Self::iter().filter(|p| p.starts_with(path)).collect() - } -} - -// fn load_embedded_fonts(platform: &dyn gpui2::Platform) { -// let font_paths = Assets.list("fonts"); -// let mut embedded_fonts = Vec::new(); -// for font_path in &font_paths { -// if font_path.ends_with(".ttf") { -// let font_path = &*font_path; -// let font_bytes = Assets.load(font_path).unwrap().to_vec(); -// embedded_fonts.push(Arc::from(font_bytes)); -// } -// } -// platform.fonts().add_fonts(&embedded_fonts).unwrap(); -// } diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml deleted file mode 100644 index 7bd9d912a0fb05ae0b921e3e5c7b7f74debb984e..0000000000000000000000000000000000000000 --- a/crates/ui/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "ui" -version = "0.1.0" -edition = "2021" -publish = false - -[dependencies] -anyhow.workspace = true -chrono = "0.4" -gpui2 = { path = "../gpui2" } -serde.workspace = true -settings = { path = "../settings" } -smallvec.workspace = true -strum = { version = "0.25.0", features = ["derive"] } -theme = { path = "../theme" } -rand = "0.8" diff --git a/crates/ui/docs/_project.md b/crates/ui/docs/_project.md deleted file mode 100644 index a3b72a9d615892d1e185a883d39482d9a84672ce..0000000000000000000000000000000000000000 --- a/crates/ui/docs/_project.md +++ /dev/null @@ -1,13 +0,0 @@ -## Project Plan - -- Port existing UI to GPUI2 -- Update UI in places that GPUI1 was limiting us* -- Understand the needs &/|| struggles the engineers have been having with building UI in the past and address as many of those as possible as we go -- Ship a simple, straightforward system with documentation that is easy to use to build UI - -## Component Classification - -To simplify the understanding of components and minimize unnecessary cognitive load, let's categorize components into two types: - -- An element refers to a standalone component that doesn't import any other 'ui' components. -- A component indicates a component that utilizes or imports other 'ui' components. diff --git a/crates/ui/docs/elevation.md b/crates/ui/docs/elevation.md deleted file mode 100644 index bd34de3396dea1f1042bb267f8a23abe6a45441f..0000000000000000000000000000000000000000 --- a/crates/ui/docs/elevation.md +++ /dev/null @@ -1,57 +0,0 @@ -# Elevation - -Elevation in Zed applies to all surfaces and components. Elevation is categorized into levels. - -Elevation accomplishes the following: -- Allows surfaces to move in front of or behind others, such as content scrolling beneath app top bars. -- Reflects spatial relationships, for instance, how a floating action button’s shadow intimates its disconnection from a collection of cards. -- Directs attention to structures at the highest elevation, like a temporary dialog arising in front of other surfaces. - -Elevations are the initial elevation values assigned to components by default. - -Components may transition to a higher elevation in some cases, like user interations. - -On such occasions, components transition to predetermined dynamic elevation offsets. These are the typical elevations to which components move when they are not at rest. - -## Understanding Elevation - -Elevation can be thought of as the physical closeness of an element to the user. Elements with lower elevations are physically further away from the user on the z-axis and appear to be underneath elements with higher elevations. - -Material Design 3 has a some great visualizations of elevation that may be helpful to understanding the mental modal of elevation. [Material Design – Elevation](https://m3.material.io/styles/elevation/overview) - -## Elevation Levels - -Zed integrates six unique elevation levels in its design system. The elevation of a surface is expressed as a whole number ranging from 0 to 5, both numbers inclusive. A component’s elevation is ascertained by combining the component’s resting elevation with any dynamic elevation offsets. - -The levels are detailed as follows: - -0. App Background -1. UI Surface -2. Elevated Elements -3. Wash -4. Focused Element -5. Dragged Element - -### 0. App Background - -The app background constitutes the lowest elevation layer, appearing behind all other surfaces and components. It is predominantly used for the background color of the app. - -### 1. UI Surface - -The UI Surface is the standard elevation for components and is placed above the app background. It is generally used for the background color of the app bar, card, and sheet. - -### 2. Elevated Elements - -Elevated elements appear above the UI surface layer surfaces and components. Elevated elements are predominantly used for creating popovers, context menus, and tooltips. - -### 3. Wash - -Wash denotes a distinct elevation reserved to isolate app UI layers from high elevation components such as modals, notifications, and overlaid panels. The wash may not consistently be visible when these components are active. This layer is often referred to as a scrim or overlay and the background color of the wash is typically deployed in its design. - -### 4. Focused Element - -Focused elements obtain a higher elevation above surfaces and components at wash elevation. They are often used for modals, notifications, and overlaid panels and indicate that they are the sole element the user is interacting with at the moment. - -### 5. Dragged Element - -Dragged elements gain the highest elevation, thus appearing above surfaces and components at the elevation of focused elements. These are typically used for elements that are being dragged, following the cursor diff --git a/crates/ui/src/children.rs b/crates/ui/src/children.rs deleted file mode 100644 index 947f8d98cfc07421a38ac6927ffe756a691b8fed..0000000000000000000000000000000000000000 --- a/crates/ui/src/children.rs +++ /dev/null @@ -1,7 +0,0 @@ -use std::any::Any; - -use gpui2::{AnyElement, ViewContext}; - -pub type HackyChildren = fn(&mut ViewContext, &dyn Any) -> Vec>; - -pub type HackyChildrenPayload = Box; diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs deleted file mode 100644 index 65b021856588c0bd881a561eb9e56683e8dd4c4c..0000000000000000000000000000000000000000 --- a/crates/ui/src/components.rs +++ /dev/null @@ -1,163 +0,0 @@ -mod assistant_panel; -mod breadcrumb; -mod buffer; -mod chat_panel; -mod collab_panel; -mod command_palette; -mod context_menu; -mod editor_pane; -mod facepile; -mod icon_button; -mod keybinding; -mod language_selector; -mod list; -mod multi_buffer; -mod palette; -mod panel; -mod panes; -mod player_stack; -mod project_panel; -mod recent_projects; -mod status_bar; -mod tab; -mod tab_bar; -mod terminal; -mod theme_selector; -mod title_bar; -mod toast; -mod toolbar; -mod traffic_lights; -mod workspace; - -pub use assistant_panel::*; -pub use breadcrumb::*; -pub use buffer::*; -pub use chat_panel::*; -pub use collab_panel::*; -pub use command_palette::*; -pub use context_menu::*; -pub use editor_pane::*; -pub use facepile::*; -pub use icon_button::*; -pub use keybinding::*; -pub use language_selector::*; -pub use list::*; -pub use multi_buffer::*; -pub use palette::*; -pub use panel::*; -pub use panes::*; -pub use player_stack::*; -pub use project_panel::*; -pub use recent_projects::*; -pub use status_bar::*; -pub use tab::*; -pub use tab_bar::*; -pub use terminal::*; -pub use theme_selector::*; -pub use title_bar::*; -pub use toast::*; -pub use toolbar::*; -pub use traffic_lights::*; -pub use workspace::*; - -// Nate: Commenting this out for now, unsure if we need it. - -// use std::marker::PhantomData; -// use std::rc::Rc; - -// use gpui2::elements::div; -// use gpui2::interactive::Interactive; -// use gpui2::platform::MouseButton; -// use gpui2::{ArcCow, Element, EventContext, IntoElement, ParentElement, ViewContext}; - -// struct ButtonHandlers { -// click: Option)>>, -// } - -// impl Default for ButtonHandlers { -// fn default() -> Self { -// Self { click: None } -// } -// } - -// #[derive(Element)] -// pub struct Button { -// handlers: ButtonHandlers, -// label: Option>, -// icon: Option>, -// data: Rc, -// view_type: PhantomData, -// } - -// // Impl block for buttons without data. -// // See below for an impl block for any button. -// impl Button { -// fn new() -> Self { -// Self { -// handlers: ButtonHandlers::default(), -// label: None, -// icon: None, -// data: Rc::new(()), -// view_type: PhantomData, -// } -// } - -// pub fn data(self, data: D) -> Button { -// Button { -// handlers: ButtonHandlers::default(), -// label: self.label, -// icon: self.icon, -// data: Rc::new(data), -// view_type: PhantomData, -// } -// } -// } - -// // Impl block for button regardless of its data type. -// impl Button { -// pub fn label(mut self, label: impl Into>) -> Self { -// self.label = Some(label.into()); -// self -// } - -// pub fn icon(mut self, icon: impl Into>) -> Self { -// self.icon = Some(icon.into()); -// self -// } - -// pub fn on_click( -// mut self, -// handler: impl Fn(&mut V, &D, &mut EventContext) + 'static, -// ) -> Self { -// self.handlers.click = Some(Rc::new(handler)); -// self -// } -// } - -// pub fn button() -> Button { -// Button::new() -// } - -// impl Button { -// fn render( -// &mut self, -// view: &mut V, -// cx: &mut ViewContext, -// ) -> impl IntoElement + Interactive { -// // let colors = &cx.theme::().colors; - -// let button = div() -// // .fill(colors.error(0.5)) -// .h_4() -// .children(self.label.clone()); - -// if let Some(handler) = self.handlers.click.clone() { -// let data = self.data.clone(); -// button.on_mouse_down(MouseButton::Left, move |view, event, cx| { -// handler(view, data.as_ref(), cx) -// }) -// } else { -// button -// } -// } -// } diff --git a/crates/ui/src/components/assistant_panel.rs b/crates/ui/src/components/assistant_panel.rs deleted file mode 100644 index a0a0c5288219b30bdb9353fed38a9a11b389a44b..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/assistant_panel.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::geometry::rems; - -use crate::prelude::*; -use crate::theme::theme; -use crate::{Icon, IconButton, Label, Panel, PanelSide}; - -#[derive(Element)] -pub struct AssistantPanel { - view_type: PhantomData, - scroll_state: ScrollState, - current_side: PanelSide, -} - -impl AssistantPanel { - pub fn new() -> Self { - Self { - view_type: PhantomData, - scroll_state: ScrollState::default(), - current_side: PanelSide::default(), - } - } - - pub fn side(mut self, side: PanelSide) -> Self { - self.current_side = side; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - struct PanelPayload { - pub scroll_state: ScrollState, - } - - Panel::new( - self.scroll_state.clone(), - |_, payload| { - let payload = payload.downcast_ref::().unwrap(); - - vec![div() - .flex() - .flex_col() - .h_full() - .px_2() - .gap_2() - // Header - .child( - div() - .flex() - .justify_between() - .gap_2() - .child( - div() - .flex() - .child(IconButton::new(Icon::Menu)) - .child(Label::new("New Conversation")), - ) - .child( - div() - .flex() - .items_center() - .gap_px() - .child(IconButton::new(Icon::SplitMessage)) - .child(IconButton::new(Icon::Quote)) - .child(IconButton::new(Icon::MagicWand)) - .child(IconButton::new(Icon::Plus)) - .child(IconButton::new(Icon::Maximize)), - ), - ) - // Chat Body - .child( - div() - .w_full() - .flex() - .flex_col() - .gap_3() - .overflow_y_scroll(payload.scroll_state.clone()) - .child(Label::new("Is this thing on?")), - ) - .into_any()] - }, - Box::new(PanelPayload { - scroll_state: self.scroll_state.clone(), - }), - ) - .side(self.current_side) - .width(rems(32.)) - } -} diff --git a/crates/ui/src/components/breadcrumb.rs b/crates/ui/src/components/breadcrumb.rs deleted file mode 100644 index c14e89ee7b6793196d55d15b391a43d51e307873..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/breadcrumb.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::path::PathBuf; - -use gpui2::elements::div::Div; - -use crate::{h_stack, theme}; -use crate::{prelude::*, HighlightedText}; - -#[derive(Clone)] -pub struct Symbol(pub Vec); - -#[derive(Element)] -pub struct Breadcrumb { - path: PathBuf, - symbols: Vec, -} - -impl Breadcrumb { - pub fn new(path: PathBuf, symbols: Vec) -> Self { - Self { path, symbols } - } - - fn render_separator(&self, theme: &Theme) -> Div { - div() - .child(" › ") - .text_color(HighlightColor::Default.hsla(theme)) - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let symbols_len = self.symbols.len(); - - h_stack() - .px_1() - // TODO: Read font from theme (or settings?). - .font("Zed Mono Extended") - .text_sm() - .text_color(theme.middle.base.default.foreground) - .rounded_md() - .hover() - .fill(theme.highest.base.hovered.background) - .child(self.path.clone().to_str().unwrap().to_string()) - .child(if !self.symbols.is_empty() { - self.render_separator(&theme) - } else { - div() - }) - .child( - div().flex().children( - self.symbols - .iter() - .enumerate() - // TODO: Could use something like `intersperse` here instead. - .flat_map(|(ix, symbol)| { - let mut items = - vec![div().flex().children(symbol.0.iter().map(|segment| { - div().child(segment.text.clone()).text_color(segment.color) - }))]; - - let is_last_segment = ix == symbols_len - 1; - if !is_last_segment { - items.push(self.render_separator(&theme)); - } - - items - }) - .collect::>(), - ), - ) - } -} diff --git a/crates/ui/src/components/buffer.rs b/crates/ui/src/components/buffer.rs deleted file mode 100644 index 00e5daee55c5d5c542daf7f1ed3d8418b0662a13..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/buffer.rs +++ /dev/null @@ -1,233 +0,0 @@ -use gpui2::{Hsla, WindowContext}; - -use crate::prelude::*; -use crate::{h_stack, theme, v_stack, Icon, IconElement}; - -#[derive(Default, PartialEq, Copy, Clone)] -pub struct PlayerCursor { - color: Hsla, - index: usize, -} - -#[derive(Default, PartialEq, Clone)] -pub struct HighlightedText { - pub text: String, - pub color: Hsla, -} - -#[derive(Default, PartialEq, Clone)] -pub struct HighlightedLine { - pub highlighted_texts: Vec, -} - -#[derive(Default, PartialEq, Clone)] -pub struct BufferRow { - pub line_number: usize, - pub code_action: bool, - pub current: bool, - pub line: Option, - pub cursors: Option>, - pub status: GitStatus, - pub show_line_number: bool, -} - -#[derive(Clone)] -pub struct BufferRows { - pub show_line_numbers: bool, - pub rows: Vec, -} - -impl Default for BufferRows { - fn default() -> Self { - Self { - show_line_numbers: true, - rows: vec![BufferRow { - line_number: 1, - code_action: false, - current: true, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number: true, - }], - } - } -} - -impl BufferRow { - pub fn new(line_number: usize) -> Self { - Self { - line_number, - code_action: false, - current: false, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number: true, - } - } - - pub fn set_line(mut self, line: Option) -> Self { - self.line = line; - self - } - - pub fn set_cursors(mut self, cursors: Option>) -> Self { - self.cursors = cursors; - self - } - - pub fn add_cursor(mut self, cursor: PlayerCursor) -> Self { - if let Some(cursors) = &mut self.cursors { - cursors.push(cursor); - } else { - self.cursors = Some(vec![cursor]); - } - self - } - - pub fn set_status(mut self, status: GitStatus) -> Self { - self.status = status; - self - } - - pub fn set_show_line_number(mut self, show_line_number: bool) -> Self { - self.show_line_number = show_line_number; - self - } - - pub fn set_code_action(mut self, code_action: bool) -> Self { - self.code_action = code_action; - self - } - - pub fn set_current(mut self, current: bool) -> Self { - self.current = current; - self - } -} - -#[derive(Element, Clone)] -pub struct Buffer { - scroll_state: ScrollState, - rows: Option, - readonly: bool, - language: Option, - title: Option, - path: Option, -} - -impl Buffer { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - rows: Some(BufferRows::default()), - readonly: false, - language: None, - title: Some("untitled".to_string()), - path: None, - } - } - - pub fn bind_scroll_state(&mut self, scroll_state: ScrollState) { - self.scroll_state = scroll_state; - } - - pub fn set_title>>(mut self, title: T) -> Self { - self.title = title.into(); - self - } - - pub fn set_path>>(mut self, path: P) -> Self { - self.path = path.into(); - self - } - - pub fn set_readonly(mut self, readonly: bool) -> Self { - self.readonly = readonly; - self - } - - pub fn set_rows>>(mut self, rows: R) -> Self { - self.rows = rows.into(); - self - } - - pub fn set_language>>(mut self, language: L) -> Self { - self.language = language.into(); - self - } - - fn render_row(row: BufferRow, cx: &WindowContext) -> impl IntoElement { - let theme = theme(cx); - let system_color = SystemColor::new(); - - let line_background = if row.current { - theme.middle.base.default.background - } else { - system_color.transparent - }; - - let line_number_color = if row.current { - HighlightColor::Default.hsla(&theme) - } else { - HighlightColor::Comment.hsla(&theme) - }; - - h_stack() - .fill(line_background) - .w_full() - .gap_2() - .px_1() - .child( - h_stack() - .w_4() - .h_full() - .px_0p5() - .when(row.code_action, |c| { - div().child(IconElement::new(Icon::Bolt)) - }), - ) - .when(row.show_line_number, |this| { - this.child( - h_stack().justify_end().px_0p5().w_3().child( - div() - .text_color(line_number_color) - .child(row.line_number.to_string()), - ), - ) - }) - .child(div().mx_0p5().w_1().h_full().fill(row.status.hsla(cx))) - .children(row.line.map(|line| { - div() - .flex() - .children(line.highlighted_texts.iter().map(|highlighted_text| { - div() - .text_color(highlighted_text.color) - .child(highlighted_text.text.clone()) - })) - })) - } - - fn render_rows(&self, cx: &WindowContext) -> Vec> { - match &self.rows { - Some(rows) => rows - .rows - .iter() - .map(|row| Self::render_row(row.clone(), cx)) - .collect(), - None => vec![], - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let rows = self.render_rows(cx); - v_stack() - .flex_1() - .w_full() - .h_full() - .fill(theme.highest.base.default.background) - .children(rows) - } -} diff --git a/crates/ui/src/components/chat_panel.rs b/crates/ui/src/components/chat_panel.rs deleted file mode 100644 index 5ae66967b62518b62bd668da58308ab5f6c6d155..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/chat_panel.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::marker::PhantomData; - -use chrono::NaiveDateTime; - -use crate::prelude::*; -use crate::theme::theme; -use crate::{Icon, IconButton, Input, Label, LabelColor}; - -#[derive(Element)] -pub struct ChatPanel { - view_type: PhantomData, - scroll_state: ScrollState, - messages: Vec, -} - -impl ChatPanel { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - messages: Vec::new(), - } - } - - pub fn with_messages(mut self, messages: Vec) -> Self { - self.messages = messages; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_col() - .justify_between() - .h_full() - .px_2() - .gap_2() - // Header - .child( - div() - .flex() - .justify_between() - .py_2() - .child(div().flex().child(Label::new("#design"))) - .child( - div() - .flex() - .items_center() - .gap_px() - .child(IconButton::new(Icon::File)) - .child(IconButton::new(Icon::AudioOn)), - ), - ) - .child( - div() - .flex() - .flex_col() - // Chat Body - .child( - div() - .w_full() - .flex() - .flex_col() - .gap_3() - .overflow_y_scroll(self.scroll_state.clone()) - .children(self.messages.clone()), - ) - // Composer - .child(div().flex().my_2().child(Input::new("Message #design"))), - ) - } -} - -#[derive(Element, Clone)] -pub struct ChatMessage { - author: String, - text: String, - sent_at: NaiveDateTime, -} - -impl ChatMessage { - pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self { - Self { - author, - text, - sent_at, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div() - .flex() - .flex_col() - .child( - div() - .flex() - .gap_2() - .child(Label::new(self.author.clone())) - .child( - Label::new(self.sent_at.format("%m/%d/%Y").to_string()) - .color(LabelColor::Muted), - ), - ) - .child(div().child(Label::new(self.text.clone()))) - } -} diff --git a/crates/ui/src/components/collab_panel.rs b/crates/ui/src/components/collab_panel.rs deleted file mode 100644 index 14dd43294f838638e0361b954b65777c117a19b0..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/collab_panel.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::elements::{img, svg}; -use gpui2::ArcCow; - -use crate::prelude::*; -use crate::theme::{theme, Theme}; -use crate::{ - static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List, - ListHeader, ToggleState, -}; - -#[derive(Element)] -pub struct CollabPanel { - view_type: PhantomData, - scroll_state: ScrollState, -} - -impl CollabPanel { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - v_stack() - .w_64() - .h_full() - .fill(theme.middle.base.default.background) - .child( - v_stack() - .w_full() - .overflow_y_scroll(self.scroll_state.clone()) - .child( - div() - .fill(theme.lowest.base.default.background) - .pb_1() - .border_color(theme.lowest.base.default.border) - .border_b() - .child( - List::new(static_collab_panel_current_call()) - .header( - ListHeader::new("CRDB") - .left_icon(Icon::Hash.into()) - .set_toggle(ToggleState::Toggled), - ) - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - v_stack().py_1().child( - List::new(static_collab_panel_channels()) - .header( - ListHeader::new("CHANNELS").set_toggle(ToggleState::Toggled), - ) - .empty_message("No channels yet. Add a channel to get started.") - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - v_stack().py_1().child( - List::new(static_collab_panel_current_call()) - .header( - ListHeader::new("CONTACTS – ONLINE") - .set_toggle(ToggleState::Toggled), - ) - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - v_stack().py_1().child( - List::new(static_collab_panel_current_call()) - .header( - ListHeader::new("CONTACTS – OFFLINE") - .set_toggle(ToggleState::NotToggled), - ) - .set_toggle(ToggleState::NotToggled), - ), - ), - ) - .child( - div() - .h_7() - .px_2() - .border_t() - .border_color(theme.middle.variant.default.border) - .flex() - .items_center() - .child( - div() - .text_sm() - .text_color(theme.middle.variant.default.foreground) - .child("Find..."), - ), - ) - } - - fn list_section_header( - &self, - label: impl Into>, - expanded: bool, - theme: &Theme, - ) -> impl Element { - div() - .h_7() - .px_2() - .flex() - .justify_between() - .items_center() - .child(div().flex().gap_1().text_sm().child(label)) - .child( - div().flex().h_full().gap_1().items_center().child( - svg() - .path(if expanded { - "icons/caret_down.svg" - } else { - "icons/caret_up.svg" - }) - .w_3p5() - .h_3p5() - .fill(theme.middle.variant.default.foreground), - ), - ) - } - - fn list_item( - &self, - avatar_uri: impl Into>, - label: impl Into>, - theme: &Theme, - ) -> impl Element { - div() - .h_7() - .px_2() - .flex() - .items_center() - .hover() - .fill(theme.lowest.variant.hovered.background) - .active() - .fill(theme.lowest.variant.pressed.background) - .child( - div() - .flex() - .items_center() - .gap_1() - .text_sm() - .child( - img() - .uri(avatar_uri) - .size_3p5() - .rounded_full() - .fill(theme.middle.positive.default.foreground), - ) - .child(label), - ) - } -} diff --git a/crates/ui/src/components/command_palette.rs b/crates/ui/src/components/command_palette.rs deleted file mode 100644 index 797876c05e61f8b257ceba7d47b1e59c50c5c967..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/command_palette.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{example_editor_actions, OrderMethod, Palette}; - -#[derive(Element)] -pub struct CommandPalette { - view_type: PhantomData, - scroll_state: ScrollState, -} - -impl CommandPalette { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(example_editor_actions()) - .placeholder("Execute a command...") - .empty_string("No items found.") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs deleted file mode 100644 index 0808bf655688bd104962ed63d5f19d1eef1f9995..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/context_menu.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::prelude::*; -use crate::theme::theme; -use crate::{ - v_stack, Label, List, ListEntry, ListItem, ListItemVariant, ListSeparator, ListSubHeader, -}; - -#[derive(Clone)] -pub enum ContextMenuItem { - Header(&'static str), - Entry(Label), - Separator, -} - -impl ContextMenuItem { - fn to_list_item(self) -> ListItem { - match self { - ContextMenuItem::Header(label) => ListSubHeader::new(label).into(), - ContextMenuItem::Entry(label) => { - ListEntry::new(label).variant(ListItemVariant::Inset).into() - } - ContextMenuItem::Separator => ListSeparator::new().into(), - } - } - pub fn header(label: &'static str) -> Self { - Self::Header(label) - } - pub fn separator() -> Self { - Self::Separator - } - pub fn entry(label: Label) -> Self { - Self::Entry(label) - } -} - -#[derive(Element)] -pub struct ContextMenu { - items: Vec, -} - -impl ContextMenu { - pub fn new(items: impl IntoIterator) -> Self { - Self { - items: items.into_iter().collect(), - } - } - fn render(&mut self, view: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - v_stack() - .flex() - .fill(theme.lowest.base.default.background) - .border() - .border_color(theme.lowest.base.default.border) - .child( - List::new( - self.items - .clone() - .into_iter() - .map(ContextMenuItem::to_list_item) - .collect(), - ) - .set_toggle(ToggleState::Toggled), - ) - //div().p_1().children(self.items.clone()) - } -} diff --git a/crates/ui/src/components/editor_pane.rs b/crates/ui/src/components/editor_pane.rs deleted file mode 100644 index 561081164c8f9c8982ef2915ba5a612428f64ed4..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/editor_pane.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::marker::PhantomData; -use std::path::PathBuf; - -use crate::prelude::*; -use crate::{v_stack, Breadcrumb, Buffer, Icon, IconButton, Symbol, Tab, TabBar, Toolbar}; - -pub struct Editor { - pub tabs: Vec, - pub path: PathBuf, - pub symbols: Vec, - pub buffer: Buffer, -} - -#[derive(Element)] -pub struct EditorPane { - view_type: PhantomData, - editor: Editor, -} - -impl EditorPane { - pub fn new(editor: Editor) -> Self { - Self { - view_type: PhantomData, - editor, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - struct LeftItemsPayload { - path: PathBuf, - symbols: Vec, - } - - v_stack() - .w_full() - .h_full() - .flex_1() - .child(TabBar::new(self.editor.tabs.clone())) - .child(Toolbar::new( - |_, payload| { - let payload = payload.downcast_ref::().unwrap(); - - vec![Breadcrumb::new(payload.path.clone(), payload.symbols.clone()).into_any()] - }, - Box::new(LeftItemsPayload { - path: self.editor.path.clone(), - symbols: self.editor.symbols.clone(), - }), - |_, _| { - vec![ - IconButton::new(Icon::InlayHint).into_any(), - IconButton::new(Icon::MagnifyingGlass).into_any(), - IconButton::new(Icon::MagicWand).into_any(), - ] - }, - Box::new(()), - )) - .child(self.editor.buffer.clone()) - } -} diff --git a/crates/ui/src/components/facepile.rs b/crates/ui/src/components/facepile.rs deleted file mode 100644 index 949e226c257d2eb504ebb00eeb9cd3611d0059ea..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/facepile.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::prelude::*; -use crate::{theme, Avatar, Player}; - -#[derive(Element)] -pub struct Facepile { - players: Vec, -} - -impl Facepile { - pub fn new>(players: P) -> Self { - Self { - players: players.collect(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let player_count = self.players.len(); - let player_list = self.players.iter().enumerate().map(|(ix, player)| { - let isnt_last = ix < player_count - 1; - - div() - .when(isnt_last, |div| div.neg_mr_1()) - .child(Avatar::new(player.avatar_src().to_string())) - }); - div().p_1().flex().items_center().children(player_list) - } -} diff --git a/crates/ui/src/components/icon_button.rs b/crates/ui/src/components/icon_button.rs deleted file mode 100644 index 8ac122d3eb0ecdac5f4ac02666770120ae18e836..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/icon_button.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::prelude::*; -use crate::{theme, Icon, IconColor, IconElement}; - -#[derive(Element)] -pub struct IconButton { - icon: Icon, - color: IconColor, - variant: ButtonVariant, - state: InteractionState, -} - -impl IconButton { - pub fn new(icon: Icon) -> Self { - Self { - icon, - color: IconColor::default(), - variant: ButtonVariant::default(), - state: InteractionState::default(), - } - } - - pub fn icon(mut self, icon: Icon) -> Self { - self.icon = icon; - self - } - - pub fn color(mut self, color: IconColor) -> Self { - self.color = color; - self - } - - pub fn variant(mut self, variant: ButtonVariant) -> Self { - self.variant = variant; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let icon_color = match (self.state, self.color) { - (InteractionState::Disabled, _) => IconColor::Disabled, - _ => self.color, - }; - - let mut div = div(); - if self.variant == ButtonVariant::Filled { - div = div.fill(theme.highest.on.default.background); - } - - div.w_7() - .h_6() - .flex() - .items_center() - .justify_center() - .rounded_md() - .hover() - .fill(theme.highest.base.hovered.background) - .active() - .fill(theme.highest.base.pressed.background) - .child(IconElement::new(self.icon).color(icon_color)) - } -} diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs deleted file mode 100644 index 63c5f11039b752b5d53bb2c12878e33ac017fccd..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/keybinding.rs +++ /dev/null @@ -1,158 +0,0 @@ -use std::collections::HashSet; - -use strum::{EnumIter, IntoEnumIterator}; - -use crate::prelude::*; -use crate::theme; - -#[derive(Element, Clone)] -pub struct Keybinding { - /// A keybinding consists of a key and a set of modifier keys. - /// More then one keybinding produces a chord. - /// - /// This should always contain at least one element. - keybinding: Vec<(String, ModifierKeys)>, -} - -impl Keybinding { - pub fn new(key: String, modifiers: ModifierKeys) -> Self { - Self { - keybinding: vec![(key, modifiers)], - } - } - - pub fn new_chord( - first_note: (String, ModifierKeys), - second_note: (String, ModifierKeys), - ) -> Self { - Self { - keybinding: vec![first_note, second_note], - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div() - .flex() - .gap_2() - .children(self.keybinding.iter().map(|(key, modifiers)| { - div() - .flex() - .gap_1() - .children(ModifierKey::iter().filter_map(|modifier| { - if modifiers.0.contains(&modifier) { - Some(Key::new(modifier.glyph())) - } else { - None - } - })) - .child(Key::new(key.clone())) - })) - } -} - -#[derive(Element)] -pub struct Key { - key: String, -} - -impl Key { - pub fn new(key: K) -> Self - where - K: Into, - { - Self { key: key.into() } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .px_2() - .py_0() - .rounded_md() - .text_sm() - .text_color(theme.lowest.on.default.foreground) - .fill(theme.lowest.on.default.background) - .child(self.key.clone()) - } -} - -// NOTE: The order the modifier keys appear in this enum impacts the order in -// which they are rendered in the UI. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum ModifierKey { - Control, - Alt, - Command, - Shift, -} - -impl ModifierKey { - /// Returns the glyph for the [`ModifierKey`]. - pub fn glyph(&self) -> char { - match self { - Self::Control => '^', - Self::Alt => '⌥', - Self::Command => '⌘', - Self::Shift => '⇧', - } - } -} - -#[derive(Clone)] -pub struct ModifierKeys(HashSet); - -impl ModifierKeys { - pub fn new() -> Self { - Self(HashSet::new()) - } - - pub fn all() -> Self { - Self(HashSet::from_iter(ModifierKey::iter())) - } - - pub fn add(mut self, modifier: ModifierKey) -> Self { - self.0.insert(modifier); - self - } - - pub fn control(mut self, control: bool) -> Self { - if control { - self.0.insert(ModifierKey::Control); - } else { - self.0.remove(&ModifierKey::Control); - } - - self - } - - pub fn alt(mut self, alt: bool) -> Self { - if alt { - self.0.insert(ModifierKey::Alt); - } else { - self.0.remove(&ModifierKey::Alt); - } - - self - } - - pub fn command(mut self, command: bool) -> Self { - if command { - self.0.insert(ModifierKey::Command); - } else { - self.0.remove(&ModifierKey::Command); - } - - self - } - - pub fn shift(mut self, shift: bool) -> Self { - if shift { - self.0.insert(ModifierKey::Shift); - } else { - self.0.remove(&ModifierKey::Shift); - } - - self - } -} diff --git a/crates/ui/src/components/language_selector.rs b/crates/ui/src/components/language_selector.rs deleted file mode 100644 index 124d7f13ee0fbefc94c7629ab12105b8d92ae356..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/language_selector.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::prelude::*; -use crate::{OrderMethod, Palette, PaletteItem}; - -#[derive(Element)] -pub struct LanguageSelector { - scroll_state: ScrollState, -} - -impl LanguageSelector { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(vec![ - PaletteItem::new("C"), - PaletteItem::new("C++"), - PaletteItem::new("CSS"), - PaletteItem::new("Elixir"), - PaletteItem::new("Elm"), - PaletteItem::new("ERB"), - PaletteItem::new("Rust (current)"), - PaletteItem::new("Scheme"), - PaletteItem::new("TOML"), - PaletteItem::new("TypeScript"), - ]) - .placeholder("Select a language...") - .empty_string("No matches") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/list.rs b/crates/ui/src/components/list.rs deleted file mode 100644 index b7dff6b2c59dec0f7f772a1b1f40df8948567850..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/list.rs +++ /dev/null @@ -1,512 +0,0 @@ -use gpui2::elements::div::Div; -use gpui2::{Hsla, WindowContext}; - -use crate::prelude::*; -use crate::{ - h_stack, theme, token, v_stack, Avatar, DisclosureControlVisibility, Icon, IconColor, - IconElement, IconSize, InteractionState, Label, LabelColor, LabelSize, SystemColor, - ToggleState, -}; - -#[derive(Clone, Copy, Default, Debug, PartialEq)] -pub enum ListItemVariant { - /// The list item extends to the far left and right of the list. - #[default] - FullWidth, - Inset, -} - -#[derive(Element, Clone, Copy)] -pub struct ListHeader { - label: &'static str, - left_icon: Option, - variant: ListItemVariant, - state: InteractionState, - toggleable: Toggleable, -} - -impl ListHeader { - pub fn new(label: &'static str) -> Self { - Self { - label, - left_icon: None, - variant: ListItemVariant::default(), - state: InteractionState::default(), - toggleable: Toggleable::default(), - } - } - - pub fn set_toggle(mut self, toggle: ToggleState) -> Self { - self.toggleable = toggle.into(); - self - } - - pub fn set_toggleable(mut self, toggleable: Toggleable) -> Self { - self.toggleable = toggleable; - self - } - - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - fn disclosure_control(&self) -> Div { - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = Toggleable::is_toggled(&self.toggleable); - - match (is_toggleable, is_toggled) { - (false, _) => div(), - (_, true) => div().child(IconElement::new(Icon::ChevronRight).color(IconColor::Muted)), - (_, false) => div().child(IconElement::new(Icon::ChevronDown).size(IconSize::Small)), - } - } - - fn background_color(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self.state { - InteractionState::Hovered => theme.lowest.base.hovered.background, - InteractionState::Active => theme.lowest.base.pressed.background, - InteractionState::Enabled => theme.lowest.on.default.background, - _ => system_color.transparent, - } - } - - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - let system_color = SystemColor::new(); - let background_color = self.background_color(cx); - - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = Toggleable::is_toggled(&self.toggleable); - - let disclosure_control = self.disclosure_control(); - - h_stack() - .flex_1() - .w_full() - .fill(background_color) - .when(self.state == InteractionState::Focused, |this| { - this.border() - .border_color(theme.lowest.accent.default.border) - }) - .relative() - .py_1() - .child( - div() - .h_6() - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) - .flex() - .flex_1() - .w_full() - .gap_1() - .items_center() - .justify_between() - .child( - div() - .flex() - .gap_1() - .items_center() - .children(self.left_icon.map(|i| { - IconElement::new(i) - .color(IconColor::Muted) - .size(IconSize::Small) - })) - .child( - Label::new(self.label) - .color(LabelColor::Muted) - .size(LabelSize::Small), - ), - ) - .child(disclosure_control), - ) - } -} - -#[derive(Element, Clone, Copy)] -pub struct ListSubHeader { - label: &'static str, - left_icon: Option, - variant: ListItemVariant, -} - -impl ListSubHeader { - pub fn new(label: &'static str) -> Self { - Self { - label, - left_icon: None, - variant: ListItemVariant::default(), - } - } - - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - - h_stack().flex_1().w_full().relative().py_1().child( - div() - .h_6() - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) - .flex() - .flex_1() - .w_full() - .gap_1() - .items_center() - .justify_between() - .child( - div() - .flex() - .gap_1() - .items_center() - .children(self.left_icon.map(|i| { - IconElement::new(i) - .color(IconColor::Muted) - .size(IconSize::Small) - })) - .child( - Label::new(self.label) - .color(LabelColor::Muted) - .size(LabelSize::Small), - ), - ), - ) - } -} - -#[derive(Clone)] -pub enum LeftContent { - Icon(Icon), - Avatar(&'static str), -} - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum ListEntrySize { - #[default] - Small, - Medium, -} - -#[derive(Clone, Element)] -pub enum ListItem { - Entry(ListEntry), - Separator(ListSeparator), - Header(ListSubHeader), -} - -impl From for ListItem { - fn from(entry: ListEntry) -> Self { - Self::Entry(entry) - } -} - -impl From for ListItem { - fn from(entry: ListSeparator) -> Self { - Self::Separator(entry) - } -} - -impl From for ListItem { - fn from(entry: ListSubHeader) -> Self { - Self::Header(entry) - } -} - -impl ListItem { - fn render(&mut self, v: &mut V, cx: &mut ViewContext) -> impl IntoElement { - match self { - ListItem::Entry(entry) => div().child(entry.render(v, cx)), - ListItem::Separator(separator) => div().child(separator.render(v, cx)), - ListItem::Header(header) => div().child(header.render(v, cx)), - } - } - pub fn new(label: Label) -> Self { - Self::Entry(ListEntry::new(label)) - } - pub fn as_entry(&mut self) -> Option<&mut ListEntry> { - if let Self::Entry(entry) = self { - Some(entry) - } else { - None - } - } -} - -#[derive(Element, Clone)] -pub struct ListEntry { - disclosure_control_style: DisclosureControlVisibility, - indent_level: u32, - label: Label, - left_content: Option, - variant: ListItemVariant, - size: ListEntrySize, - state: InteractionState, - toggle: Option, -} - -impl ListEntry { - pub fn new(label: Label) -> Self { - Self { - disclosure_control_style: DisclosureControlVisibility::default(), - indent_level: 0, - label, - variant: ListItemVariant::default(), - left_content: None, - size: ListEntrySize::default(), - state: InteractionState::default(), - toggle: None, - } - } - pub fn variant(mut self, variant: ListItemVariant) -> Self { - self.variant = variant; - self - } - pub fn indent_level(mut self, indent_level: u32) -> Self { - self.indent_level = indent_level; - self - } - - pub fn set_toggle(mut self, toggle: ToggleState) -> Self { - self.toggle = Some(toggle); - self - } - - pub fn left_content(mut self, left_content: LeftContent) -> Self { - self.left_content = Some(left_content); - self - } - - pub fn left_icon(mut self, left_icon: Icon) -> Self { - self.left_content = Some(LeftContent::Icon(left_icon)); - self - } - - pub fn left_avatar(mut self, left_avatar: &'static str) -> Self { - self.left_content = Some(LeftContent::Avatar(left_avatar)); - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - pub fn size(mut self, size: ListEntrySize) -> Self { - self.size = size; - self - } - - pub fn disclosure_control_style( - mut self, - disclosure_control_style: DisclosureControlVisibility, - ) -> Self { - self.disclosure_control_style = disclosure_control_style; - self - } - - fn background_color(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self.state { - InteractionState::Hovered => theme.lowest.base.hovered.background, - InteractionState::Active => theme.lowest.base.pressed.background, - InteractionState::Enabled => theme.lowest.on.default.background, - _ => system_color.transparent, - } - } - - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - - fn disclosure_control( - &mut self, - cx: &mut ViewContext, - ) -> Option> { - let theme = theme(cx); - let token = token(); - - let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle { - IconElement::new(Icon::ChevronDown) - } else { - IconElement::new(Icon::ChevronRight) - } - .color(IconColor::Muted) - .size(IconSize::Small); - - match (self.toggle, self.disclosure_control_style) { - (Some(_), DisclosureControlVisibility::OnHover) => { - Some(div().absolute().neg_left_5().child(disclosure_control_icon)) - } - (Some(_), DisclosureControlVisibility::Always) => { - Some(div().child(disclosure_control_icon)) - } - (None, _) => None, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - let system_color = SystemColor::new(); - let background_color = self.background_color(cx); - - let left_content = match self.left_content { - Some(LeftContent::Icon(i)) => { - Some(h_stack().child(IconElement::new(i).size(IconSize::Small))) - } - Some(LeftContent::Avatar(src)) => Some(h_stack().child(Avatar::new(src))), - None => None, - }; - - let sized_item = match self.size { - ListEntrySize::Small => div().h_6(), - ListEntrySize::Medium => div().h_7(), - }; - - div() - .fill(background_color) - .when(self.state == InteractionState::Focused, |this| { - this.border() - .border_color(theme.lowest.accent.default.border) - }) - .relative() - .py_1() - .child( - sized_item - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) - // .ml(rems(0.75 * self.indent_level as f32)) - .children((0..self.indent_level).map(|_| { - div() - .w(token.list_indent_depth) - .h_full() - .flex() - .justify_center() - .child(h_stack().child(div().w_px().h_full()).child( - div().w_px().h_full().fill(theme.middle.base.default.border), - )) - })) - .flex() - .gap_1() - .items_center() - .relative() - .children(self.disclosure_control(cx)) - .children(left_content) - .child(self.label.clone()), - ) - } -} - -#[derive(Clone, Default, Element)] -pub struct ListSeparator; - -impl ListSeparator { - pub fn new() -> Self { - Self::default() - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div().h_px().w_full().fill(theme.lowest.base.default.border) - } -} - -#[derive(Element)] -pub struct List { - items: Vec, - empty_message: &'static str, - header: Option, - toggleable: Toggleable, -} - -impl List { - pub fn new(items: Vec) -> Self { - Self { - items, - empty_message: "No items", - header: None, - toggleable: Toggleable::default(), - } - } - - pub fn empty_message(mut self, empty_message: &'static str) -> Self { - self.empty_message = empty_message; - self - } - - pub fn header(mut self, header: ListHeader) -> Self { - self.header = Some(header); - self - } - - pub fn set_toggle(mut self, toggle: ToggleState) -> Self { - self.toggleable = toggle.into(); - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = Toggleable::is_toggled(&self.toggleable); - - let disclosure_control = if is_toggleable { - IconElement::new(Icon::ChevronRight) - } else { - IconElement::new(Icon::ChevronDown) - }; - - let list_content = match (self.items.is_empty(), is_toggled) { - (_, false) => div(), - (false, _) => div().children(self.items.iter().cloned()), - (true, _) => div().child(Label::new(self.empty_message).color(LabelColor::Muted)), - }; - - v_stack() - .py_1() - .children( - self.header - .clone() - .map(|header| header.set_toggleable(self.toggleable)), - ) - .child(list_content) - } -} diff --git a/crates/ui/src/components/multi_buffer.rs b/crates/ui/src/components/multi_buffer.rs deleted file mode 100644 index d38603457a7d5778c3918d84ab5487830a97e606..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/multi_buffer.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{v_stack, Buffer, Icon, IconButton, Label, LabelSize}; - -#[derive(Element)] -pub struct MultiBuffer { - view_type: PhantomData, - buffers: Vec, -} - -impl MultiBuffer { - pub fn new(buffers: Vec) -> Self { - Self { - view_type: PhantomData, - buffers, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - v_stack() - .w_full() - .h_full() - .flex_1() - .children(self.buffers.clone().into_iter().map(|buffer| { - v_stack() - .child( - div() - .flex() - .items_center() - .justify_between() - .p_4() - .fill(theme.lowest.base.default.background) - .child(Label::new("main.rs").size(LabelSize::Small)) - .child(IconButton::new(Icon::ArrowUpRight)), - ) - .child(buffer) - })) - } -} diff --git a/crates/ui/src/components/palette.rs b/crates/ui/src/components/palette.rs deleted file mode 100644 index 16001e50c1c7031483611dd86ab7e3d8d64efc9e..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/palette.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::theme::theme; -use crate::{h_stack, v_stack, Keybinding, Label, LabelColor}; - -#[derive(Element)] -pub struct Palette { - view_type: PhantomData, - scroll_state: ScrollState, - input_placeholder: &'static str, - empty_string: &'static str, - items: Vec, - default_order: OrderMethod, -} - -impl Palette { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - input_placeholder: "Find something...", - empty_string: "No items found.", - items: vec![], - default_order: OrderMethod::default(), - } - } - - pub fn items(mut self, items: Vec) -> Self { - self.items = items; - self - } - - pub fn placeholder(mut self, input_placeholder: &'static str) -> Self { - self.input_placeholder = input_placeholder; - self - } - - pub fn empty_string(mut self, empty_string: &'static str) -> Self { - self.empty_string = empty_string; - self - } - - // TODO: Hook up sort order - pub fn default_order(mut self, default_order: OrderMethod) -> Self { - self.default_order = default_order; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - v_stack() - .w_96() - .rounded_lg() - .fill(theme.lowest.base.default.background) - .border() - .border_color(theme.lowest.base.default.border) - .child( - v_stack() - .gap_px() - .child(v_stack().py_0p5().px_1().child( - div().px_2().py_0p5().child( - Label::new(self.input_placeholder).color(LabelColor::Placeholder), - ), - )) - .child(div().h_px().w_full().fill(theme.lowest.base.default.border)) - .child( - v_stack() - .py_0p5() - .px_1() - .grow() - .max_h_96() - .overflow_y_scroll(self.scroll_state.clone()) - .children( - vec![if self.items.is_empty() { - Some(h_stack().justify_between().px_2().py_1().child( - Label::new(self.empty_string).color(LabelColor::Muted), - )) - } else { - None - }] - .into_iter() - .flatten(), - ) - .children(self.items.iter().map(|item| { - h_stack() - .justify_between() - .px_2() - .py_0p5() - .rounded_lg() - .hover() - .fill(theme.lowest.base.hovered.background) - .active() - .fill(theme.lowest.base.pressed.background) - .child(item.clone()) - })), - ), - ) - } -} - -#[derive(Element, Clone)] -pub struct PaletteItem { - pub label: &'static str, - pub sublabel: Option<&'static str>, - pub keybinding: Option, -} - -impl PaletteItem { - pub fn new(label: &'static str) -> Self { - Self { - label, - sublabel: None, - keybinding: None, - } - } - - pub fn label(mut self, label: &'static str) -> Self { - self.label = label; - self - } - - pub fn sublabel>>(mut self, sublabel: L) -> Self { - self.sublabel = sublabel.into(); - self - } - - pub fn keybinding(mut self, keybinding: K) -> Self - where - K: Into>, - { - self.keybinding = keybinding.into(); - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_row() - .grow() - .justify_between() - .child( - v_stack() - .child(Label::new(self.label)) - .children(self.sublabel.map(|sublabel| Label::new(sublabel))), - ) - .children(self.keybinding.clone()) - } -} diff --git a/crates/ui/src/components/panel.rs b/crates/ui/src/components/panel.rs deleted file mode 100644 index cbcf502670f4957c90558c84e97a45f44ca93b16..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/panel.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::geometry::AbsoluteLength; - -use crate::prelude::*; -use crate::{theme, token, v_stack}; - -#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub enum PanelAllowedSides { - LeftOnly, - RightOnly, - BottomOnly, - #[default] - LeftAndRight, - All, -} - -impl PanelAllowedSides { - /// Return a `HashSet` that contains the allowable `PanelSide`s. - pub fn allowed_sides(&self) -> HashSet { - match self { - Self::LeftOnly => HashSet::from_iter([PanelSide::Left]), - Self::RightOnly => HashSet::from_iter([PanelSide::Right]), - Self::BottomOnly => HashSet::from_iter([PanelSide::Bottom]), - Self::LeftAndRight => HashSet::from_iter([PanelSide::Left, PanelSide::Right]), - Self::All => HashSet::from_iter([PanelSide::Left, PanelSide::Right, PanelSide::Bottom]), - } - } -} - -#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub enum PanelSide { - #[default] - Left, - Right, - Bottom, -} - -use std::collections::HashSet; - -#[derive(Element)] -pub struct Panel { - view_type: PhantomData, - scroll_state: ScrollState, - current_side: PanelSide, - /// Defaults to PanelAllowedSides::LeftAndRight - allowed_sides: PanelAllowedSides, - initial_width: AbsoluteLength, - width: Option, - children: HackyChildren, - payload: HackyChildrenPayload, -} - -impl Panel { - pub fn new( - scroll_state: ScrollState, - children: HackyChildren, - payload: HackyChildrenPayload, - ) -> Self { - let token = token(); - - Self { - view_type: PhantomData, - scroll_state, - current_side: PanelSide::default(), - allowed_sides: PanelAllowedSides::default(), - initial_width: token.default_panel_size, - width: None, - children, - payload, - } - } - - pub fn initial_width(mut self, initial_width: AbsoluteLength) -> Self { - self.initial_width = initial_width; - self - } - - pub fn width(mut self, width: AbsoluteLength) -> Self { - self.width = Some(width); - self - } - - pub fn allowed_sides(mut self, allowed_sides: PanelAllowedSides) -> Self { - self.allowed_sides = allowed_sides; - self - } - - pub fn side(mut self, side: PanelSide) -> Self { - let allowed_sides = self.allowed_sides.allowed_sides(); - - if allowed_sides.contains(&side) { - self.current_side = side; - } else { - panic!( - "The panel side {:?} was not added as allowed before it was set.", - side - ); - } - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let token = token(); - let theme = theme(cx); - - let panel_base; - let current_width = self.width.unwrap_or(self.initial_width); - - match self.current_side { - PanelSide::Left => { - panel_base = v_stack() - .flex_initial() - .h_full() - .w(current_width) - .fill(theme.middle.base.default.background) - .border_r() - .border_color(theme.middle.base.default.border); - } - PanelSide::Right => { - panel_base = v_stack() - .flex_initial() - .h_full() - .w(current_width) - .fill(theme.middle.base.default.background) - .border_l() - .border_color(theme.middle.base.default.border); - } - PanelSide::Bottom => { - panel_base = v_stack() - .flex_initial() - .w_full() - .h(current_width) - .fill(theme.middle.base.default.background) - .border_t() - .border_color(theme.middle.base.default.border); - } - } - - panel_base.children_any((self.children)(cx, self.payload.as_ref())) - } -} diff --git a/crates/ui/src/components/panes.rs b/crates/ui/src/components/panes.rs deleted file mode 100644 index 2518eea8ee9db3c3353ff59ccc7c241efcaffaf6..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/panes.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::geometry::{Length, Size}; -use gpui2::{hsla, Hsla}; - -use crate::prelude::*; -use crate::theme; - -#[derive(Default, PartialEq)] -pub enum SplitDirection { - #[default] - Horizontal, - Vertical, -} - -#[derive(Element)] -pub struct Pane { - view_type: PhantomData, - scroll_state: ScrollState, - size: Size, - fill: Hsla, - children: HackyChildren, - payload: HackyChildrenPayload, -} - -impl Pane { - pub fn new( - scroll_state: ScrollState, - size: Size, - children: HackyChildren, - payload: HackyChildrenPayload, - ) -> Self { - // Fill is only here for debugging purposes, remove before release - let system_color = SystemColor::new(); - - Self { - view_type: PhantomData, - scroll_state, - size, - fill: hsla(0.3, 0.3, 0.3, 1.), - // fill: system_color.transparent, - children, - payload, - } - } - - pub fn fill(mut self, fill: Hsla) -> Self { - self.fill = fill; - self - } - - fn render(&mut self, view: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_initial() - .fill(self.fill) - .w(self.size.width) - .h(self.size.height) - .overflow_y_scroll(self.scroll_state.clone()) - .children_any((self.children)(cx, self.payload.as_ref())) - } -} - -#[derive(Element)] -pub struct PaneGroup { - view_type: PhantomData, - groups: Vec>, - panes: Vec>, - split_direction: SplitDirection, -} - -impl PaneGroup { - pub fn new_groups(groups: Vec>, split_direction: SplitDirection) -> Self { - Self { - view_type: PhantomData, - groups, - panes: Vec::new(), - split_direction, - } - } - - pub fn new_panes(panes: Vec>, split_direction: SplitDirection) -> Self { - Self { - view_type: PhantomData, - groups: Vec::new(), - panes, - split_direction, - } - } - - fn render(&mut self, view: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - if !self.panes.is_empty() { - let el = div() - .flex() - .flex_1() - .gap_px() - .w_full() - .h_full() - .fill(theme.lowest.base.default.background) - .children(self.panes.iter_mut().map(|pane| pane.render(view, cx))); - - if self.split_direction == SplitDirection::Horizontal { - return el; - } else { - return el.flex_col(); - } - } - - if !self.groups.is_empty() { - let el = div() - .flex() - .flex_1() - .gap_px() - .w_full() - .h_full() - .fill(theme.lowest.base.default.background) - .children(self.groups.iter_mut().map(|group| group.render(view, cx))); - - if self.split_direction == SplitDirection::Horizontal { - return el; - } else { - return el.flex_col(); - } - } - - unreachable!() - } -} diff --git a/crates/ui/src/components/player_stack.rs b/crates/ui/src/components/player_stack.rs deleted file mode 100644 index 7df6f065fb280f4c821f1bb5b2488691d0eef874..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/player_stack.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::prelude::*; -use crate::{Avatar, Facepile, PlayerWithCallStatus}; - -#[derive(Element)] -pub struct PlayerStack { - player_with_call_status: PlayerWithCallStatus, -} - -impl PlayerStack { - pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self { - Self { - player_with_call_status, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let system_color = SystemColor::new(); - let player = self.player_with_call_status.get_player(); - self.player_with_call_status.get_call_status(); - - let followers = self - .player_with_call_status - .get_call_status() - .followers - .as_ref() - .map(|followers| followers.clone()); - - // if we have no followers return a slightly different element - // if mic_status == muted add a red ring to avatar - - div() - .h_full() - .flex() - .flex_col() - .gap_px() - .justify_center() - .child( - div().flex().justify_center().w_full().child( - div() - .w_4() - .h_0p5() - .rounded_sm() - .fill(player.cursor_color(cx)), - ), - ) - .child( - div() - .flex() - .items_center() - .justify_center() - .h_6() - .pl_1() - .rounded_lg() - .fill(if followers.is_none() { - system_color.transparent - } else { - player.selection_color(cx) - }) - .child(Avatar::new(player.avatar_src().to_string())) - .children(followers.map(|followers| { - div().neg_ml_2().child(Facepile::new(followers.into_iter())) - })), - ) - } -} diff --git a/crates/ui/src/components/project_panel.rs b/crates/ui/src/components/project_panel.rs deleted file mode 100644 index 1f32c698e583d79b7fd327b24b9671f1ad0af0ac..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/project_panel.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{ - static_project_panel_project_items, static_project_panel_single_items, theme, Input, List, - ListHeader, -}; - -#[derive(Element)] -pub struct ProjectPanel { - view_type: PhantomData, - scroll_state: ScrollState, -} - -impl ProjectPanel { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_col() - .w_full() - .h_full() - .px_2() - .fill(theme.middle.base.default.background) - .child( - div() - .w_56() - .flex() - .flex_col() - .overflow_y_scroll(ScrollState::default()) - .child( - List::new(static_project_panel_single_items()) - .header(ListHeader::new("FILES").set_toggle(ToggleState::Toggled)) - .empty_message("No files in directory") - .set_toggle(ToggleState::Toggled), - ) - .child( - List::new(static_project_panel_project_items()) - .header(ListHeader::new("PROJECT").set_toggle(ToggleState::Toggled)) - .empty_message("No folders in directory") - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - Input::new("Find something...") - .value("buffe".to_string()) - .state(InteractionState::Focused), - ) - } -} diff --git a/crates/ui/src/components/recent_projects.rs b/crates/ui/src/components/recent_projects.rs deleted file mode 100644 index 6aca6631b9f523bf232d5d6b6d35ae79a11b3174..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/recent_projects.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::prelude::*; -use crate::{OrderMethod, Palette, PaletteItem}; - -#[derive(Element)] -pub struct RecentProjects { - scroll_state: ScrollState, -} - -impl RecentProjects { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(vec![ - PaletteItem::new("zed").sublabel("~/projects/zed"), - PaletteItem::new("saga").sublabel("~/projects/saga"), - PaletteItem::new("journal").sublabel("~/journal"), - PaletteItem::new("dotfiles").sublabel("~/dotfiles"), - PaletteItem::new("zed.dev").sublabel("~/projects/zed.dev"), - PaletteItem::new("laminar").sublabel("~/projects/laminar"), - ]) - .placeholder("Recent Projects...") - .empty_string("No matches") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/status_bar.rs b/crates/ui/src/components/status_bar.rs deleted file mode 100644 index 1970b0bff9807299961741c993491805eba93431..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/status_bar.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::theme::{theme, Theme}; -use crate::{Button, Icon, IconButton, IconColor, ToolDivider}; - -#[derive(Default, PartialEq)] -pub enum Tool { - #[default] - ProjectPanel, - CollaborationPanel, - Terminal, - Assistant, - Feedback, - Diagnostics, -} - -struct ToolGroup { - active_index: Option, - tools: Vec, -} - -impl Default for ToolGroup { - fn default() -> Self { - ToolGroup { - active_index: None, - tools: vec![], - } - } -} - -#[derive(Element)] -pub struct StatusBar { - view_type: PhantomData, - left_tools: Option, - right_tools: Option, - bottom_tools: Option, -} - -impl StatusBar { - pub fn new() -> Self { - Self { - view_type: PhantomData, - left_tools: None, - right_tools: None, - bottom_tools: None, - } - } - - pub fn left_tool(mut self, tool: Tool, active_index: Option) -> Self { - self.left_tools = { - let mut tools = vec![tool]; - tools.extend(self.left_tools.take().unwrap_or_default().tools); - Some(ToolGroup { - active_index, - tools, - }) - }; - self - } - - pub fn right_tool(mut self, tool: Tool, active_index: Option) -> Self { - self.right_tools = { - let mut tools = vec![tool]; - tools.extend(self.left_tools.take().unwrap_or_default().tools); - Some(ToolGroup { - active_index, - tools, - }) - }; - self - } - - pub fn bottom_tool(mut self, tool: Tool, active_index: Option) -> Self { - self.bottom_tools = { - let mut tools = vec![tool]; - tools.extend(self.left_tools.take().unwrap_or_default().tools); - Some(ToolGroup { - active_index, - tools, - }) - }; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .py_0p5() - .px_1() - .flex() - .items_center() - .justify_between() - .w_full() - .fill(theme.lowest.base.default.background) - .child(self.left_tools(&theme)) - .child(self.right_tools(&theme)) - } - - fn left_tools(&self, theme: &Theme) -> impl Element { - div() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::FileTree).color(IconColor::Accent)) - .child(IconButton::new(Icon::Hash)) - .child(ToolDivider::new()) - .child(IconButton::new(Icon::XCircle)) - } - fn right_tools(&self, theme: &Theme) -> impl Element { - div() - .flex() - .items_center() - .gap_2() - .child( - div() - .flex() - .items_center() - .gap_1() - .child(Button::new("116:25")) - .child(Button::new("Rust")), - ) - .child(ToolDivider::new()) - .child( - div() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::Copilot)) - .child(IconButton::new(Icon::Envelope)), - ) - .child(ToolDivider::new()) - .child( - div() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::Terminal)) - .child(IconButton::new(Icon::MessageBubbles)) - .child(IconButton::new(Icon::Ai)), - ) - } -} diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs deleted file mode 100644 index 9eb1122775474382b61151ef6c370438abe38ec6..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/tab.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::prelude::*; -use crate::{theme, Icon, IconColor, IconElement, Label, LabelColor}; - -#[derive(Element, Clone)] -pub struct Tab { - title: String, - icon: Option, - current: bool, - dirty: bool, - fs_status: FileSystemStatus, - git_status: GitStatus, - diagnostic_status: DiagnosticStatus, - close_side: IconSide, -} - -impl Tab { - pub fn new() -> Self { - Self { - title: "untitled".to_string(), - icon: None, - current: false, - dirty: false, - fs_status: FileSystemStatus::None, - git_status: GitStatus::None, - diagnostic_status: DiagnosticStatus::None, - close_side: IconSide::Right, - } - } - - pub fn current(mut self, current: bool) -> Self { - self.current = current; - self - } - - pub fn title(mut self, title: String) -> Self { - self.title = title; - self - } - - pub fn icon(mut self, icon: I) -> Self - where - I: Into>, - { - self.icon = icon.into(); - self - } - - pub fn dirty(mut self, dirty: bool) -> Self { - self.dirty = dirty; - self - } - - pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self { - self.fs_status = fs_status; - self - } - - pub fn git_status(mut self, git_status: GitStatus) -> Self { - self.git_status = git_status; - self - } - - pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self { - self.diagnostic_status = diagnostic_status; - self - } - - pub fn close_side(mut self, close_side: IconSide) -> Self { - self.close_side = close_side; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict; - let is_deleted = self.fs_status == FileSystemStatus::Deleted; - - let label = match (self.git_status, is_deleted) { - (_, true) | (GitStatus::Deleted, false) => Label::new(self.title.clone()) - .color(LabelColor::Hidden) - .set_strikethrough(true), - (GitStatus::None, false) => Label::new(self.title.clone()), - (GitStatus::Created, false) => { - Label::new(self.title.clone()).color(LabelColor::Created) - } - (GitStatus::Modified, false) => { - Label::new(self.title.clone()).color(LabelColor::Modified) - } - (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(LabelColor::Accent), - (GitStatus::Conflict, false) => Label::new(self.title.clone()), - }; - - let close_icon = IconElement::new(Icon::Close).color(IconColor::Muted); - - div() - .px_2() - .py_0p5() - .flex() - .items_center() - .justify_center() - .fill(if self.current { - theme.highest.base.default.background - } else { - theme.middle.base.default.background - }) - .child( - div() - .px_1() - .flex() - .items_center() - .gap_1() - .children(has_fs_conflict.then(|| { - IconElement::new(Icon::ExclamationTriangle) - .size(crate::IconSize::Small) - .color(IconColor::Warning) - })) - .children(self.icon.map(IconElement::new)) - .children(if self.close_side == IconSide::Left { - Some(close_icon.clone()) - } else { - None - }) - .child(label) - .children(if self.close_side == IconSide::Right { - Some(close_icon) - } else { - None - }), - ) - } -} diff --git a/crates/ui/src/components/tab_bar.rs b/crates/ui/src/components/tab_bar.rs deleted file mode 100644 index 8addcb87b1599545355fc01d5b3c972515d251f0..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/tab_bar.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{theme, Icon, IconButton, Tab}; - -#[derive(Element)] -pub struct TabBar { - view_type: PhantomData, - scroll_state: ScrollState, - tabs: Vec, -} - -impl TabBar { - pub fn new(tabs: Vec) -> Self { - Self { - view_type: PhantomData, - scroll_state: ScrollState::default(), - tabs, - } - } - - pub fn bind_scroll_state(&mut self, scroll_state: ScrollState) { - self.scroll_state = scroll_state; - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let can_navigate_back = true; - let can_navigate_forward = false; - - div() - .w_full() - .flex() - .fill(theme.middle.base.default.background) - // Left Side - .child( - div() - .px_1() - .flex() - .flex_none() - .gap_2() - // Nav Buttons - .child( - div() - .flex() - .items_center() - .gap_px() - .child( - IconButton::new(Icon::ArrowLeft) - .state(InteractionState::Enabled.if_enabled(can_navigate_back)), - ) - .child( - IconButton::new(Icon::ArrowRight).state( - InteractionState::Enabled.if_enabled(can_navigate_forward), - ), - ), - ), - ) - .child( - div().w_0().flex_1().h_full().child( - div() - .flex() - .overflow_x_scroll(self.scroll_state.clone()) - .children(self.tabs.clone()), - ), - ) - // Right Side - .child( - div() - .px_1() - .flex() - .flex_none() - .gap_2() - // Nav Buttons - .child( - div() - .flex() - .items_center() - .gap_px() - .child(IconButton::new(Icon::Plus)) - .child(IconButton::new(Icon::Split)), - ), - ) - } -} diff --git a/crates/ui/src/components/terminal.rs b/crates/ui/src/components/terminal.rs deleted file mode 100644 index 909cb886ce01d8a8dff3212e0d5d44d09a433b95..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/terminal.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::sync::Arc; - -use gpui2::geometry::{relative, rems, Size}; - -use crate::prelude::*; -use crate::{theme, Icon, IconButton, Pane, Tab}; - -#[derive(Element)] -pub struct Terminal {} - -impl Terminal { - pub fn new() -> Self { - Self {} - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let can_navigate_back = true; - let can_navigate_forward = false; - - div() - .flex() - .flex_col() - .w_full() - .child( - // Terminal Tabs. - div() - .w_full() - .flex() - .fill(theme.middle.base.default.background) - .child( - div().px_1().flex().flex_none().gap_2().child( - div() - .flex() - .items_center() - .gap_px() - .child( - IconButton::new(Icon::ArrowLeft).state( - InteractionState::Enabled.if_enabled(can_navigate_back), - ), - ) - .child(IconButton::new(Icon::ArrowRight).state( - InteractionState::Enabled.if_enabled(can_navigate_forward), - )), - ), - ) - .child( - div().w_0().flex_1().h_full().child( - div() - .flex() - .child( - Tab::new() - .title("zed — fish".to_string()) - .icon(Icon::Terminal) - .close_side(IconSide::Right) - .current(true), - ) - .child( - Tab::new() - .title("zed — fish".to_string()) - .icon(Icon::Terminal) - .close_side(IconSide::Right) - .current(false), - ), - ), - ), - ) - // Terminal Pane. - .child(Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: rems(36.).into(), - }, - |_, payload| { - let theme = payload.downcast_ref::>().unwrap(); - - vec![crate::static_data::terminal_buffer(&theme).into_any()] - }, - Box::new(theme), - )) - } -} diff --git a/crates/ui/src/components/theme_selector.rs b/crates/ui/src/components/theme_selector.rs deleted file mode 100644 index e6f5237afe1a52eb89fef51955f03821e87976a9..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/theme_selector.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::prelude::*; -use crate::{OrderMethod, Palette, PaletteItem}; - -#[derive(Element)] -pub struct ThemeSelector { - scroll_state: ScrollState, -} - -impl ThemeSelector { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(vec![ - PaletteItem::new("One Dark"), - PaletteItem::new("Rosé Pine"), - PaletteItem::new("Rosé Pine Moon"), - PaletteItem::new("Sandcastle"), - PaletteItem::new("Solarized Dark"), - PaletteItem::new("Summercamp"), - PaletteItem::new("Atelier Cave Light"), - PaletteItem::new("Atelier Dune Light"), - PaletteItem::new("Atelier Estuary Light"), - PaletteItem::new("Atelier Forest Light"), - PaletteItem::new("Atelier Heath Light"), - ]) - .placeholder("Select Theme...") - .empty_string("No matches") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/title_bar.rs b/crates/ui/src/components/title_bar.rs deleted file mode 100644 index dd3d6c1fba652a139235e79eb0823002861901f6..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/title_bar.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::marker::PhantomData; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; - -use crate::{prelude::*, PlayerWithCallStatus}; -use crate::{ - theme, Avatar, Button, Icon, IconButton, IconColor, PlayerStack, ToolDivider, TrafficLights, -}; - -#[derive(Clone)] -pub struct Livestream { - pub players: Vec, - pub channel: Option, // projects - // windows -} - -#[derive(Element)] -pub struct TitleBar { - view_type: PhantomData, - /// If the window is active from the OS's perspective. - is_active: Arc, - livestream: Option, -} - -impl TitleBar { - pub fn new(cx: &mut ViewContext) -> Self { - let is_active = Arc::new(AtomicBool::new(true)); - let active = is_active.clone(); - - cx.observe_window_activation(move |_, is_active, cx| { - active.store(is_active, std::sync::atomic::Ordering::SeqCst); - cx.notify(); - }) - .detach(); - - Self { - view_type: PhantomData, - is_active, - livestream: None, - } - } - - pub fn set_livestream(mut self, livestream: Option) -> Self { - self.livestream = livestream; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let has_focus = cx.window_is_active(); - - let player_list = if let Some(livestream) = &self.livestream { - livestream.players.clone().into_iter() - } else { - vec![].into_iter() - }; - - div() - .flex() - .items_center() - .justify_between() - .w_full() - .h_8() - .fill(theme.lowest.base.default.background) - .child( - div() - .flex() - .items_center() - .h_full() - .gap_4() - .px_2() - .child(TrafficLights::new().window_has_focus(has_focus)) - // === Project Info === // - .child( - div() - .flex() - .items_center() - .gap_1() - .child(Button::new("zed")) - .child(Button::new("nate/gpui2-ui-components")), - ) - .children(player_list.map(|p| PlayerStack::new(p))) - .child(IconButton::new(Icon::Plus)), - ) - .child( - div() - .flex() - .items_center() - .child( - div() - .px_2() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::FolderX)) - .child(IconButton::new(Icon::Close)), - ) - .child(ToolDivider::new()) - .child( - div() - .px_2() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::Mic)) - .child(IconButton::new(Icon::AudioOn)) - .child(IconButton::new(Icon::Screen).color(IconColor::Accent)), - ) - .child( - div().px_2().flex().items_center().child( - Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4") - .shape(Shape::RoundedRectangle), - ), - ), - ) - } -} diff --git a/crates/ui/src/components/toast.rs b/crates/ui/src/components/toast.rs deleted file mode 100644 index c299cdd6bcd52fdded20fa73ccbfd29eb461ed34..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/toast.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::prelude::*; - -#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] -pub enum ToastOrigin { - #[default] - Bottom, - BottomRight, -} - -#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] -pub enum ToastVariant { - #[default] - Toast, - Status, -} - -/// A toast is a small, temporary window that appears to show a message to the user -/// or indicate a required action. -/// -/// Toasts should not persist on the screen for more than a few seconds unless -/// they are actively showing the a process in progress. -/// -/// Only one toast may be visible at a time. -#[derive(Element)] -pub struct Toast { - origin: ToastOrigin, - children: HackyChildren, - payload: HackyChildrenPayload, -} - -impl Toast { - pub fn new( - origin: ToastOrigin, - children: HackyChildren, - payload: HackyChildrenPayload, - ) -> Self { - Self { - origin, - children, - payload, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let color = ThemeColor::new(cx); - - let mut div = div(); - - if self.origin == ToastOrigin::Bottom { - div = div.right_1_2(); - } else { - div = div.right_4(); - } - - div.absolute() - .bottom_4() - .flex() - .py_2() - .px_1p5() - .min_w_40() - .rounded_md() - .fill(color.elevated_surface) - .max_w_64() - .children_any((self.children)(cx, self.payload.as_ref())) - } -} diff --git a/crates/ui/src/components/toolbar.rs b/crates/ui/src/components/toolbar.rs deleted file mode 100644 index e0953bf3b2d21f7e95e0e22a027a6e8a78f0dd3f..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/toolbar.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Clone)] -pub struct ToolbarItem {} - -#[derive(Element)] -pub struct Toolbar { - left_items: HackyChildren, - left_items_payload: HackyChildrenPayload, - right_items: HackyChildren, - right_items_payload: HackyChildrenPayload, -} - -impl Toolbar { - pub fn new( - left_items: HackyChildren, - left_items_payload: HackyChildrenPayload, - right_items: HackyChildren, - right_items_payload: HackyChildrenPayload, - ) -> Self { - Self { - left_items, - left_items_payload, - right_items, - right_items_payload, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .fill(theme.highest.base.default.background) - .p_2() - .flex() - .justify_between() - .child( - div() - .flex() - .children_any((self.left_items)(cx, self.left_items_payload.as_ref())), - ) - .child( - div() - .flex() - .children_any((self.right_items)(cx, self.right_items_payload.as_ref())), - ) - } -} diff --git a/crates/ui/src/components/traffic_lights.rs b/crates/ui/src/components/traffic_lights.rs deleted file mode 100644 index 0d644c49ca83c4c3ad74424721d16fc1fd407c27..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/traffic_lights.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::prelude::*; -use crate::{theme, token, SystemColor}; - -#[derive(Clone, Copy)] -enum TrafficLightColor { - Red, - Yellow, - Green, -} - -#[derive(Element)] -struct TrafficLight { - color: TrafficLightColor, - window_has_focus: bool, -} - -impl TrafficLight { - fn new(color: TrafficLightColor, window_has_focus: bool) -> Self { - Self { - color, - window_has_focus, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let system_color = SystemColor::new(); - - let fill = match (self.window_has_focus, self.color) { - (true, TrafficLightColor::Red) => system_color.mac_os_traffic_light_red, - (true, TrafficLightColor::Yellow) => system_color.mac_os_traffic_light_yellow, - (true, TrafficLightColor::Green) => system_color.mac_os_traffic_light_green, - (false, _) => theme.lowest.base.active.background, - }; - - div().w_3().h_3().rounded_full().fill(fill) - } -} - -#[derive(Element)] -pub struct TrafficLights { - window_has_focus: bool, -} - -impl TrafficLights { - pub fn new() -> Self { - Self { - window_has_focus: true, - } - } - - pub fn window_has_focus(mut self, window_has_focus: bool) -> Self { - self.window_has_focus = window_has_focus; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - - div() - .flex() - .items_center() - .gap_2() - .child(TrafficLight::new( - TrafficLightColor::Red, - self.window_has_focus, - )) - .child(TrafficLight::new( - TrafficLightColor::Yellow, - self.window_has_focus, - )) - .child(TrafficLight::new( - TrafficLightColor::Green, - self.window_has_focus, - )) - } -} diff --git a/crates/ui/src/components/workspace.rs b/crates/ui/src/components/workspace.rs deleted file mode 100644 index b3d375bd647c2281ebe80e4e4618eeed74ac3c3a..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/workspace.rs +++ /dev/null @@ -1,186 +0,0 @@ -use std::sync::Arc; - -use chrono::DateTime; -use gpui2::geometry::{relative, rems, Size}; - -use crate::{ - hello_world_rust_editor_with_status_example, prelude::*, random_players_with_call_status, - Livestream, -}; -use crate::{ - theme, v_stack, ChatMessage, ChatPanel, EditorPane, Pane, PaneGroup, Panel, PanelAllowedSides, - PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar, -}; - -#[derive(Element, Default)] -pub struct WorkspaceElement { - left_panel_scroll_state: ScrollState, - right_panel_scroll_state: ScrollState, - tab_bar_scroll_state: ScrollState, - bottom_panel_scroll_state: ScrollState, -} - -impl WorkspaceElement { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx).clone(); - - let temp_size = rems(36.).into(); - - let root_group = PaneGroup::new_groups( - vec![ - PaneGroup::new_panes( - vec![ - Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: temp_size, - }, - |_, payload| { - let theme = payload.downcast_ref::>().unwrap(); - - vec![EditorPane::new(hello_world_rust_editor_with_status_example( - &theme, - )) - .into_any()] - }, - Box::new(theme.clone()), - ), - Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: temp_size, - }, - |_, _| vec![Terminal::new().into_any()], - Box::new(()), - ), - ], - SplitDirection::Vertical, - ), - PaneGroup::new_panes( - vec![Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: relative(1.).into(), - }, - |_, payload| { - let theme = payload.downcast_ref::>().unwrap(); - - vec![EditorPane::new(hello_world_rust_editor_with_status_example( - &theme, - )) - .into_any()] - }, - Box::new(theme.clone()), - )], - SplitDirection::Vertical, - ), - ], - SplitDirection::Horizontal, - ); - - div() - .relative() - .size_full() - .flex() - .flex_col() - .font("Zed Sans Extended") - .gap_0() - .justify_start() - .items_start() - .text_color(theme.lowest.base.default.foreground) - .fill(theme.lowest.base.default.background) - .child(TitleBar::new(cx).set_livestream(Some(Livestream { - players: random_players_with_call_status(7), - channel: Some("gpui2-ui".to_string()), - }))) - .child( - div() - .flex_1() - .w_full() - .flex() - .flex_row() - .overflow_hidden() - .border_t() - .border_b() - .border_color(theme.lowest.base.default.border) - .child( - Panel::new( - self.left_panel_scroll_state.clone(), - |_, payload| vec![ProjectPanel::new(ScrollState::default()).into_any()], - Box::new(()), - ) - .side(PanelSide::Left), - ) - .child( - v_stack() - .flex_1() - .h_full() - .child( - div() - .flex() - .flex_1() - // CSS Hack: Flex 1 has to have a set height to properly fill the space - // Or it will give you a height of 0 - .h_px() - .child(root_group), - ) - .child( - Panel::new( - self.bottom_panel_scroll_state.clone(), - |_, _| vec![Terminal::new().into_any()], - Box::new(()), - ) - .allowed_sides(PanelAllowedSides::BottomOnly) - .side(PanelSide::Bottom), - ), - ) - .child( - Panel::new( - self.right_panel_scroll_state.clone(), - |_, payload| { - vec![ChatPanel::new(ScrollState::default()) - .with_messages(vec![ - ChatMessage::new( - "osiewicz".to_string(), - "is this thing on?".to_string(), - DateTime::parse_from_rfc3339( - "2023-09-27T15:40:52.707Z", - ) - .unwrap() - .naive_local(), - ), - ChatMessage::new( - "maxdeviant".to_string(), - "Reading you loud and clear!".to_string(), - DateTime::parse_from_rfc3339( - "2023-09-28T15:40:52.707Z", - ) - .unwrap() - .naive_local(), - ), - ]) - .into_any()] - }, - Box::new(()), - ) - .side(PanelSide::Right), - ), - ) - .child(StatusBar::new()) - // An example of a toast is below - // Currently because of stacking order this gets obscured by other elements - - // .child(Toast::new( - // ToastOrigin::Bottom, - // |_, payload| { - // let theme = payload.downcast_ref::>().unwrap(); - - // vec![Label::new("label").into_any()] - // }, - // Box::new(theme.clone()), - // )) - } -} diff --git a/crates/ui/src/element_ext.rs b/crates/ui/src/element_ext.rs deleted file mode 100644 index 67352f0779774527af4067b37bda662b1fcfdb2c..0000000000000000000000000000000000000000 --- a/crates/ui/src/element_ext.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::Element; - -use crate::theme::{Theme, Themed}; - -pub trait ElementExt: Element { - fn themed(self, theme: Theme) -> Themed - where - Self: Sized; -} - -impl> ElementExt for E { - fn themed(self, theme: Theme) -> Themed - where - Self: Sized, - { - Themed { - child: self, - theme, - view_type: PhantomData, - } - } -} diff --git a/crates/ui/src/elements.rs b/crates/ui/src/elements.rs deleted file mode 100644 index c60902ae9865e5a3756e893745e764c2d3c5f304..0000000000000000000000000000000000000000 --- a/crates/ui/src/elements.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod avatar; -mod button; -mod details; -mod icon; -mod input; -mod label; -mod player; -mod stack; -mod tool_divider; - -pub use avatar::*; -pub use button::*; -pub use details::*; -pub use icon::*; -pub use input::*; -pub use label::*; -pub use player::*; -pub use stack::*; -pub use tool_divider::*; diff --git a/crates/ui/src/elements/avatar.rs b/crates/ui/src/elements/avatar.rs deleted file mode 100644 index 2072b0e501f58c1178f1f29fcd716b8f4051d362..0000000000000000000000000000000000000000 --- a/crates/ui/src/elements/avatar.rs +++ /dev/null @@ -1,41 +0,0 @@ -use gpui2::elements::img; -use gpui2::ArcCow; - -use crate::prelude::*; -use crate::theme; - -#[derive(Element, Clone)] -pub struct Avatar { - src: ArcCow<'static, str>, - shape: Shape, -} - -impl Avatar { - pub fn new(src: impl Into>) -> Self { - Self { - src: src.into(), - shape: Shape::Circle, - } - } - - pub fn shape(mut self, shape: Shape) -> Self { - self.shape = shape; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let mut img = img(); - - if self.shape == Shape::Circle { - img = img.rounded_full(); - } else { - img = img.rounded_md(); - } - - img.uri(self.src.clone()) - .size_4() - .fill(theme.middle.warning.default.foreground) - } -} diff --git a/crates/ui/src/elements/button.rs b/crates/ui/src/elements/button.rs deleted file mode 100644 index c516b27908e25aa4183588084d9fa0e8453fe57b..0000000000000000000000000000000000000000 --- a/crates/ui/src/elements/button.rs +++ /dev/null @@ -1,203 +0,0 @@ -use std::rc::Rc; - -use gpui2::geometry::DefiniteLength; -use gpui2::platform::MouseButton; -use gpui2::{EventContext, Hsla, Interactive, WindowContext}; - -use crate::prelude::*; -use crate::{h_stack, theme, Icon, IconColor, IconElement, Label, LabelColor, LabelSize}; - -#[derive(Default, PartialEq, Clone, Copy)] -pub enum IconPosition { - #[default] - Left, - Right, -} - -#[derive(Default, Copy, Clone, PartialEq)] -pub enum ButtonVariant { - #[default] - Ghost, - Filled, -} - -struct ButtonHandlers { - click: Option)>>, -} - -impl Default for ButtonHandlers { - fn default() -> Self { - Self { click: None } - } -} - -#[derive(Element)] -pub struct Button { - label: String, - variant: ButtonVariant, - state: InteractionState, - icon: Option, - icon_position: Option, - width: Option, - handlers: ButtonHandlers, -} - -impl Button { - pub fn new(label: L) -> Self - where - L: Into, - { - Self { - label: label.into(), - variant: Default::default(), - state: Default::default(), - icon: None, - icon_position: None, - width: Default::default(), - handlers: ButtonHandlers::default(), - } - } - - pub fn ghost(label: L) -> Self - where - L: Into, - { - Self::new(label).variant(ButtonVariant::Ghost) - } - - pub fn variant(mut self, variant: ButtonVariant) -> Self { - self.variant = variant; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - pub fn icon(mut self, icon: Icon) -> Self { - self.icon = Some(icon); - self - } - - pub fn icon_position(mut self, icon_position: IconPosition) -> Self { - if self.icon.is_none() { - panic!("An icon must be present if an icon_position is provided."); - } - self.icon_position = Some(icon_position); - self - } - - pub fn width(mut self, width: Option) -> Self { - self.width = width; - self - } - - pub fn on_click(mut self, handler: impl Fn(&mut V, &mut EventContext) + 'static) -> Self { - self.handlers.click = Some(Rc::new(handler)); - self - } - - fn background_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match (self.variant, self.state) { - (ButtonVariant::Ghost, InteractionState::Hovered) => { - theme.lowest.base.hovered.background - } - (ButtonVariant::Ghost, InteractionState::Active) => { - theme.lowest.base.pressed.background - } - (ButtonVariant::Filled, InteractionState::Enabled) => { - theme.lowest.on.default.background - } - (ButtonVariant::Filled, InteractionState::Hovered) => { - theme.lowest.on.hovered.background - } - (ButtonVariant::Filled, InteractionState::Active) => theme.lowest.on.pressed.background, - (ButtonVariant::Filled, InteractionState::Disabled) => { - theme.lowest.on.disabled.background - } - _ => system_color.transparent, - } - } - - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - - fn border_color(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self.state { - InteractionState::Focused => theme.lowest.accent.default.border, - _ => system_color.transparent, - } - } - - fn render_label(&self) -> Label { - Label::new(self.label.clone()) - .size(LabelSize::Small) - .color(self.label_color()) - } - - fn render_icon(&self, icon_color: IconColor) -> Option { - self.icon.map(|i| IconElement::new(i).color(icon_color)) - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let icon_color = self.icon_color(); - let system_color = SystemColor::new(); - let border_color = self.border_color(cx); - - let mut el = h_stack() - .h_6() - .px_1() - .items_center() - .rounded_md() - .border() - .border_color(border_color) - .fill(self.background_color(cx)); - - match (self.icon, self.icon_position) { - (Some(_), Some(IconPosition::Left)) => { - el = el - .gap_1() - .child(self.render_label()) - .children(self.render_icon(icon_color)) - } - (Some(_), Some(IconPosition::Right)) => { - el = el - .gap_1() - .children(self.render_icon(icon_color)) - .child(self.render_label()) - } - (_, _) => el = el.child(self.render_label()), - } - - if let Some(width) = self.width { - el = el.w(width).justify_center(); - } - - if let Some(click_handler) = self.handlers.click.clone() { - el = el.on_mouse_down(MouseButton::Left, move |view, event, cx| { - click_handler(view, cx); - }); - } - - el - } -} diff --git a/crates/ui/src/elements/details.rs b/crates/ui/src/elements/details.rs deleted file mode 100644 index 9c829bcd4160d358c89835e8808eae75ee5de0b0..0000000000000000000000000000000000000000 --- a/crates/ui/src/elements/details.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Element, Clone)] -pub struct Details { - text: &'static str, - meta: Option<&'static str>, -} - -impl Details { - pub fn new(text: &'static str) -> Self { - Self { text, meta: None } - } - - pub fn meta_text(mut self, meta: &'static str) -> Self { - self.meta = Some(meta); - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - // .flex() - // .w_full() - .p_1() - .gap_0p5() - .text_xs() - .text_color(theme.lowest.base.default.foreground) - .child(self.text) - .children(self.meta.map(|m| m)) - } -} diff --git a/crates/ui/src/elements/icon.rs b/crates/ui/src/elements/icon.rs deleted file mode 100644 index 26bf7dab22779a6eea220c43f3436f4f0b9bce93..0000000000000000000000000000000000000000 --- a/crates/ui/src/elements/icon.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::sync::Arc; - -use gpui2::elements::svg; -use gpui2::Hsla; -use strum::EnumIter; - -use crate::prelude::*; -use crate::theme::theme; -use crate::Theme; - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum IconSize { - Small, - #[default] - Large, -} - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum IconColor { - #[default] - Default, - Muted, - Disabled, - Placeholder, - Accent, - Error, - Warning, - Success, - Info, -} - -impl IconColor { - pub fn color(self, theme: Arc) -> Hsla { - match self { - IconColor::Default => theme.lowest.base.default.foreground, - IconColor::Muted => theme.lowest.variant.default.foreground, - IconColor::Disabled => theme.lowest.base.disabled.foreground, - IconColor::Placeholder => theme.lowest.base.disabled.foreground, - IconColor::Accent => theme.lowest.accent.default.foreground, - IconColor::Error => theme.lowest.negative.default.foreground, - IconColor::Warning => theme.lowest.warning.default.foreground, - IconColor::Success => theme.lowest.positive.default.foreground, - IconColor::Info => theme.lowest.accent.default.foreground, - } - } -} - -#[derive(Default, PartialEq, Copy, Clone, EnumIter)] -pub enum Icon { - Ai, - ArrowLeft, - ArrowRight, - ArrowUpRight, - AudioOff, - AudioOn, - Bolt, - ChevronDown, - ChevronLeft, - ChevronRight, - ChevronUp, - Close, - ExclamationTriangle, - ExternalLink, - File, - FileGeneric, - FileDoc, - FileGit, - FileLock, - FileRust, - FileToml, - FileTree, - Folder, - FolderOpen, - FolderX, - #[default] - Hash, - InlayHint, - MagicWand, - MagnifyingGlass, - Maximize, - Menu, - MessageBubbles, - Mic, - MicMute, - Plus, - Quote, - Screen, - SelectAll, - Split, - SplitMessage, - Terminal, - XCircle, - Copilot, - Envelope, -} - -impl Icon { - pub fn path(self) -> &'static str { - match self { - Icon::Ai => "icons/ai.svg", - Icon::ArrowLeft => "icons/arrow_left.svg", - Icon::ArrowRight => "icons/arrow_right.svg", - Icon::ArrowUpRight => "icons/arrow_up_right.svg", - Icon::AudioOff => "icons/speaker-off.svg", - Icon::AudioOn => "icons/speaker-loud.svg", - Icon::Bolt => "icons/bolt.svg", - Icon::ChevronDown => "icons/chevron_down.svg", - Icon::ChevronLeft => "icons/chevron_left.svg", - Icon::ChevronRight => "icons/chevron_right.svg", - Icon::ChevronUp => "icons/chevron_up.svg", - Icon::Close => "icons/x.svg", - Icon::ExclamationTriangle => "icons/warning.svg", - Icon::ExternalLink => "icons/external_link.svg", - Icon::File => "icons/file.svg", - Icon::FileGeneric => "icons/file_icons/file.svg", - Icon::FileDoc => "icons/file_icons/book.svg", - Icon::FileGit => "icons/file_icons/git.svg", - Icon::FileLock => "icons/file_icons/lock.svg", - Icon::FileRust => "icons/file_icons/rust.svg", - Icon::FileToml => "icons/file_icons/toml.svg", - Icon::FileTree => "icons/project.svg", - Icon::Folder => "icons/file_icons/folder.svg", - Icon::FolderOpen => "icons/file_icons/folder_open.svg", - Icon::FolderX => "icons/stop_sharing.svg", - Icon::Hash => "icons/hash.svg", - Icon::InlayHint => "icons/inlay_hint.svg", - Icon::MagicWand => "icons/magic-wand.svg", - Icon::MagnifyingGlass => "icons/magnifying_glass.svg", - Icon::Maximize => "icons/maximize.svg", - Icon::Menu => "icons/menu.svg", - Icon::MessageBubbles => "icons/conversations.svg", - Icon::Mic => "icons/mic.svg", - Icon::MicMute => "icons/mic-mute.svg", - Icon::Plus => "icons/plus.svg", - Icon::Quote => "icons/quote.svg", - Icon::Screen => "icons/desktop.svg", - Icon::SelectAll => "icons/select-all.svg", - Icon::Split => "icons/split.svg", - Icon::SplitMessage => "icons/split_message.svg", - Icon::Terminal => "icons/terminal.svg", - Icon::XCircle => "icons/error.svg", - Icon::Copilot => "icons/copilot.svg", - Icon::Envelope => "icons/feedback.svg", - } - } -} - -#[derive(Element, Clone)] -pub struct IconElement { - icon: Icon, - color: IconColor, - size: IconSize, -} - -impl IconElement { - pub fn new(icon: Icon) -> Self { - Self { - icon, - color: IconColor::default(), - size: IconSize::default(), - } - } - - pub fn color(mut self, color: IconColor) -> Self { - self.color = color; - self - } - - pub fn size(mut self, size: IconSize) -> Self { - self.size = size; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let fill = self.color.color(theme); - - let sized_svg = match self.size { - IconSize::Small => svg().size_3p5(), - IconSize::Large => svg().size_4(), - }; - - sized_svg.flex_none().path(self.icon.path()).fill(fill) - } -} diff --git a/crates/ui/src/elements/input.rs b/crates/ui/src/elements/input.rs deleted file mode 100644 index fd860f30c2cd02afce02802ef6476386ffc007ca..0000000000000000000000000000000000000000 --- a/crates/ui/src/elements/input.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Default, PartialEq)] -pub enum InputVariant { - #[default] - Ghost, - Filled, -} - -#[derive(Element)] -pub struct Input { - placeholder: &'static str, - value: String, - state: InteractionState, - variant: InputVariant, -} - -impl Input { - pub fn new(placeholder: &'static str) -> Self { - Self { - placeholder, - value: "".to_string(), - state: InteractionState::default(), - variant: InputVariant::default(), - } - } - - pub fn value(mut self, value: String) -> Self { - self.value = value; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - pub fn variant(mut self, variant: InputVariant) -> Self { - self.variant = variant; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let text_el; - let text_color; - let background_color_default; - let background_color_active; - - let mut border_color_default = theme.middle.base.default.border; - let mut border_color_hover = theme.middle.base.hovered.border; - let mut border_color_active = theme.middle.base.pressed.border; - let border_color_focus = theme.middle.base.pressed.background; - - match self.variant { - InputVariant::Ghost => { - background_color_default = theme.middle.base.default.background; - background_color_active = theme.middle.base.active.background; - } - InputVariant::Filled => { - background_color_default = theme.middle.on.default.background; - background_color_active = theme.middle.on.active.background; - } - }; - - if self.state == InteractionState::Focused { - border_color_default = theme.players[0].cursor; - border_color_hover = theme.players[0].cursor; - border_color_active = theme.players[0].cursor; - } - - if self.state == InteractionState::Focused || self.state == InteractionState::Active { - text_el = self.value.clone(); - text_color = theme.lowest.base.default.foreground; - } else { - text_el = self.placeholder.to_string().clone(); - text_color = theme.lowest.base.disabled.foreground; - } - - div() - .h_7() - .w_full() - .px_2() - .border() - .border_color(border_color_default) - .fill(background_color_default) - .hover() - .border_color(border_color_hover) - .active() - .border_color(border_color_active) - .fill(background_color_active) - .flex() - .items_center() - .child( - div() - .flex() - .items_center() - .text_sm() - .text_color(text_color) - .child(text_el) - .child(div().text_color(theme.players[0].cursor).child("|")), - ) - } -} diff --git a/crates/ui/src/elements/label.rs b/crates/ui/src/elements/label.rs deleted file mode 100644 index e7b15aaf02248a24cbfd3c2c5ab874bd43fd86b1..0000000000000000000000000000000000000000 --- a/crates/ui/src/elements/label.rs +++ /dev/null @@ -1,161 +0,0 @@ -use gpui2::{Hsla, WindowContext}; -use smallvec::SmallVec; - -use crate::prelude::*; -use crate::theme::theme; - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum LabelColor { - #[default] - Default, - Muted, - Created, - Modified, - Deleted, - Disabled, - Hidden, - Placeholder, - Accent, -} - -impl LabelColor { - pub fn hsla(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - - match self { - Self::Default => theme.middle.base.default.foreground, - Self::Muted => theme.middle.variant.default.foreground, - Self::Created => theme.middle.positive.default.foreground, - Self::Modified => theme.middle.warning.default.foreground, - Self::Deleted => theme.middle.negative.default.foreground, - Self::Disabled => theme.middle.base.disabled.foreground, - Self::Hidden => theme.middle.variant.default.foreground, - Self::Placeholder => theme.middle.base.disabled.foreground, - Self::Accent => theme.middle.accent.default.foreground, - } - } -} - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum LabelSize { - #[default] - Default, - Small, -} - -#[derive(Element, Clone)] -pub struct Label { - label: String, - color: LabelColor, - size: LabelSize, - highlight_indices: Vec, - strikethrough: bool, -} - -impl Label { - pub fn new(label: L) -> Self - where - L: Into, - { - Self { - label: label.into(), - color: LabelColor::Default, - size: LabelSize::Default, - highlight_indices: Vec::new(), - strikethrough: false, - } - } - - pub fn color(mut self, color: LabelColor) -> Self { - self.color = color; - self - } - - pub fn size(mut self, size: LabelSize) -> Self { - self.size = size; - self - } - - pub fn with_highlights(mut self, indices: Vec) -> Self { - self.highlight_indices = indices; - self - } - - pub fn set_strikethrough(mut self, strikethrough: bool) -> Self { - self.strikethrough = strikethrough; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let highlight_color = theme.lowest.accent.default.foreground; - - let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); - - let mut runs: SmallVec<[Run; 8]> = SmallVec::new(); - - for (char_ix, char) in self.label.char_indices() { - let mut color = self.color.hsla(cx); - - if let Some(highlight_ix) = highlight_indices.peek() { - if char_ix == *highlight_ix { - color = highlight_color; - - highlight_indices.next(); - } - } - - let last_run = runs.last_mut(); - - let start_new_run = if let Some(last_run) = last_run { - if color == last_run.color { - last_run.text.push(char); - false - } else { - true - } - } else { - true - }; - - if start_new_run { - runs.push(Run { - text: char.to_string(), - color, - }); - } - } - - div() - .flex() - .when(self.strikethrough, |this| { - this.relative().child( - div() - .absolute() - .top_px() - .my_auto() - .w_full() - .h_px() - .fill(LabelColor::Hidden.hsla(cx)), - ) - }) - .children(runs.into_iter().map(|run| { - let mut div = div(); - - if self.size == LabelSize::Small { - div = div.text_xs(); - } else { - div = div.text_sm(); - } - - div.text_color(run.color).child(run.text) - })) - } -} - -/// A run of text that receives the same style. -struct Run { - pub text: String, - pub color: Hsla, -} diff --git a/crates/ui/src/elements/player.rs b/crates/ui/src/elements/player.rs deleted file mode 100644 index 465542dc7f39a0ce397e978da70520c7aa137897..0000000000000000000000000000000000000000 --- a/crates/ui/src/elements/player.rs +++ /dev/null @@ -1,133 +0,0 @@ -use gpui2::{Hsla, ViewContext}; - -use crate::theme; - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum PlayerStatus { - #[default] - Offline, - Online, - InCall, - Away, - DoNotDisturb, - Invisible, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum MicStatus { - Muted, - #[default] - Unmuted, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum VideoStatus { - On, - #[default] - Off, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum ScreenShareStatus { - Shared, - #[default] - NotShared, -} - -#[derive(Clone)] -pub struct PlayerCallStatus { - pub mic_status: MicStatus, - /// Indicates if the player is currently speaking - /// And the intensity of the volume coming through - /// - /// 0.0 - 1.0 - pub voice_activity: f32, - pub video_status: VideoStatus, - pub screen_share_status: ScreenShareStatus, - pub in_current_project: bool, - pub disconnected: bool, - pub following: Option>, - pub followers: Option>, -} - -impl PlayerCallStatus { - pub fn new() -> Self { - Self { - mic_status: MicStatus::default(), - voice_activity: 0., - video_status: VideoStatus::default(), - screen_share_status: ScreenShareStatus::default(), - in_current_project: true, - disconnected: false, - following: None, - followers: None, - } - } -} - -#[derive(PartialEq, Clone)] -pub struct Player { - index: usize, - avatar_src: String, - username: String, - status: PlayerStatus, -} - -#[derive(Clone)] -pub struct PlayerWithCallStatus { - player: Player, - call_status: PlayerCallStatus, -} - -impl PlayerWithCallStatus { - pub fn new(player: Player, call_status: PlayerCallStatus) -> Self { - Self { - player, - call_status, - } - } - - pub fn get_player(&self) -> &Player { - &self.player - } - - pub fn get_call_status(&self) -> &PlayerCallStatus { - &self.call_status - } -} - -impl Player { - pub fn new(index: usize, avatar_src: String, username: String) -> Self { - Self { - index, - avatar_src, - username, - status: Default::default(), - } - } - - pub fn set_status(mut self, status: PlayerStatus) -> Self { - self.status = status; - self - } - - pub fn cursor_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - let index = self.index % 8; - theme.players[self.index].cursor - } - - pub fn selection_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - let index = self.index % 8; - theme.players[self.index].selection - } - - pub fn avatar_src(&self) -> &str { - &self.avatar_src - } - - pub fn index(&self) -> usize { - self.index - } -} diff --git a/crates/ui/src/elements/stack.rs b/crates/ui/src/elements/stack.rs deleted file mode 100644 index ef186f5ebe7fb6f80cfbb6445a09e84e5a36e5c3..0000000000000000000000000000000000000000 --- a/crates/ui/src/elements/stack.rs +++ /dev/null @@ -1,31 +0,0 @@ -use gpui2::elements::div::Div; - -use crate::prelude::*; - -pub trait Stack: StyleHelpers { - /// Horizontally stacks elements. - fn h_stack(self) -> Self { - self.flex().flex_row().items_center() - } - - /// Vertically stacks elements. - fn v_stack(self) -> Self { - self.flex().flex_col() - } -} - -impl Stack for Div {} - -/// Horizontally stacks elements. -/// -/// Sets `flex()`, `flex_row()`, `items_center()` -pub fn h_stack() -> Div { - div().h_stack() -} - -/// Vertically stacks elements. -/// -/// Sets `flex()`, `flex_col()` -pub fn v_stack() -> Div { - div().v_stack() -} diff --git a/crates/ui/src/elements/tool_divider.rs b/crates/ui/src/elements/tool_divider.rs deleted file mode 100644 index 8b5a191445b389135ca88fee484d42e296ffa516..0000000000000000000000000000000000000000 --- a/crates/ui/src/elements/tool_divider.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Element)] -pub struct ToolDivider {} - -impl ToolDivider { - pub fn new() -> Self { - Self {} - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div().w_px().h_3().fill(theme.lowest.base.default.border) - } -} diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs deleted file mode 100644 index 39156d3ab46df504ebc3dc963c95cf2b4cd3335a..0000000000000000000000000000000000000000 --- a/crates/ui/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![allow(dead_code, unused_variables)] - -mod children; -mod components; -mod element_ext; -mod elements; -pub mod prelude; -mod static_data; -mod theme; -mod tokens; - -pub use children::*; -pub use components::*; -pub use element_ext::*; -pub use elements::*; -pub use prelude::*; -pub use static_data::*; -pub use tokens::*; - -pub use crate::theme::*; diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs deleted file mode 100644 index e0da3579e21df1ce5fca02451688f9ca7b5014de..0000000000000000000000000000000000000000 --- a/crates/ui/src/prelude.rs +++ /dev/null @@ -1,274 +0,0 @@ -pub use gpui2::elements::div::{div, ScrollState}; -pub use gpui2::style::{StyleHelpers, Styleable}; -pub use gpui2::{Element, IntoElement, ParentElement, ViewContext}; - -pub use crate::{theme, ButtonVariant, HackyChildren, HackyChildrenPayload, InputVariant, Theme}; - -use gpui2::{hsla, rgb, Hsla, WindowContext}; -use strum::EnumIter; - -#[derive(Default)] -pub struct SystemColor { - pub transparent: Hsla, - pub mac_os_traffic_light_red: Hsla, - pub mac_os_traffic_light_yellow: Hsla, - pub mac_os_traffic_light_green: Hsla, -} - -impl SystemColor { - pub fn new() -> SystemColor { - SystemColor { - transparent: hsla(0.0, 0.0, 0.0, 0.0), - mac_os_traffic_light_red: rgb::(0xEC695E), - mac_os_traffic_light_yellow: rgb::(0xF4BF4F), - mac_os_traffic_light_green: rgb::(0x62C554), - } - } - pub fn color(&self) -> Hsla { - self.transparent - } -} - -#[derive(Clone, Copy)] -pub struct ThemeColor { - pub border: Hsla, - pub border_variant: Hsla, - /// The background color of an elevated surface, like a modal, tooltip or toast. - pub elevated_surface: Hsla, -} - -impl ThemeColor { - pub fn new(cx: &WindowContext) -> Self { - let theme = theme(cx); - - Self { - border: theme.lowest.base.default.border, - border_variant: theme.lowest.variant.default.border, - elevated_surface: theme.middle.base.default.background, - } - } -} - -#[derive(Default, PartialEq, EnumIter, Clone, Copy)] -pub enum HighlightColor { - #[default] - Default, - Comment, - String, - Function, - Keyword, -} - -impl HighlightColor { - pub fn hsla(&self, theme: &Theme) -> Hsla { - let system_color = SystemColor::new(); - - match self { - Self::Default => theme - .syntax - .get("primary") - .expect("no theme.syntax.primary") - .clone(), - Self::Comment => theme - .syntax - .get("comment") - .expect("no theme.syntax.comment") - .clone(), - Self::String => theme - .syntax - .get("string") - .expect("no theme.syntax.string") - .clone(), - Self::Function => theme - .syntax - .get("function") - .expect("no theme.syntax.function") - .clone(), - Self::Keyword => theme - .syntax - .get("keyword") - .expect("no theme.syntax.keyword") - .clone(), - } - } -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum FileSystemStatus { - #[default] - None, - Conflict, - Deleted, -} - -impl FileSystemStatus { - pub fn to_string(&self) -> String { - match self { - Self::None => "None".to_string(), - Self::Conflict => "Conflict".to_string(), - Self::Deleted => "Deleted".to_string(), - } - } -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum GitStatus { - #[default] - None, - Created, - Modified, - Deleted, - Conflict, - Renamed, -} - -impl GitStatus { - pub fn to_string(&self) -> String { - match self { - Self::None => "None".to_string(), - Self::Created => "Created".to_string(), - Self::Modified => "Modified".to_string(), - Self::Deleted => "Deleted".to_string(), - Self::Conflict => "Conflict".to_string(), - Self::Renamed => "Renamed".to_string(), - } - } - - pub fn hsla(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self { - Self::None => system_color.transparent, - Self::Created => theme.lowest.positive.default.foreground, - Self::Modified => theme.lowest.warning.default.foreground, - Self::Deleted => theme.lowest.negative.default.foreground, - Self::Conflict => theme.lowest.warning.default.foreground, - Self::Renamed => theme.lowest.accent.default.foreground, - } - } -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum DiagnosticStatus { - #[default] - None, - Error, - Warning, - Info, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum IconSide { - #[default] - Left, - Right, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum OrderMethod { - #[default] - Ascending, - Descending, - MostRecent, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum Shape { - #[default] - Circle, - RoundedRectangle, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum DisclosureControlVisibility { - #[default] - OnHover, - Always, -} - -#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)] -pub enum InteractionState { - #[default] - Enabled, - Hovered, - Active, - Focused, - Disabled, -} - -impl InteractionState { - pub fn if_enabled(&self, enabled: bool) -> Self { - if enabled { - *self - } else { - InteractionState::Disabled - } - } -} - -#[derive(Default, PartialEq)] -pub enum SelectedState { - #[default] - Unselected, - PartiallySelected, - Selected, -} - -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] -pub enum Toggleable { - Toggleable(ToggleState), - #[default] - NotToggleable, -} - -impl Toggleable { - pub fn is_toggled(&self) -> bool { - match self { - Self::Toggleable(ToggleState::Toggled) => true, - _ => false, - } - } -} - -impl From for Toggleable { - fn from(state: ToggleState) -> Self { - Self::Toggleable(state) - } -} - -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] -pub enum ToggleState { - /// The "on" state of a toggleable element. - /// - /// Example: - /// - A collasable list that is currently expanded - /// - A toggle button that is currently on. - Toggled, - /// The "off" state of a toggleable element. - /// - /// Example: - /// - A collasable list that is currently collapsed - /// - A toggle button that is currently off. - #[default] - NotToggled, -} - -impl From for ToggleState { - fn from(toggleable: Toggleable) -> Self { - match toggleable { - Toggleable::Toggleable(state) => state, - Toggleable::NotToggleable => ToggleState::NotToggled, - } - } -} - -impl From for ToggleState { - fn from(toggled: bool) -> Self { - if toggled { - ToggleState::Toggled - } else { - ToggleState::NotToggled - } - } -} diff --git a/crates/ui/src/static_data.rs b/crates/ui/src/static_data.rs deleted file mode 100644 index b8c4e18f14c31cfb018fe581353e43d2fca971f6..0000000000000000000000000000000000000000 --- a/crates/ui/src/static_data.rs +++ /dev/null @@ -1,966 +0,0 @@ -use std::path::PathBuf; -use std::str::FromStr; - -use rand::Rng; - -use crate::{ - Buffer, BufferRow, BufferRows, Editor, FileSystemStatus, GitStatus, HighlightColor, - HighlightedLine, HighlightedText, Icon, Keybinding, Label, LabelColor, ListEntry, - ListEntrySize, ListItem, Livestream, MicStatus, ModifierKeys, PaletteItem, Player, - PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, Theme, ToggleState, - VideoStatus, -}; - -pub fn static_tabs_example() -> Vec { - vec![ - Tab::new() - .title("wip.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .fs_status(FileSystemStatus::Deleted), - Tab::new() - .title("Cargo.toml".to_string()) - .icon(Icon::FileToml) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("Channels Panel".to_string()) - .icon(Icon::Hash) - .current(false), - Tab::new() - .title("channels_panel.rs".to_string()) - .icon(Icon::FileRust) - .current(true) - .git_status(GitStatus::Modified), - Tab::new() - .title("workspace.rs".to_string()) - .current(false) - .icon(Icon::FileRust) - .git_status(GitStatus::Modified), - Tab::new() - .title("icon_button.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("storybook.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .git_status(GitStatus::Created), - Tab::new() - .title("theme.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("theme_registry.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("styleable_helpers.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - ] -} - -pub fn static_tabs_1() -> Vec { - vec![ - Tab::new() - .title("project_panel.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .fs_status(FileSystemStatus::Deleted), - Tab::new() - .title("tab_bar.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("workspace.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("tab.rs".to_string()) - .icon(Icon::FileRust) - .current(true) - .git_status(GitStatus::Modified), - ] -} - -pub fn static_tabs_2() -> Vec { - vec![ - Tab::new() - .title("tab_bar.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .fs_status(FileSystemStatus::Deleted), - Tab::new() - .title("static_data.rs".to_string()) - .icon(Icon::FileRust) - .current(true) - .git_status(GitStatus::Modified), - ] -} - -pub fn static_tabs_3() -> Vec { - vec![Tab::new().git_status(GitStatus::Created).current(true)] -} - -pub fn static_players() -> Vec { - vec![ - Player::new( - 0, - "https://avatars.githubusercontent.com/u/1714999?v=4".into(), - "nathansobo".into(), - ), - Player::new( - 1, - "https://avatars.githubusercontent.com/u/326587?v=4".into(), - "maxbrunsfeld".into(), - ), - Player::new( - 2, - "https://avatars.githubusercontent.com/u/482957?v=4".into(), - "as-cii".into(), - ), - Player::new( - 3, - "https://avatars.githubusercontent.com/u/1714999?v=4".into(), - "iamnbutler".into(), - ), - Player::new( - 4, - "https://avatars.githubusercontent.com/u/1486634?v=4".into(), - "maxdeviant".into(), - ), - ] -} - -#[derive(Debug)] -pub struct PlayerData { - pub url: String, - pub name: String, -} -pub fn static_player_data() -> Vec { - vec![ - PlayerData { - url: "https://avatars.githubusercontent.com/u/1714999?v=4".into(), - name: "iamnbutler".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/326587?v=4".into(), - name: "maxbrunsfeld".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/482957?v=4".into(), - name: "as-cii".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/1789?v=4".into(), - name: "nathansobo".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(), - name: "ForLoveOfCats".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/2690773?v=4".into(), - name: "SomeoneToIgnore".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/19867440?v=4".into(), - name: "JosephTLyons".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/24362066?v=4".into(), - name: "osiewicz".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/22121886?v=4".into(), - name: "KCaverly".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(), - name: "maxdeviant".into(), - }, - ] -} -pub fn create_static_players(player_data: Vec) -> Vec { - let mut players = Vec::new(); - for data in player_data { - players.push(Player::new(players.len(), data.url, data.name)); - } - players -} -pub fn static_player_1(data: &Vec) -> Player { - Player::new(1, data[0].url.clone(), data[0].name.clone()) -} -pub fn static_player_2(data: &Vec) -> Player { - Player::new(2, data[1].url.clone(), data[1].name.clone()) -} -pub fn static_player_3(data: &Vec) -> Player { - Player::new(3, data[2].url.clone(), data[2].name.clone()) -} -pub fn static_player_4(data: &Vec) -> Player { - Player::new(4, data[3].url.clone(), data[3].name.clone()) -} -pub fn static_player_5(data: &Vec) -> Player { - Player::new(5, data[4].url.clone(), data[4].name.clone()) -} -pub fn static_player_6(data: &Vec) -> Player { - Player::new(6, data[5].url.clone(), data[5].name.clone()) -} -pub fn static_player_7(data: &Vec) -> Player { - Player::new(7, data[6].url.clone(), data[6].name.clone()) -} -pub fn static_player_8(data: &Vec) -> Player { - Player::new(8, data[7].url.clone(), data[7].name.clone()) -} -pub fn static_player_9(data: &Vec) -> Player { - Player::new(9, data[8].url.clone(), data[8].name.clone()) -} -pub fn static_player_10(data: &Vec) -> Player { - Player::new(10, data[9].url.clone(), data[9].name.clone()) -} -pub fn static_livestream() -> Livestream { - Livestream { - players: random_players_with_call_status(7), - channel: Some("gpui2-ui".to_string()), - } -} -pub fn populate_player_call_status( - player: Player, - followers: Option>, -) -> PlayerCallStatus { - let mut rng = rand::thread_rng(); - let in_current_project: bool = rng.gen(); - let disconnected: bool = rng.gen(); - let voice_activity: f32 = rng.gen(); - let mic_status = if rng.gen_bool(0.5) { - MicStatus::Muted - } else { - MicStatus::Unmuted - }; - let video_status = if rng.gen_bool(0.5) { - VideoStatus::On - } else { - VideoStatus::Off - }; - let screen_share_status = if rng.gen_bool(0.5) { - ScreenShareStatus::Shared - } else { - ScreenShareStatus::NotShared - }; - PlayerCallStatus { - mic_status, - voice_activity, - video_status, - screen_share_status, - in_current_project, - disconnected, - following: None, - followers, - } -} -pub fn random_players_with_call_status(number_of_players: usize) -> Vec { - let players = create_static_players(static_player_data()); - let mut player_status = vec![]; - for i in 0..number_of_players { - let followers = if i == 0 { - Some(vec![ - players[1].clone(), - players[3].clone(), - players[5].clone(), - players[6].clone(), - ]) - } else if i == 1 { - Some(vec![players[2].clone(), players[6].clone()]) - } else { - None - }; - let call_status = populate_player_call_status(players[i].clone(), followers); - player_status.push(PlayerWithCallStatus::new(players[i].clone(), call_status)); - } - player_status -} - -pub fn static_players_with_call_status() -> Vec { - let players = static_players(); - let mut player_0_status = PlayerCallStatus::new(); - let player_1_status = PlayerCallStatus::new(); - let player_2_status = PlayerCallStatus::new(); - let mut player_3_status = PlayerCallStatus::new(); - let mut player_4_status = PlayerCallStatus::new(); - - player_0_status.screen_share_status = ScreenShareStatus::Shared; - player_0_status.followers = Some(vec![players[1].clone(), players[3].clone()]); - - player_3_status.voice_activity = 0.5; - player_4_status.mic_status = MicStatus::Muted; - player_4_status.in_current_project = false; - - vec![ - PlayerWithCallStatus::new(players[0].clone(), player_0_status), - PlayerWithCallStatus::new(players[1].clone(), player_1_status), - PlayerWithCallStatus::new(players[2].clone(), player_2_status), - PlayerWithCallStatus::new(players[3].clone(), player_3_status), - PlayerWithCallStatus::new(players[4].clone(), player_4_status), - ] -} - -pub fn static_project_panel_project_items() -> Vec { - vec![ - ListEntry::new(Label::new("zed")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(0) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new(".cargo")) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".config")) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".git").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".cargo")) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".idea").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new("assets")) - .left_icon(Icon::Folder.into()) - .indent_level(1) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("cargo-target").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new("crates")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(1) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("activity_indicator")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("ai")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("audio")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("auto_update")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("breadcrumbs")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("call")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("sqlez").color(LabelColor::Modified)) - .left_icon(Icon::Folder.into()) - .indent_level(2) - .set_toggle(ToggleState::NotToggled), - ListEntry::new(Label::new("gpui2")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(2) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("src")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(3) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("derive_element.rs")) - .left_icon(Icon::FileRust.into()) - .indent_level(4), - ListEntry::new(Label::new("storybook").color(LabelColor::Modified)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(1) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("docs").color(LabelColor::Default)) - .left_icon(Icon::Folder.into()) - .indent_level(2) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("src").color(LabelColor::Modified)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(3) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("ui").color(LabelColor::Modified)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(4) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("component").color(LabelColor::Created)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(5) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("facepile.rs").color(LabelColor::Default)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("follow_group.rs").color(LabelColor::Default)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("list_item.rs").color(LabelColor::Created)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("tab.rs").color(LabelColor::Default)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("target").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".dockerignore")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new(".DS_Store").color(LabelColor::Hidden)) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new("Cargo.lock")) - .left_icon(Icon::FileLock.into()) - .indent_level(1), - ListEntry::new(Label::new("Cargo.toml")) - .left_icon(Icon::FileToml.into()) - .indent_level(1), - ListEntry::new(Label::new("Dockerfile")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new("Procfile")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new("README.md")) - .left_icon(Icon::FileDoc.into()) - .indent_level(1), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_project_panel_single_items() -> Vec { - vec![ - ListEntry::new(Label::new("todo.md")) - .left_icon(Icon::FileDoc.into()) - .indent_level(0), - ListEntry::new(Label::new("README.md")) - .left_icon(Icon::FileDoc.into()) - .indent_level(0), - ListEntry::new(Label::new("config.json")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(0), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_collab_panel_current_call() -> Vec { - vec![ - ListEntry::new(Label::new("as-cii")).left_avatar("http://github.com/as-cii.png?s=50"), - ListEntry::new(Label::new("nathansobo")) - .left_avatar("http://github.com/nathansobo.png?s=50"), - ListEntry::new(Label::new("maxbrunsfeld")) - .left_avatar("http://github.com/maxbrunsfeld.png?s=50"), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_collab_panel_channels() -> Vec { - vec![ - ListEntry::new(Label::new("zed")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(0), - ListEntry::new(Label::new("community")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(1), - ListEntry::new(Label::new("dashboards")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("feedback")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("teams-in-channels-alpha")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("current-projects")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(1), - ListEntry::new(Label::new("codegen")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("gpui2")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("livestreaming")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("open-source")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("replace")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("semantic-index")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("vim")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("web-tech")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn example_editor_actions() -> Vec { - vec![ - PaletteItem::new("New File").keybinding(Keybinding::new( - "N".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Open File").keybinding(Keybinding::new( - "O".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Save File").keybinding(Keybinding::new( - "S".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Cut").keybinding(Keybinding::new( - "X".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Copy").keybinding(Keybinding::new( - "C".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Paste").keybinding(Keybinding::new( - "V".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Undo").keybinding(Keybinding::new( - "Z".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Redo").keybinding(Keybinding::new( - "Z".to_string(), - ModifierKeys::new().control(true).shift(true), - )), - PaletteItem::new("Find").keybinding(Keybinding::new( - "F".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Replace").keybinding(Keybinding::new( - "R".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Jump to Line"), - PaletteItem::new("Select All"), - PaletteItem::new("Deselect All"), - PaletteItem::new("Switch Document"), - PaletteItem::new("Insert Line Below"), - PaletteItem::new("Insert Line Above"), - PaletteItem::new("Move Line Up"), - PaletteItem::new("Move Line Down"), - PaletteItem::new("Toggle Comment"), - PaletteItem::new("Delete Line"), - ] -} - -pub fn empty_editor_example() -> Editor { - Editor { - tabs: static_tabs_example(), - path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), - symbols: vec![], - buffer: empty_buffer_example(), - } -} - -pub fn empty_buffer_example() -> Buffer { - Buffer::new().set_rows(Some(BufferRows::default())) -} - -pub fn hello_world_rust_editor_example(theme: &Theme) -> Editor { - Editor { - tabs: static_tabs_example(), - path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), - symbols: vec![Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ])], - buffer: hello_world_rust_buffer_example(theme), - } -} - -pub fn hello_world_rust_buffer_example(theme: &Theme) -> Buffer { - Buffer::new() - .set_title("hello_world.rs".to_string()) - .set_path("src/hello_world.rs".to_string()) - .set_language("rust".to_string()) - .set_rows(Some(BufferRows { - show_line_numbers: true, - rows: hello_world_rust_buffer_rows(theme), - })) -} - -pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { - let show_line_number = true; - - vec![ - BufferRow { - line_number: 1, - code_action: false, - current: true, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "() {".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 2, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: " // Statements here are executed when the compiled binary is called." - .to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 3, - code_action: false, - current: false, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 4, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: " // Print text to the console.".to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 5, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: " println!(".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "\"Hello, world!\"".to_string(), - color: HighlightColor::String.hsla(&theme), - }, - HighlightedText { - text: ");".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 6, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "}".to_string(), - color: HighlightColor::Default.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - ] -} - -pub fn hello_world_rust_editor_with_status_example(theme: &Theme) -> Editor { - Editor { - tabs: static_tabs_example(), - path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), - symbols: vec![Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ])], - buffer: hello_world_rust_buffer_with_status_example(theme), - } -} - -pub fn hello_world_rust_buffer_with_status_example(theme: &Theme) -> Buffer { - Buffer::new() - .set_title("hello_world.rs".to_string()) - .set_path("src/hello_world.rs".to_string()) - .set_language("rust".to_string()) - .set_rows(Some(BufferRows { - show_line_numbers: true, - rows: hello_world_rust_with_status_buffer_rows(theme), - })) -} - -pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec { - let show_line_number = true; - - vec![ - BufferRow { - line_number: 1, - code_action: false, - current: true, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "() {".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 2, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "// Statements here are executed when the compiled binary is called." - .to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::Modified, - show_line_number, - }, - BufferRow { - line_number: 3, - code_action: false, - current: false, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 4, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: " // Print text to the console.".to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 5, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: " println!(".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "\"Hello, world!\"".to_string(), - color: HighlightColor::String.hsla(&theme), - }, - HighlightedText { - text: ");".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 6, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "}".to_string(), - color: HighlightColor::Default.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 7, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "".to_string(), - color: HighlightColor::Default.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::Created, - show_line_number, - }, - BufferRow { - line_number: 8, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "// Marshall and Nate were here".to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::Created, - show_line_number, - }, - ] -} - -pub fn terminal_buffer(theme: &Theme) -> Buffer { - Buffer::new() - .set_title("zed — fish".to_string()) - .set_rows(Some(BufferRows { - show_line_numbers: false, - rows: terminal_buffer_rows(theme), - })) -} - -pub fn terminal_buffer_rows(theme: &Theme) -> Vec { - let show_line_number = false; - - vec![ - BufferRow { - line_number: 1, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: "maxdeviant ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "in ".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "profaned-capital ".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "in ".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "~/p/zed ".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "on ".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "î‚  gpui2-ui ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 2, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "λ ".to_string(), - color: HighlightColor::String.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - ] -} diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs deleted file mode 100644 index d4c8c262739d102c2515c097d88f8da22e70a515..0000000000000000000000000000000000000000 --- a/crates/ui/src/theme.rs +++ /dev/null @@ -1,196 +0,0 @@ -use std::collections::HashMap; -use std::fmt; -use std::marker::PhantomData; -use std::sync::Arc; - -use gpui2::color::Hsla; -use gpui2::element::Element; -use gpui2::{serde_json, AppContext, IntoElement, Vector2F, ViewContext, WindowContext}; -use serde::de::Visitor; -use serde::{Deserialize, Deserializer}; -use theme::ThemeSettings; - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Theme { - pub name: String, - pub is_light: bool, - pub lowest: Layer, - pub middle: Layer, - pub highest: Layer, - pub popover_shadow: Shadow, - pub modal_shadow: Shadow, - #[serde(deserialize_with = "deserialize_player_colors")] - pub players: Vec, - #[serde(deserialize_with = "deserialize_syntax_colors")] - pub syntax: HashMap, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Layer { - pub base: StyleSet, - pub variant: StyleSet, - pub on: StyleSet, - pub accent: StyleSet, - pub positive: StyleSet, - pub warning: StyleSet, - pub negative: StyleSet, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct StyleSet { - #[serde(rename = "default")] - pub default: ContainerColors, - pub hovered: ContainerColors, - pub pressed: ContainerColors, - pub active: ContainerColors, - pub disabled: ContainerColors, - pub inverted: ContainerColors, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct ContainerColors { - pub background: Hsla, - pub foreground: Hsla, - pub border: Hsla, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct PlayerColors { - pub selection: Hsla, - pub cursor: Hsla, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Shadow { - pub blur: u8, - pub color: Hsla, - pub offset: Vec, -} - -fn deserialize_player_colors<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct PlayerArrayVisitor; - - impl<'de> Visitor<'de> for PlayerArrayVisitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("an object with integer keys") - } - - fn visit_map>( - self, - mut map: A, - ) -> Result { - let mut players = Vec::with_capacity(8); - while let Some((key, value)) = map.next_entry::()? { - if key < 8 { - players.push(value); - } else { - return Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Unsigned(key as u64), - &"a key in range 0..7", - )); - } - } - Ok(players) - } - } - - deserializer.deserialize_map(PlayerArrayVisitor) -} - -fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - #[derive(Deserialize)] - struct ColorWrapper { - color: Hsla, - } - - struct SyntaxVisitor; - - impl<'de> Visitor<'de> for SyntaxVisitor { - type Value = HashMap; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a map with keys and objects with a single color field as values") - } - - fn visit_map(self, mut map: M) -> Result, M::Error> - where - M: serde::de::MapAccess<'de>, - { - let mut result = HashMap::new(); - while let Some(key) = map.next_key()? { - let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla - result.insert(key, wrapper.color); - } - Ok(result) - } - } - deserializer.deserialize_map(SyntaxVisitor) -} - -#[derive(IntoElement)] -pub struct Themed> { - pub(crate) theme: Theme, - pub(crate) child: E, - pub(crate) view_type: PhantomData, -} - -impl> Element for Themed { - type PaintState = E::PaintState; - - fn layout( - &mut self, - view: &mut V, - cx: &mut ViewContext, - ) -> anyhow::Result<(gpui2::LayoutId, Self::PaintState)> - where - Self: Sized, - { - cx.push_theme(self.theme.clone()); - let result = self.child.layout(view, cx); - cx.pop_theme(); - result - } - - fn paint( - &mut self, - view: &mut V, - parent_origin: Vector2F, - layout: &gpui2::Layout, - state: &mut Self::PaintState, - cx: &mut ViewContext, - ) where - Self: Sized, - { - cx.push_theme(self.theme.clone()); - self.child.paint(view, parent_origin, layout, state, cx); - cx.pop_theme(); - } -} - -fn preferred_theme(cx: &AppContext) -> Theme { - settings::get::(cx) - .theme - .deserialized_base_theme - .lock() - .get_or_insert_with(|| { - let theme: Theme = - serde_json::from_value(settings::get::(cx).theme.base_theme.clone()) - .unwrap(); - Box::new(theme) - }) - .downcast_ref::() - .unwrap() - .clone() -} - -pub fn theme(cx: &WindowContext) -> Arc { - cx.theme::() -} diff --git a/crates/ui/src/tokens.rs b/crates/ui/src/tokens.rs deleted file mode 100644 index 5fd5b69a2a23ae62be52841984e5d5a20f38fe83..0000000000000000000000000000000000000000 --- a/crates/ui/src/tokens.rs +++ /dev/null @@ -1,25 +0,0 @@ -use gpui2::geometry::AbsoluteLength; -use gpui2::{hsla, Hsla}; - -#[derive(Clone, Copy)] -pub struct Token { - pub list_indent_depth: AbsoluteLength, - pub default_panel_size: AbsoluteLength, - pub state_hover_background: Hsla, - pub state_active_background: Hsla, -} - -impl Default for Token { - fn default() -> Self { - Self { - list_indent_depth: AbsoluteLength::Rems(0.5), - default_panel_size: AbsoluteLength::Rems(16.), - state_hover_background: hsla(0.0, 0.0, 0.0, 0.08), - state_active_background: hsla(0.0, 0.0, 0.0, 0.16), - } - } -} - -pub fn token() -> Token { - Token::default() -} diff --git a/crates/ui/tracker.md b/crates/ui/tracker.md deleted file mode 100644 index 11b5804db201ee7c2c814e00c6cd12b7cedb85c2..0000000000000000000000000000000000000000 --- a/crates/ui/tracker.md +++ /dev/null @@ -1,133 +0,0 @@ -* = Not in the app today - -## Template -- [ ] Workspace -- [ ] Title Bar -- [ ] Project Panel -- [ ] Collab Panel -- [ ] Project Diagnosics -- [ ] Project Search -- [ ] Feedback Editor -- [ ] Terminal -- [ ] Assistant -- [ ] Chat* -- [ ] Notifications* -- [ ] Status Bar -- [ ] Panes -- [ ] Pane -- [ ] Editor -- [ ] Tab Bar -- [ ] Tool Bar -- [ ] Buffer -- [ ] Zoomed Editor (Modal) - -### Palettes -- [ ] Project Files Palette (⌘-P) -- [ ] Command Palette (⌘-SHIFT-P) -- [ ] Recent Projects Palette (⌘-OPT-O) -- [ ] Recent Branches Palette (⌘-OPT-B) -- [ ] Project Symbols (⌘-T) -- [ ] Theme Palette (⌘-K, ⌘-T) -- [ ] Outline View (⌘-SHIFT-O) - -### Debug Views -- [ ] LSP Tool -- [ ] Syntax Tree - -## Modules - -### Title Bar -- [ ] Traffic Lights -- [ ] Host Menu -- [ ] Project Menu -- [ ] Branch Menu -- [ ] Collaborators -- [ ] Add Collaborator* -- [ ] Project Controls -- [ ] Call Controls -- [ ] User Menu - -### Project Panel -- [ ] Open Editors* -- [ ] Open Files (Non-project files) -- [ ] Project Files -- [ ] Root Folder - Context Menu -- [ ] Folder - Context Menu -- [ ] File - Context Menu -- [ ] Project Filter* - -### Collab Panel -- [ ] Current Call -- [ ] Channels -- [ ] Channel - Context Menu -- [ ] Contacts -- [ ] Collab Filter - -### Project Diagnosics -WIP - -### Feedback Editor -- [ ] Feedback Header -- [ ] Editor -- [ ] Feedback Actions - -### Terminal -- [ ] Terminal Toolbar* -- [ ] Terminal Line -- [ ] Terminal Input - -### Assistant -- [ ] Toolbar -- [ ] History / Past Conversations -- [ ] Model Controls / Token Counter -- [ ] Chat Editor - -### Chat -WIP - -### Notifications -WIP - -### Status Bar -- [ ] Status Bar Tool (Icon) -- [ ] Status Bar Tool (Text) -- [ ] Status Bar Tool - Context Menu -- [ ] Status Bar Tool - Popover Palette -- [ ] Status Bar Tool - Popover Menu -- [ ] Diagnostic Message -- [ ] LSP Message -- [ ] Update message (New version available, downloading, etc) - -### Panes/Pane - -- [ ] Editor -- [ ] Split Divider/Control - -### Editor -- [ ] Editor -- [ ] Read-only Editor -- [ ] Rendered Markdown View* - -### Tab Bar -- [ ] Navigation History / Control -- [ ] Tabs -- [ ] Editor Controls (New, Split, Zoom) - -### Tool Bar -- [ ] Breadcrumb -- [ ] Editor Tool (Togglable) -- [ ] Buffer Search - -### Buffer - -### Zoomed Editor (Modal) -- [ ] Modal View - -### Palette -- [ ] Input -- [ ] Section Title -- [ ] List - -## Components - -- [ ] Context Menu From c4fc9f7ed81e6f0199c24ed99a60a64dd1eb98cb Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 19:28:17 -0400 Subject: [PATCH 131/180] Eagerly attempt to resolve missing completion documentation --- crates/editor/src/editor.rs | 260 ++++++++++++++++++++++++++---------- 1 file changed, 189 insertions(+), 71 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d7ef82da3613999a61a07c4a6c66313f3434e25e..bdacf0be380d666cf35156e9900676877bc0c1a3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -25,7 +25,7 @@ use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Context, Result}; use blink_manager::BlinkManager; -use client::{ClickhouseEvent, Collaborator, ParticipantIndex, TelemetrySettings}; +use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings}; use clock::{Global, ReplicaId}; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use convert_case::{Case, Casing}; @@ -62,8 +62,8 @@ use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, - IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, - SelectionGoal, TransactionId, + IndentSize, Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, + Selection, SelectionGoal, TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -954,7 +954,7 @@ impl CompletionsMenu { ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -967,7 +967,7 @@ impl CompletionsMenu { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -980,7 +980,7 @@ impl CompletionsMenu { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -991,16 +991,99 @@ impl CompletionsMenu { ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } - fn attempt_resolve_selected_completion( + fn pre_resolve_completion_documentation( + &self, + project: Option>, + cx: &mut ViewContext, + ) { + let Some(project) = project else { + return; + }; + let client = project.read(cx).client(); + let language_registry = project.read(cx).languages().clone(); + + let is_remote = project.read(cx).is_remote(); + let project_id = project.read(cx).remote_id(); + + let completions = self.completions.clone(); + let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect(); + + cx.spawn(move |this, mut cx| async move { + if is_remote { + let Some(project_id) = project_id else { + log::error!("Remote project without remote_id"); + return; + }; + + for completion_index in completion_indices { + let completions_guard = completions.read(); + let completion = &completions_guard[completion_index]; + if completion.documentation.is_some() { + continue; + } + + let server_id = completion.server_id; + let completion = completion.lsp_completion.clone(); + drop(completions_guard); + + Self::resolve_completion_documentation_remote( + project_id, + server_id, + completions.clone(), + completion_index, + completion, + client.clone(), + language_registry.clone(), + ) + .await; + + _ = this.update(&mut cx, |_, cx| cx.notify()); + } + } else { + for completion_index in completion_indices { + let completions_guard = completions.read(); + let completion = &completions_guard[completion_index]; + if completion.documentation.is_some() { + continue; + } + + let server_id = completion.server_id; + let completion = completion.lsp_completion.clone(); + drop(completions_guard); + + let server = project.read_with(&mut cx, |project, _| { + project.language_server_for_id(server_id) + }); + let Some(server) = server else { + return; + }; + + Self::resolve_completion_documentation_local( + server, + completions.clone(), + completion_index, + completion, + language_registry.clone(), + ) + .await; + + _ = this.update(&mut cx, |_, cx| cx.notify()); + } + } + }) + .detach(); + } + + fn attempt_resolve_selected_completion_documentation( &mut self, project: Option<&ModelHandle>, cx: &mut ViewContext, ) { - let index = self.matches[self.selected_item].candidate_id; + let completion_index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { return; }; @@ -1008,7 +1091,7 @@ impl CompletionsMenu { let completions = self.completions.clone(); let completions_guard = completions.read(); - let completion = &completions_guard[index]; + let completion = &completions_guard[completion_index]; if completion.documentation.is_some() { return; } @@ -1024,54 +1107,95 @@ impl CompletionsMenu { }; let client = project.read(cx).client(); - let request = proto::ResolveCompletionDocumentation { - project_id, - language_server_id: server_id.0 as u64, - lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), - }; - - cx.spawn(|this, mut cx| async move { - let Some(response) = client - .request(request) - .await - .context("completion documentation resolve proto request") - .log_err() - else { - return; - }; - if response.text.is_empty() { - let mut completions = completions.write(); - let completion = &mut completions[index]; - completion.documentation = Some(Documentation::Undocumented); - } + cx.spawn(move |this, mut cx| async move { + Self::resolve_completion_documentation_remote( + project_id, + server_id, + completions.clone(), + completion_index, + completion, + client, + language_registry.clone(), + ) + .await; - let documentation = if response.is_markdown { - Documentation::MultiLineMarkdown( - markdown::parse_markdown(&response.text, &language_registry, None).await, - ) - } else if response.text.lines().count() <= 1 { - Documentation::SingleLine(response.text) - } else { - Documentation::MultiLinePlainText(response.text) - }; + _ = this.update(&mut cx, |_, cx| cx.notify()); + }) + .detach(); + } else { + let Some(server) = project.read(cx).language_server_for_id(server_id) else { + return; + }; - let mut completions = completions.write(); - let completion = &mut completions[index]; - completion.documentation = Some(documentation); - drop(completions); + cx.spawn(move |this, mut cx| async move { + Self::resolve_completion_documentation_local( + server, + completions, + completion_index, + completion, + language_registry, + ) + .await; _ = this.update(&mut cx, |_, cx| cx.notify()); }) .detach(); + } + } + async fn resolve_completion_documentation_remote( + project_id: u64, + server_id: LanguageServerId, + completions: Arc>>, + completion_index: usize, + completion: lsp::CompletionItem, + client: Arc, + language_registry: Arc, + ) { + let request = proto::ResolveCompletionDocumentation { + project_id, + language_server_id: server_id.0 as u64, + lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), + }; + + let Some(response) = client + .request(request) + .await + .context("completion documentation resolve proto request") + .log_err() + else { return; + }; + + if response.text.is_empty() { + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(Documentation::Undocumented); } - let Some(server) = project.read(cx).language_server_for_id(server_id) else { - return; + let documentation = if response.is_markdown { + Documentation::MultiLineMarkdown( + markdown::parse_markdown(&response.text, &language_registry, None).await, + ) + } else if response.text.lines().count() <= 1 { + Documentation::SingleLine(response.text) + } else { + Documentation::MultiLinePlainText(response.text) }; + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(documentation); + } + + async fn resolve_completion_documentation_local( + server: Arc, + completions: Arc>>, + completion_index: usize, + completion: lsp::CompletionItem, + language_registry: Arc, + ) { let can_resolve = server .capabilities() .completion_provider @@ -1082,33 +1206,27 @@ impl CompletionsMenu { return; } - cx.spawn(|this, mut cx| async move { - let request = server.request::(completion); - let Some(completion_item) = request.await.log_err() else { - return; - }; - - if let Some(lsp_documentation) = completion_item.documentation { - let documentation = language::prepare_completion_documentation( - &lsp_documentation, - &language_registry, - None, // TODO: Try to reasonably work out which language the completion is for - ) - .await; + let request = server.request::(completion); + let Some(completion_item) = request.await.log_err() else { + return; + }; - let mut completions = completions.write(); - let completion = &mut completions[index]; - completion.documentation = Some(documentation); - drop(completions); + if let Some(lsp_documentation) = completion_item.documentation { + let documentation = language::prepare_completion_documentation( + &lsp_documentation, + &language_registry, + None, // TODO: Try to reasonably work out which language the completion is for + ) + .await; - _ = this.update(&mut cx, |_, cx| cx.notify()); - } else { - let mut completions = completions.write(); - let completion = &mut completions[index]; - completion.documentation = Some(Documentation::Undocumented); - } - }) - .detach(); + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(documentation); + } else { + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(Documentation::Undocumented); + } } fn visible(&self) -> bool { @@ -3450,7 +3568,7 @@ impl Editor { None } else { _ = this.update(&mut cx, |editor, cx| { - menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); + menu.pre_resolve_completion_documentation(editor.project.clone(), cx); }); Some(menu) } From 1c3ecc4ad242047a700a602ceda0b3630b7118d0 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 21:00:31 -0400 Subject: [PATCH 132/180] Whooooops --- crates/editor/src/editor.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bdacf0be380d666cf35156e9900676877bc0c1a3..1a17f38f92a41556e4e4e7a71eedf69c8f9d70c9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3596,7 +3596,6 @@ impl Editor { let menu = menu.unwrap(); *context_menu = Some(ContextMenu::Completions(menu)); drop(context_menu); - this.completion_tasks.clear(); this.discard_copilot_suggestion(cx); cx.notify(); } else if this.completion_tasks.is_empty() { From a7db2aa39dfd5293c0569db22fa132887f53c63c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 12 Oct 2023 19:59:50 -0600 Subject: [PATCH 133/180] Add check_is_channel_participant Refactor permission checks to load ancestor permissions into memory for all checks to make the different logics more explicit. --- .../20221109000000_test_schema.sql | 3 +- crates/collab/src/db/ids.rs | 4 + crates/collab/src/db/queries/channels.rs | 180 +++++++++++++++--- crates/collab/src/db/tables/channel.rs | 2 +- crates/collab/src/db/tests/channel_tests.rs | 121 +++++++++++- crates/collab/src/tests/channel_tests.rs | 5 +- crates/rpc/proto/zed.proto | 1 + 7 files changed, 285 insertions(+), 31 deletions(-) diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index dd6e80150be6034c02368372d2ff6f6936a00ef7..dcb793aa513d0bde1a5e6f5787dc0d7f95fead99 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -192,7 +192,8 @@ CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id"); CREATE TABLE "channels" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" VARCHAR NOT NULL, - "created_at" TIMESTAMP NOT NULL DEFAULT now + "created_at" TIMESTAMP NOT NULL DEFAULT now, + "visibility" VARCHAR NOT NULL ); CREATE TABLE IF NOT EXISTS "channel_chat_participants" ( diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index d2e990a6409429307d03130e4bf1cc19093b0e57..5ba724dd1262fee425af8c6dd395416cd96aae50 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -91,6 +91,8 @@ pub enum ChannelRole { Member, #[sea_orm(string_value = "guest")] Guest, + #[sea_orm(string_value = "banned")] + Banned, } impl From for ChannelRole { @@ -99,6 +101,7 @@ impl From for ChannelRole { proto::ChannelRole::Admin => ChannelRole::Admin, proto::ChannelRole::Member => ChannelRole::Member, proto::ChannelRole::Guest => ChannelRole::Guest, + proto::ChannelRole::Banned => ChannelRole::Banned, } } } @@ -109,6 +112,7 @@ impl Into for ChannelRole { ChannelRole::Admin => proto::ChannelRole::Admin, ChannelRole::Member => proto::ChannelRole::Member, ChannelRole::Guest => proto::ChannelRole::Guest, + ChannelRole::Banned => proto::ChannelRole::Banned, } } } diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 5c96955eba5cb88d55abe35506c6867466029eee..7ce20e1a20a8eb8a5cd2a3777c20a65fc7bdb6af 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -37,8 +37,9 @@ impl Database { } let channel = channel::ActiveModel { + id: ActiveValue::NotSet, name: ActiveValue::Set(name.to_string()), - ..Default::default() + visibility: ActiveValue::Set(ChannelVisibility::ChannelMembers), } .insert(&*tx) .await?; @@ -89,6 +90,29 @@ impl Database { .await } + pub async fn set_channel_visibility( + &self, + channel_id: ChannelId, + visibility: ChannelVisibility, + user_id: UserId, + ) -> Result<()> { + self.transaction(move |tx| async move { + self.check_user_is_channel_admin(channel_id, user_id, &*tx) + .await?; + + channel::ActiveModel { + id: ActiveValue::Unchanged(channel_id), + visibility: ActiveValue::Set(visibility), + ..Default::default() + } + .update(&*tx) + .await?; + + Ok(()) + }) + .await + } + pub async fn delete_channel( &self, channel_id: ChannelId, @@ -160,11 +184,11 @@ impl Database { &self, channel_id: ChannelId, invitee_id: UserId, - inviter_id: UserId, + admin_id: UserId, role: ChannelRole, ) -> Result<()> { self.transaction(move |tx| async move { - self.check_user_is_channel_admin(channel_id, inviter_id, &*tx) + self.check_user_is_channel_admin(channel_id, admin_id, &*tx) .await?; channel_member::ActiveModel { @@ -262,10 +286,10 @@ impl Database { &self, channel_id: ChannelId, member_id: UserId, - remover_id: UserId, + admin_id: UserId, ) -> Result<()> { self.transaction(|tx| async move { - self.check_user_is_channel_admin(channel_id, remover_id, &*tx) + self.check_user_is_channel_admin(channel_id, admin_id, &*tx) .await?; let result = channel_member::Entity::delete_many() @@ -481,12 +505,12 @@ impl Database { pub async fn set_channel_member_role( &self, channel_id: ChannelId, - from: UserId, + admin_id: UserId, for_user: UserId, role: ChannelRole, ) -> Result<()> { self.transaction(|tx| async move { - self.check_user_is_channel_admin(channel_id, from, &*tx) + self.check_user_is_channel_admin(channel_id, admin_id, &*tx) .await?; let result = channel_member::Entity::update_many() @@ -613,43 +637,147 @@ impl Database { Ok(user_ids) } + pub async fn check_user_is_channel_admin( + &self, + channel_id: ChannelId, + user_id: UserId, + tx: &DatabaseTransaction, + ) -> Result<()> { + match self.channel_role_for_user(channel_id, user_id, tx).await? { + Some(ChannelRole::Admin) => Ok(()), + Some(ChannelRole::Member) + | Some(ChannelRole::Banned) + | Some(ChannelRole::Guest) + | None => Err(anyhow!( + "user is not a channel admin or channel does not exist" + ))?, + } + } + pub async fn check_user_is_channel_member( &self, channel_id: ChannelId, user_id: UserId, tx: &DatabaseTransaction, ) -> Result<()> { - let channel_ids = self.get_channel_ancestors(channel_id, tx).await?; - channel_member::Entity::find() - .filter( - channel_member::Column::ChannelId - .is_in(channel_ids) - .and(channel_member::Column::UserId.eq(user_id)), - ) - .one(&*tx) - .await? - .ok_or_else(|| anyhow!("user is not a channel member or channel does not exist"))?; - Ok(()) + match self.channel_role_for_user(channel_id, user_id, tx).await? { + Some(ChannelRole::Admin) | Some(ChannelRole::Member) => Ok(()), + Some(ChannelRole::Banned) | Some(ChannelRole::Guest) | None => Err(anyhow!( + "user is not a channel member or channel does not exist" + ))?, + } } - pub async fn check_user_is_channel_admin( + pub async fn check_user_is_channel_participant( &self, channel_id: ChannelId, user_id: UserId, tx: &DatabaseTransaction, ) -> Result<()> { + match self.channel_role_for_user(channel_id, user_id, tx).await? { + Some(ChannelRole::Admin) | Some(ChannelRole::Member) | Some(ChannelRole::Guest) => { + Ok(()) + } + Some(ChannelRole::Banned) | None => Err(anyhow!( + "user is not a channel participant or channel does not exist" + ))?, + } + } + + pub async fn channel_role_for_user( + &self, + channel_id: ChannelId, + user_id: UserId, + tx: &DatabaseTransaction, + ) -> Result> { let channel_ids = self.get_channel_ancestors(channel_id, tx).await?; - channel_member::Entity::find() + + #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] + enum QueryChannelMembership { + ChannelId, + Role, + Admin, + Visibility, + } + + let mut rows = channel_member::Entity::find() + .left_join(channel::Entity) .filter( channel_member::Column::ChannelId .is_in(channel_ids) - .and(channel_member::Column::UserId.eq(user_id)) - .and(channel_member::Column::Admin.eq(true)), + .and(channel_member::Column::UserId.eq(user_id)), ) - .one(&*tx) - .await? - .ok_or_else(|| anyhow!("user is not a channel admin or channel does not exist"))?; - Ok(()) + .select_only() + .column(channel_member::Column::ChannelId) + .column(channel_member::Column::Role) + .column(channel_member::Column::Admin) + .column(channel::Column::Visibility) + .into_values::<_, QueryChannelMembership>() + .stream(&*tx) + .await?; + + let mut is_admin = false; + let mut is_member = false; + let mut is_participant = false; + let mut is_banned = false; + let mut current_channel_visibility = None; + + // note these channels are not iterated in any particular order, + // our current logic takes the highest permission available. + while let Some(row) = rows.next().await { + let (ch_id, role, admin, visibility): ( + ChannelId, + Option, + bool, + ChannelVisibility, + ) = row?; + match role { + Some(ChannelRole::Admin) => is_admin = true, + Some(ChannelRole::Member) => is_member = true, + Some(ChannelRole::Guest) => { + if visibility == ChannelVisibility::Public { + is_participant = true + } + } + Some(ChannelRole::Banned) => is_banned = true, + None => { + // rows created from pre-role collab server. + if admin { + is_admin = true + } else { + is_member = true + } + } + } + if channel_id == ch_id { + current_channel_visibility = Some(visibility); + } + } + // free up database connection + drop(rows); + + Ok(if is_admin { + Some(ChannelRole::Admin) + } else if is_member { + Some(ChannelRole::Member) + } else if is_banned { + Some(ChannelRole::Banned) + } else if is_participant { + if current_channel_visibility.is_none() { + current_channel_visibility = channel::Entity::find() + .filter(channel::Column::Id.eq(channel_id)) + .one(&*tx) + .await? + .map(|channel| channel.visibility); + } + if current_channel_visibility == Some(ChannelVisibility::Public) { + Some(ChannelRole::Guest) + } else { + None + } + } else { + None + }) } /// Returns the channel ancestors, deepest first diff --git a/crates/collab/src/db/tables/channel.rs b/crates/collab/src/db/tables/channel.rs index efda02ec43e18fe582637c60e3c362077b4dd9af..0975a8cc30c7109f4ad34a1039780b0b33471e77 100644 --- a/crates/collab/src/db/tables/channel.rs +++ b/crates/collab/src/db/tables/channel.rs @@ -7,7 +7,7 @@ pub struct Model { #[sea_orm(primary_key)] pub id: ChannelId, pub name: String, - pub visbility: ChannelVisibility, + pub visibility: ChannelVisibility, } impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 90b3a0cd2e6e8f98c1b258cb0d603d7c9cfddd68..2263920955e256a1dfda138433dc3ddc837a61b4 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -8,11 +8,14 @@ use crate::{ db::{ queries::channels::ChannelGraph, tests::{graph, TEST_RELEASE_CHANNEL}, - ChannelId, ChannelRole, Database, NewUserParams, + ChannelId, ChannelRole, Database, NewUserParams, UserId, }, test_both_dbs, }; -use std::sync::Arc; +use std::sync::{ + atomic::{AtomicI32, Ordering}, + Arc, +}; test_both_dbs!(test_channels, test_channels_postgres, test_channels_sqlite); @@ -850,6 +853,101 @@ async fn test_db_channel_moving_bugs(db: &Arc) { ); } +test_both_dbs!( + test_user_is_channel_participant, + test_user_is_channel_participant_postgres, + test_user_is_channel_participant_sqlite +); + +async fn test_user_is_channel_participant(db: &Arc) { + let admin_id = new_test_user(db, "admin@example.com").await; + let member_id = new_test_user(db, "member@example.com").await; + let guest_id = new_test_user(db, "guest@example.com").await; + + let zed_id = db.create_root_channel("zed", admin_id).await.unwrap(); + let intermediate_id = db + .create_channel("active", Some(zed_id), admin_id) + .await + .unwrap(); + let public_id = db + .create_channel("active", Some(intermediate_id), admin_id) + .await + .unwrap(); + + db.set_channel_visibility(public_id, crate::db::ChannelVisibility::Public, admin_id) + .await + .unwrap(); + db.invite_channel_member(intermediate_id, member_id, admin_id, ChannelRole::Member) + .await + .unwrap(); + db.invite_channel_member(public_id, guest_id, admin_id, ChannelRole::Guest) + .await + .unwrap(); + + db.transaction(|tx| async move { + db.check_user_is_channel_participant(public_id, admin_id, &*tx) + .await + }) + .await + .unwrap(); + db.transaction(|tx| async move { + db.check_user_is_channel_participant(public_id, member_id, &*tx) + .await + }) + .await + .unwrap(); + db.transaction(|tx| async move { + db.check_user_is_channel_participant(public_id, guest_id, &*tx) + .await + }) + .await + .unwrap(); + + db.set_channel_member_role(public_id, admin_id, guest_id, ChannelRole::Banned) + .await + .unwrap(); + assert!(db + .transaction(|tx| async move { + db.check_user_is_channel_participant(public_id, guest_id, &*tx) + .await + }) + .await + .is_err()); + + db.remove_channel_member(public_id, guest_id, admin_id) + .await + .unwrap(); + + db.set_channel_visibility(zed_id, crate::db::ChannelVisibility::Public, admin_id) + .await + .unwrap(); + + db.invite_channel_member(zed_id, guest_id, admin_id, ChannelRole::Guest) + .await + .unwrap(); + + db.transaction(|tx| async move { + db.check_user_is_channel_participant(zed_id, guest_id, &*tx) + .await + }) + .await + .unwrap(); + assert!(db + .transaction(|tx| async move { + db.check_user_is_channel_participant(intermediate_id, guest_id, &*tx) + .await + }) + .await + .is_err(),); + + db.transaction(|tx| async move { + db.check_user_is_channel_participant(public_id, guest_id, &*tx) + .await + }) + .await + .unwrap(); +} + #[track_caller] fn assert_dag(actual: ChannelGraph, expected: &[(ChannelId, Option)]) { let mut actual_map: HashMap> = HashMap::default(); @@ -874,3 +972,22 @@ fn assert_dag(actual: ChannelGraph, expected: &[(ChannelId, Option)]) pretty_assertions::assert_eq!(actual_map, expected_map) } + +static GITHUB_USER_ID: AtomicI32 = AtomicI32::new(5); + +async fn new_test_user(db: &Arc, email: &str) -> UserId { + let gid = GITHUB_USER_ID.fetch_add(1, Ordering::SeqCst); + + db.create_user( + email, + false, + NewUserParams { + github_login: email[0..email.find("@").unwrap()].to_string(), + github_user_id: GITHUB_USER_ID.fetch_add(1, Ordering::SeqCst), + invite_count: 0, + }, + ) + .await + .unwrap() + .user_id +} diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index bc814d06a26721963fd82b345cb7dc85aee6771a..95a672e76c7b70e18c7deadbc1a19193655a8dcd 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -6,7 +6,10 @@ use call::ActiveCall; use channel::{ChannelId, ChannelMembership, ChannelStore}; use client::User; use gpui::{executor::Deterministic, ModelHandle, TestAppContext}; -use rpc::{proto, RECEIVE_TIMEOUT}; +use rpc::{ + proto::{self}, + RECEIVE_TIMEOUT, +}; use std::sync::Arc; #[gpui::test] diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index fec56ad9dc6377006efb84869d4d08a2114afe09..90e425a39ffef4ab04b05f65e94951296768124e 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1040,6 +1040,7 @@ enum ChannelRole { Admin = 0; Member = 1; Guest = 2; + Banned = 3; } message SetChannelMemberRole { From ec4391b88e9a41e47de295896cb20764f007e053 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 22:08:47 -0400 Subject: [PATCH 134/180] Add setting to disable completion docs --- assets/settings/default.json | 3 +++ crates/editor/src/editor.rs | 24 ++++++++++++++++++++++-- crates/editor/src/editor_settings.rs | 2 ++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 8fb73a2ecb0b8143f7e42981a71966327edd0f54..8a3598eed11e466934a799b7a91a52b282425e1f 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -50,6 +50,9 @@ // Whether to pop the completions menu while typing in an editor without // explicitly requesting it. "show_completions_on_input": true, + // Whether to display inline and alongside documentation for items in the + // completions menu + "show_completion_documentation": true, // Whether to show wrap guides in the editor. Setting this to true will // show a guide at the 'preferred_line_length' value if softwrap is set to // 'preferred_line_length', and will show any additional guides as specified diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1a17f38f92a41556e4e4e7a71eedf69c8f9d70c9..bb6d693d82548f61573c39ceb6d287d3fb1d8684 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1000,6 +1000,11 @@ impl CompletionsMenu { project: Option>, cx: &mut ViewContext, ) { + let settings = settings::get::(cx); + if !settings.show_completion_documentation { + return; + } + let Some(project) = project else { return; }; @@ -1083,6 +1088,11 @@ impl CompletionsMenu { project: Option<&ModelHandle>, cx: &mut ViewContext, ) { + let settings = settings::get::(cx); + if !settings.show_completion_documentation { + return; + } + let completion_index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { return; @@ -1241,6 +1251,9 @@ impl CompletionsMenu { ) -> AnyElement { enum CompletionTag {} + let settings = settings::get::(cx); + let show_completion_documentation = settings.show_completion_documentation; + let widest_completion_ix = self .matches .iter() @@ -1252,7 +1265,9 @@ impl CompletionsMenu { let mut len = completion.label.text.chars().count(); if let Some(Documentation::SingleLine(text)) = documentation { - len += text.chars().count(); + if show_completion_documentation { + len += text.chars().count(); + } } len @@ -1273,7 +1288,12 @@ impl CompletionsMenu { let item_ix = start_ix + ix; let candidate_id = mat.candidate_id; let completion = &completions_guard[candidate_id]; - let documentation = &completion.documentation; + + let documentation = if show_completion_documentation { + &completion.documentation + } else { + &None + }; items.push( MouseEventHandler::new::( diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index b06f23429a15b17368c8da23a755ad2fe3c637c5..75f8b800f93757bec3bcbbf01b68226880267d57 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -7,6 +7,7 @@ pub struct EditorSettings { pub cursor_blink: bool, pub hover_popover_enabled: bool, pub show_completions_on_input: bool, + pub show_completion_documentation: bool, pub use_on_type_format: bool, pub scrollbar: Scrollbar, pub relative_line_numbers: bool, @@ -33,6 +34,7 @@ pub struct EditorSettingsContent { pub cursor_blink: Option, pub hover_popover_enabled: Option, pub show_completions_on_input: Option, + pub show_completion_documentation: Option, pub use_on_type_format: Option, pub scrollbar: Option, pub relative_line_numbers: Option, From da2b8082b36d704131e6ce9f2555b8a17ca6ca35 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 12 Oct 2023 20:42:42 -0600 Subject: [PATCH 135/180] Rename members to participants in db crate --- crates/collab/src/db/queries/buffers.rs | 4 +++- crates/collab/src/db/queries/channels.rs | 6 +++--- crates/collab/src/db/queries/messages.rs | 4 +++- crates/collab/src/db/queries/rooms.rs | 13 +++++++++---- crates/collab/src/db/tests/channel_tests.rs | 4 ++-- crates/collab/src/rpc.rs | 2 +- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/crates/collab/src/db/queries/buffers.rs b/crates/collab/src/db/queries/buffers.rs index c85432f2bba1b62a1e346626171fb59bfb8ef7ac..69f100e6b8351a5e88d8fbda94aeb325e899634e 100644 --- a/crates/collab/src/db/queries/buffers.rs +++ b/crates/collab/src/db/queries/buffers.rs @@ -482,7 +482,9 @@ impl Database { ) .await?; - channel_members = self.get_channel_members_internal(channel_id, &*tx).await?; + channel_members = self + .get_channel_participants_internal(channel_id, &*tx) + .await?; let collaborators = self .get_channel_buffer_collaborators_internal(channel_id, &*tx) .await?; diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 7ce20e1a20a8eb8a5cd2a3777c20a65fc7bdb6af..a9601d54c827a378b3bc0d149f5567596d575778 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -498,7 +498,7 @@ impl Database { } pub async fn get_channel_members(&self, id: ChannelId) -> Result> { - self.transaction(|tx| async move { self.get_channel_members_internal(id, &*tx).await }) + self.transaction(|tx| async move { self.get_channel_participants_internal(id, &*tx).await }) .await } @@ -536,7 +536,7 @@ impl Database { .await } - pub async fn get_channel_member_details( + pub async fn get_channel_participant_details( &self, channel_id: ChannelId, user_id: UserId, @@ -616,7 +616,7 @@ impl Database { .await } - pub async fn get_channel_members_internal( + pub async fn get_channel_participants_internal( &self, id: ChannelId, tx: &DatabaseTransaction, diff --git a/crates/collab/src/db/queries/messages.rs b/crates/collab/src/db/queries/messages.rs index a48d425d90ee6021dd13f3c06febba15beec041f..7b389197750f0a2423a714e85fc389c35f9d2dc6 100644 --- a/crates/collab/src/db/queries/messages.rs +++ b/crates/collab/src/db/queries/messages.rs @@ -180,7 +180,9 @@ impl Database { ) .await?; - let mut channel_members = self.get_channel_members_internal(channel_id, &*tx).await?; + let mut channel_members = self + .get_channel_participants_internal(channel_id, &*tx) + .await?; channel_members.retain(|member| !participant_user_ids.contains(member)); Ok(( diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index a38c77dc0fc67eab6ea18da14dad43dd612bf69d..625615db5f2b5316dc9f2fbb7a9ba2ac0e98d242 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -53,7 +53,9 @@ impl Database { let (channel_id, room) = self.get_channel_room(room_id, &tx).await?; let channel_members; if let Some(channel_id) = channel_id { - channel_members = self.get_channel_members_internal(channel_id, &tx).await?; + channel_members = self + .get_channel_participants_internal(channel_id, &tx) + .await?; } else { channel_members = Vec::new(); @@ -377,7 +379,8 @@ impl Database { let room = self.get_room(room_id, &tx).await?; let channel_members = if let Some(channel_id) = channel_id { - self.get_channel_members_internal(channel_id, &tx).await? + self.get_channel_participants_internal(channel_id, &tx) + .await? } else { Vec::new() }; @@ -681,7 +684,8 @@ impl Database { let (channel_id, room) = self.get_channel_room(room_id, &tx).await?; let channel_members = if let Some(channel_id) = channel_id { - self.get_channel_members_internal(channel_id, &tx).await? + self.get_channel_participants_internal(channel_id, &tx) + .await? } else { Vec::new() }; @@ -839,7 +843,8 @@ impl Database { }; let channel_members = if let Some(channel_id) = channel_id { - self.get_channel_members_internal(channel_id, &tx).await? + self.get_channel_participants_internal(channel_id, &tx) + .await? } else { Vec::new() }; diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 2263920955e256a1dfda138433dc3ddc837a61b4..846af94a523ed5be7637afc2f0e6ad5213eb9a8c 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -322,7 +322,7 @@ async fn test_channel_invites(db: &Arc) { assert_eq!(user_3_invites, &[channel_1_1]); let members = db - .get_channel_member_details(channel_1_1, user_1) + .get_channel_participant_details(channel_1_1, user_1) .await .unwrap(); assert_eq!( @@ -356,7 +356,7 @@ async fn test_channel_invites(db: &Arc) { .unwrap(); let members = db - .get_channel_member_details(channel_1_3, user_1) + .get_channel_participant_details(channel_1_3, user_1) .await .unwrap(); assert_eq!( diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 962a032ece1a8cdb33fe1a0ecf391c7514a88baa..f8ac77325c951633df3227eab72c92f08081893b 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -2557,7 +2557,7 @@ async fn get_channel_members( let db = session.db().await; let channel_id = ChannelId::from_proto(request.channel_id); let members = db - .get_channel_member_details(channel_id, session.user_id) + .get_channel_participant_details(channel_id, session.user_id) .await?; response.send(proto::GetChannelMembersResponse { members })?; Ok(()) From 65a0ebf97598134e19a55d89e324e6655f208d28 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 12 Oct 2023 21:36:21 -0600 Subject: [PATCH 136/180] Update get_channel_participant_details to include guests --- crates/collab/src/db/ids.rs | 12 ++ crates/collab/src/db/queries/channels.rs | 106 +++++++--- crates/collab/src/db/tests/channel_tests.rs | 203 +++++++++++++------- 3 files changed, 231 insertions(+), 90 deletions(-) diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 5ba724dd1262fee425af8c6dd395416cd96aae50..ee8c879ed3da3fc3ec68a1cece455095380f7768 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -95,6 +95,18 @@ pub enum ChannelRole { Banned, } +impl ChannelRole { + pub fn should_override(&self, other: Self) -> bool { + use ChannelRole::*; + match self { + Admin => matches!(other, Member | Banned | Guest), + Member => matches!(other, Banned | Guest), + Banned => matches!(other, Guest), + Guest => false, + } + } +} + impl From for ChannelRole { fn from(value: proto::ChannelRole) -> Self { match value { diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index a9601d54c827a378b3bc0d149f5567596d575778..4cb3d00b167fa3d9d857f6dea8dc43ddbd7e343b 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -1,5 +1,7 @@ +use std::cmp::Ordering; + use super::*; -use rpc::proto::ChannelEdge; +use rpc::proto::{channel_member::Kind, ChannelEdge}; use smallvec::SmallVec; type ChannelDescendants = HashMap>; @@ -539,12 +541,19 @@ impl Database { pub async fn get_channel_participant_details( &self, channel_id: ChannelId, - user_id: UserId, + admin_id: UserId, ) -> Result> { self.transaction(|tx| async move { - self.check_user_is_channel_admin(channel_id, user_id, &*tx) + self.check_user_is_channel_admin(channel_id, admin_id, &*tx) .await?; + let channel_visibility = channel::Entity::find() + .filter(channel::Column::Id.eq(channel_id)) + .one(&*tx) + .await? + .map(|channel| channel.visibility) + .unwrap_or(ChannelVisibility::ChannelMembers); + #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] enum QueryMemberDetails { UserId, @@ -552,12 +561,13 @@ impl Database { Role, IsDirectMember, Accepted, + Visibility, } let tx = tx; let ancestor_ids = self.get_channel_ancestors(channel_id, &*tx).await?; let mut stream = channel_member::Entity::find() - .distinct() + .left_join(channel::Entity) .filter(channel_member::Column::ChannelId.is_in(ancestor_ids.iter().copied())) .select_only() .column(channel_member::Column::UserId) @@ -568,19 +578,32 @@ impl Database { QueryMemberDetails::IsDirectMember, ) .column(channel_member::Column::Accepted) - .order_by_asc(channel_member::Column::UserId) + .column(channel::Column::Visibility) .into_values::<_, QueryMemberDetails>() .stream(&*tx) .await?; - let mut rows = Vec::::new(); + struct UserDetail { + kind: Kind, + channel_role: ChannelRole, + } + let mut user_details: HashMap = HashMap::default(); + while let Some(row) = stream.next().await { - let (user_id, is_admin, channel_role, is_direct_member, is_invite_accepted): ( + let ( + user_id, + is_admin, + channel_role, + is_direct_member, + is_invite_accepted, + visibility, + ): ( UserId, bool, Option, bool, bool, + ChannelVisibility, ) = row?; let kind = match (is_direct_member, is_invite_accepted) { (true, true) => proto::channel_member::Kind::Member, @@ -593,25 +616,64 @@ impl Database { } else { ChannelRole::Member }); - let user_id = user_id.to_proto(); - let kind = kind.into(); - if let Some(last_row) = rows.last_mut() { - if last_row.user_id == user_id { - if is_direct_member { - last_row.kind = kind; - last_row.role = channel_role.into() - } - continue; + + if channel_role == ChannelRole::Guest + && visibility != ChannelVisibility::Public + && channel_visibility != ChannelVisibility::Public + { + continue; + } + + if let Some(details_mut) = user_details.get_mut(&user_id) { + if channel_role.should_override(details_mut.channel_role) { + details_mut.channel_role = channel_role; + } + if kind == Kind::Member { + details_mut.kind = kind; + // the UI is going to be a bit confusing if you already have permissions + // that are greater than or equal to the ones you're being invited to. + } else if kind == Kind::Invitee && details_mut.kind == Kind::AncestorMember { + details_mut.kind = kind; } + } else { + user_details.insert(user_id, UserDetail { kind, channel_role }); } - rows.push(proto::ChannelMember { - user_id, - kind, - role: channel_role.into(), - }); } - Ok(rows) + // sort by permissions descending, within each section, show members, then ancestor members, then invitees. + let mut results: Vec<(UserId, UserDetail)> = user_details.into_iter().collect(); + results.sort_by(|a, b| { + if a.1.channel_role.should_override(b.1.channel_role) { + return Ordering::Less; + } else if b.1.channel_role.should_override(a.1.channel_role) { + return Ordering::Greater; + } + + if a.1.kind == Kind::Member && b.1.kind != Kind::Member { + return Ordering::Less; + } else if b.1.kind == Kind::Member && a.1.kind != Kind::Member { + return Ordering::Greater; + } + + if a.1.kind == Kind::AncestorMember && b.1.kind != Kind::AncestorMember { + return Ordering::Less; + } else if b.1.kind == Kind::AncestorMember && a.1.kind != Kind::AncestorMember { + return Ordering::Greater; + } + + // would be nice to sort alphabetically instead of by user id. + // (or defer all sorting to the UI, but we need something to help the tests) + return a.0.cmp(&b.0); + }); + + Ok(results + .into_iter() + .map(|(user_id, details)| proto::ChannelMember { + user_id: user_id.to_proto(), + kind: details.kind.into(), + role: details.channel_role.into(), + }) + .collect()) }) .await } diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 846af94a523ed5be7637afc2f0e6ad5213eb9a8c..2044310d8e025119de85dbb49cde3149ced93c6c 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -246,46 +246,9 @@ test_both_dbs!( async fn test_channel_invites(db: &Arc) { db.create_server("test").await.unwrap(); - let user_1 = db - .create_user( - "user1@example.com", - false, - NewUserParams { - github_login: "user1".into(), - github_user_id: 5, - invite_count: 0, - }, - ) - .await - .unwrap() - .user_id; - let user_2 = db - .create_user( - "user2@example.com", - false, - NewUserParams { - github_login: "user2".into(), - github_user_id: 6, - invite_count: 0, - }, - ) - .await - .unwrap() - .user_id; - - let user_3 = db - .create_user( - "user3@example.com", - false, - NewUserParams { - github_login: "user3".into(), - github_user_id: 7, - invite_count: 0, - }, - ) - .await - .unwrap() - .user_id; + let user_1 = new_test_user(db, "user1@example.com").await; + let user_2 = new_test_user(db, "user2@example.com").await; + let user_3 = new_test_user(db, "user3@example.com").await; let channel_1_1 = db.create_root_channel("channel_1", user_1).await.unwrap(); @@ -334,14 +297,14 @@ async fn test_channel_invites(db: &Arc) { role: proto::ChannelRole::Admin.into(), }, proto::ChannelMember { - user_id: user_2.to_proto(), + user_id: user_3.to_proto(), kind: proto::channel_member::Kind::Invitee.into(), - role: proto::ChannelRole::Member.into(), + role: proto::ChannelRole::Admin.into(), }, proto::ChannelMember { - user_id: user_3.to_proto(), + user_id: user_2.to_proto(), kind: proto::channel_member::Kind::Invitee.into(), - role: proto::ChannelRole::Admin.into(), + role: proto::ChannelRole::Member.into(), }, ] ); @@ -860,92 +823,198 @@ test_both_dbs!( ); async fn test_user_is_channel_participant(db: &Arc) { - let admin_id = new_test_user(db, "admin@example.com").await; - let member_id = new_test_user(db, "member@example.com").await; - let guest_id = new_test_user(db, "guest@example.com").await; + let admin = new_test_user(db, "admin@example.com").await; + let member = new_test_user(db, "member@example.com").await; + let guest = new_test_user(db, "guest@example.com").await; - let zed_id = db.create_root_channel("zed", admin_id).await.unwrap(); - let intermediate_id = db - .create_channel("active", Some(zed_id), admin_id) + let zed_channel = db.create_root_channel("zed", admin).await.unwrap(); + let active_channel = db + .create_channel("active", Some(zed_channel), admin) .await .unwrap(); - let public_id = db - .create_channel("active", Some(intermediate_id), admin_id) + let vim_channel = db + .create_channel("vim", Some(active_channel), admin) .await .unwrap(); - db.set_channel_visibility(public_id, crate::db::ChannelVisibility::Public, admin_id) + db.set_channel_visibility(vim_channel, crate::db::ChannelVisibility::Public, admin) + .await + .unwrap(); + db.invite_channel_member(active_channel, member, admin, ChannelRole::Member) .await .unwrap(); - db.invite_channel_member(intermediate_id, member_id, admin_id, ChannelRole::Member) + db.invite_channel_member(vim_channel, guest, admin, ChannelRole::Guest) .await .unwrap(); - db.invite_channel_member(public_id, guest_id, admin_id, ChannelRole::Guest) + + db.respond_to_channel_invite(active_channel, member, true) .await .unwrap(); db.transaction(|tx| async move { - db.check_user_is_channel_participant(public_id, admin_id, &*tx) + db.check_user_is_channel_participant(vim_channel, admin, &*tx) .await }) .await .unwrap(); db.transaction(|tx| async move { - db.check_user_is_channel_participant(public_id, member_id, &*tx) + db.check_user_is_channel_participant(vim_channel, member, &*tx) .await }) .await .unwrap(); db.transaction(|tx| async move { - db.check_user_is_channel_participant(public_id, guest_id, &*tx) + db.check_user_is_channel_participant(vim_channel, guest, &*tx) .await }) .await .unwrap(); - db.set_channel_member_role(public_id, admin_id, guest_id, ChannelRole::Banned) + let members = db + .get_channel_participant_details(vim_channel, admin) + .await + .unwrap(); + assert_eq!( + members, + &[ + proto::ChannelMember { + user_id: admin.to_proto(), + kind: proto::channel_member::Kind::Member.into(), + role: proto::ChannelRole::Admin.into(), + }, + proto::ChannelMember { + user_id: member.to_proto(), + kind: proto::channel_member::Kind::AncestorMember.into(), + role: proto::ChannelRole::Member.into(), + }, + proto::ChannelMember { + user_id: guest.to_proto(), + kind: proto::channel_member::Kind::Invitee.into(), + role: proto::ChannelRole::Guest.into(), + }, + ] + ); + + db.set_channel_member_role(vim_channel, admin, guest, ChannelRole::Banned) .await .unwrap(); assert!(db .transaction(|tx| async move { - db.check_user_is_channel_participant(public_id, guest_id, &*tx) + db.check_user_is_channel_participant(vim_channel, guest, &*tx) .await }) .await .is_err()); - db.remove_channel_member(public_id, guest_id, admin_id) + let members = db + .get_channel_participant_details(vim_channel, admin) + .await + .unwrap(); + + assert_eq!( + members, + &[ + proto::ChannelMember { + user_id: admin.to_proto(), + kind: proto::channel_member::Kind::Member.into(), + role: proto::ChannelRole::Admin.into(), + }, + proto::ChannelMember { + user_id: member.to_proto(), + kind: proto::channel_member::Kind::AncestorMember.into(), + role: proto::ChannelRole::Member.into(), + }, + proto::ChannelMember { + user_id: guest.to_proto(), + kind: proto::channel_member::Kind::Invitee.into(), + role: proto::ChannelRole::Banned.into(), + }, + ] + ); + + db.remove_channel_member(vim_channel, guest, admin) .await .unwrap(); - db.set_channel_visibility(zed_id, crate::db::ChannelVisibility::Public, admin_id) + db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin) .await .unwrap(); - db.invite_channel_member(zed_id, guest_id, admin_id, ChannelRole::Guest) + db.invite_channel_member(zed_channel, guest, admin, ChannelRole::Guest) .await .unwrap(); db.transaction(|tx| async move { - db.check_user_is_channel_participant(zed_id, guest_id, &*tx) + db.check_user_is_channel_participant(zed_channel, guest, &*tx) .await }) .await .unwrap(); assert!(db .transaction(|tx| async move { - db.check_user_is_channel_participant(intermediate_id, guest_id, &*tx) + db.check_user_is_channel_participant(active_channel, guest, &*tx) .await }) .await .is_err(),); db.transaction(|tx| async move { - db.check_user_is_channel_participant(public_id, guest_id, &*tx) + db.check_user_is_channel_participant(vim_channel, guest, &*tx) .await }) .await .unwrap(); + + // currently people invited to parent channels are not shown here + // (though they *do* have permissions!) + let members = db + .get_channel_participant_details(vim_channel, admin) + .await + .unwrap(); + assert_eq!( + members, + &[ + proto::ChannelMember { + user_id: admin.to_proto(), + kind: proto::channel_member::Kind::Member.into(), + role: proto::ChannelRole::Admin.into(), + }, + proto::ChannelMember { + user_id: member.to_proto(), + kind: proto::channel_member::Kind::AncestorMember.into(), + role: proto::ChannelRole::Member.into(), + }, + ] + ); + + db.respond_to_channel_invite(zed_channel, guest, true) + .await + .unwrap(); + + let members = db + .get_channel_participant_details(vim_channel, admin) + .await + .unwrap(); + assert_eq!( + members, + &[ + proto::ChannelMember { + user_id: admin.to_proto(), + kind: proto::channel_member::Kind::Member.into(), + role: proto::ChannelRole::Admin.into(), + }, + proto::ChannelMember { + user_id: member.to_proto(), + kind: proto::channel_member::Kind::AncestorMember.into(), + role: proto::ChannelRole::Member.into(), + }, + proto::ChannelMember { + user_id: guest.to_proto(), + kind: proto::channel_member::Kind::AncestorMember.into(), + role: proto::ChannelRole::Guest.into(), + }, + ] + ); } #[track_caller] @@ -976,8 +1045,6 @@ fn assert_dag(actual: ChannelGraph, expected: &[(ChannelId, Option)]) static GITHUB_USER_ID: AtomicI32 = AtomicI32::new(5); async fn new_test_user(db: &Arc, email: &str) -> UserId { - let gid = GITHUB_USER_ID.fetch_add(1, Ordering::SeqCst); - db.create_user( email, false, From 525ff6bf7458a2f48747fc9e06dd10e83845554b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Oct 2023 10:20:55 +0300 Subject: [PATCH 137/180] Remove zed -> ... -> semantic_index -> zed Cargo dependency cycle --- Cargo.lock | 2 +- crates/semantic_index/Cargo.toml | 4 ---- crates/zed/Cargo.toml | 4 ++++ .../examples/eval.rs => zed/examples/semantic_index_eval.rs} | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename crates/{semantic_index/examples/eval.rs => zed/examples/semantic_index_eval.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index 01153ca0f8222e341ee77287518dca45ed53048d..85b80fa487d0c30f2fc0dc8df1f8076dbda54e98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6956,7 +6956,6 @@ dependencies = [ "unindent", "util", "workspace", - "zed", ] [[package]] @@ -10049,6 +10048,7 @@ name = "zed" version = "0.109.0" dependencies = [ "activity_indicator", + "ai", "anyhow", "assistant", "async-compression", diff --git a/crates/semantic_index/Cargo.toml b/crates/semantic_index/Cargo.toml index 34850f7035c15aba1e7a1ba1c54bc6b1431a5c9d..1febb2af786e205e19764e90ca1c78d954d8a1bb 100644 --- a/crates/semantic_index/Cargo.toml +++ b/crates/semantic_index/Cargo.toml @@ -51,7 +51,6 @@ workspace = { path = "../workspace", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"]} rust-embed = { version = "8.0", features = ["include-exclude"] } client = { path = "../client" } -zed = { path = "../zed"} node_runtime = { path = "../node_runtime"} pretty_assertions.workspace = true @@ -70,6 +69,3 @@ tree-sitter-elixir.workspace = true tree-sitter-lua.workspace = true tree-sitter-ruby.workspace = true tree-sitter-php.workspace = true - -[[example]] -name = "eval" diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 4174f7d6d54089ffe4250d3a6f80ab0060b1306d..f9abcc1e91d2bd07671e2e8fa881ab96a74d234f 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -15,6 +15,9 @@ doctest = false name = "Zed" path = "src/main.rs" +[[example]] +name = "semantic_index_eval" + [dependencies] audio = { path = "../audio" } activity_indicator = { path = "../activity_indicator" } @@ -141,6 +144,7 @@ urlencoding = "2.1.2" uuid.workspace = true [dev-dependencies] +ai = { path = "../ai" } call = { path = "../call", features = ["test-support"] } client = { path = "../client", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/semantic_index/examples/eval.rs b/crates/zed/examples/semantic_index_eval.rs similarity index 100% rename from crates/semantic_index/examples/eval.rs rename to crates/zed/examples/semantic_index_eval.rs From 803ab81eb6e1cc718679fdba862163c23d7ef174 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Oct 2023 12:13:18 +0300 Subject: [PATCH 138/180] Update diagnostics indicator when diagnostics are udpated --- crates/diagnostics/src/items.rs | 4 ++++ crates/project/src/project.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index c3733018b67e0142115c3baac2d8a068d5f6e328..8d3c2fedd6d9d65d7d28e0a88f05f1e85161d117 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -38,6 +38,10 @@ impl DiagnosticIndicator { this.in_progress_checks.remove(language_server_id); cx.notify(); } + project::Event::DiagnosticsUpdated { .. } => { + this.summary = project.read(cx).diagnostic_summary(cx); + cx.notify(); + } _ => {} }) .detach(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f9e1b1ce9607993d4e4cf4db0fe90a82447a542e..e3251d74835a39648e379607a56dfc25105f7ab4 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2934,8 +2934,8 @@ impl Project { move |mut params, mut cx| { let this = this; let adapter = adapter.clone(); - adapter.process_diagnostics(&mut params); if let Some(this) = this.upgrade(&cx) { + adapter.process_diagnostics(&mut params); this.update(&mut cx, |this, cx| { this.update_diagnostics( server_id, From bfbe4ae4b47140024cdaf8d9680956bd228d6b84 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 13 Oct 2023 18:58:59 +0200 Subject: [PATCH 139/180] Piotr/z 651 vue support (#3123) Release Notes: - Added Vue language support. --- Cargo.lock | 10 + Cargo.toml | 2 +- crates/language/src/language.rs | 3 - crates/project/src/project.rs | 25 ++- crates/util/src/github.rs | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 10 +- crates/zed/src/languages/vue.rs | 214 ++++++++++++++++++++ crates/zed/src/languages/vue/brackets.scm | 2 + crates/zed/src/languages/vue/config.toml | 14 ++ crates/zed/src/languages/vue/highlights.scm | 15 ++ crates/zed/src/languages/vue/injections.scm | 7 + 12 files changed, 286 insertions(+), 18 deletions(-) create mode 100644 crates/zed/src/languages/vue.rs create mode 100644 crates/zed/src/languages/vue/brackets.scm create mode 100644 crates/zed/src/languages/vue/config.toml create mode 100644 crates/zed/src/languages/vue/highlights.scm create mode 100644 crates/zed/src/languages/vue/injections.scm diff --git a/Cargo.lock b/Cargo.lock index 85b80fa487d0c30f2fc0dc8df1f8076dbda54e98..2ef86073ceab93bb85943d25526d994d99e9bf24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8792,6 +8792,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-vue" +version = "0.0.1" +source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=95b2890#95b28908d90e928c308866f7631e73ef6e1d4b5f" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-yaml" version = "0.0.1" @@ -10161,6 +10170,7 @@ dependencies = [ "tree-sitter-svelte", "tree-sitter-toml", "tree-sitter-typescript", + "tree-sitter-vue", "tree-sitter-yaml", "unindent", "url", diff --git a/Cargo.toml b/Cargo.toml index 532610efd631edb05fee1040b2c2800e9c256a42..995cd15edd45d6c983fa20c19c6ba82749300260 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -149,7 +149,7 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", tree-sitter-lua = "0.0.14" tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} - +tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "95b2890"} [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index bd389652a024c6777c34ae30c6952bf318010458..eb6f6e89f70bfe5fd823b86d2578f469df785159 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -110,7 +110,6 @@ pub struct LanguageServerName(pub Arc); pub struct CachedLspAdapter { pub name: LanguageServerName, pub short_name: &'static str, - pub initialization_options: Option, pub disk_based_diagnostic_sources: Vec, pub disk_based_diagnostics_progress_token: Option, pub language_ids: HashMap, @@ -121,7 +120,6 @@ impl CachedLspAdapter { pub async fn new(adapter: Arc) -> Arc { let name = adapter.name().await; let short_name = adapter.short_name(); - let initialization_options = adapter.initialization_options().await; let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await; let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token().await; @@ -130,7 +128,6 @@ impl CachedLspAdapter { Arc::new(CachedLspAdapter { name, short_name, - initialization_options, disk_based_diagnostic_sources, disk_based_diagnostics_progress_token, language_ids, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e3251d74835a39648e379607a56dfc25105f7ab4..875086a4e397ced93311104bb53ac85f3e9109be 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2751,15 +2751,6 @@ impl Project { let lsp = project_settings.lsp.get(&adapter.name.0); let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); - let mut initialization_options = adapter.initialization_options.clone(); - match (&mut initialization_options, override_options) { - (Some(initialization_options), Some(override_options)) => { - merge_json_value_into(override_options, initialization_options); - } - (None, override_options) => initialization_options = override_options, - _ => {} - } - let server_id = pending_server.server_id; let container_dir = pending_server.container_dir.clone(); let state = LanguageServerState::Starting({ @@ -2771,7 +2762,7 @@ impl Project { cx.spawn_weak(|this, mut cx| async move { let result = Self::setup_and_insert_language_server( this, - initialization_options, + override_options, pending_server, adapter.clone(), language.clone(), @@ -2874,7 +2865,7 @@ impl Project { async fn setup_and_insert_language_server( this: WeakModelHandle, - initialization_options: Option, + override_initialization_options: Option, pending_server: PendingLanguageServer, adapter: Arc, language: Arc, @@ -2884,7 +2875,7 @@ impl Project { ) -> Result>> { let setup = Self::setup_pending_language_server( this, - initialization_options, + override_initialization_options, pending_server, adapter.clone(), server_id, @@ -2916,7 +2907,7 @@ impl Project { async fn setup_pending_language_server( this: WeakModelHandle, - initialization_options: Option, + override_options: Option, pending_server: PendingLanguageServer, adapter: Arc, server_id: LanguageServerId, @@ -3062,6 +3053,14 @@ impl Project { } }) .detach(); + let mut initialization_options = adapter.adapter.initialization_options().await; + match (&mut initialization_options, override_options) { + (Some(initialization_options), Some(override_options)) => { + merge_json_value_into(override_options, initialization_options); + } + (None, override_options) => initialization_options = override_options, + _ => {} + } let language_server = language_server.initialize(initialization_options).await?; diff --git a/crates/util/src/github.rs b/crates/util/src/github.rs index b1e981ae4963e19829a91cc3cd961d604c9d1643..a3df4c996bea900f44b2f485d344809e1cf762b6 100644 --- a/crates/util/src/github.rs +++ b/crates/util/src/github.rs @@ -16,6 +16,7 @@ pub struct GithubRelease { pub pre_release: bool, pub assets: Vec, pub tarball_url: String, + pub zipball_url: String, } #[derive(Deserialize, Debug)] diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index f9abcc1e91d2bd07671e2e8fa881ab96a74d234f..aeabd4b45315b238be8e2fade5d0f13396324f1b 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -138,6 +138,7 @@ tree-sitter-yaml.workspace = true tree-sitter-lua.workspace = true tree-sitter-nix.workspace = true tree-sitter-nu.workspace = true +tree-sitter-vue.workspace = true url = "2.2" urlencoding = "2.1.2" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 04e5292a7df6cf4fa9c58d1999aed246f963c352..caf3cbf7c948ec239b8e620d30587f79c5e9b291 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -24,6 +24,7 @@ mod rust; mod svelte; mod tailwind; mod typescript; +mod vue; mod yaml; // 1. Add tree-sitter-{language} parser to zed crate @@ -190,13 +191,20 @@ pub fn init( language( "php", tree_sitter_php::language(), - vec![Arc::new(php::IntelephenseLspAdapter::new(node_runtime))], + vec![Arc::new(php::IntelephenseLspAdapter::new( + node_runtime.clone(), + ))], ); language("elm", tree_sitter_elm::language(), vec![]); language("glsl", tree_sitter_glsl::language(), vec![]); language("nix", tree_sitter_nix::language(), vec![]); language("nu", tree_sitter_nu::language(), vec![]); + language( + "vue", + tree_sitter_vue::language(), + vec![Arc::new(vue::VueLspAdapter::new(node_runtime))], + ); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed/src/languages/vue.rs b/crates/zed/src/languages/vue.rs new file mode 100644 index 0000000000000000000000000000000000000000..f0374452dfa6104c24709f975deffdf3cfe782af --- /dev/null +++ b/crates/zed/src/languages/vue.rs @@ -0,0 +1,214 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use futures::StreamExt; +pub use language::*; +use lsp::{CodeActionKind, LanguageServerBinary}; +use node_runtime::NodeRuntime; +use parking_lot::Mutex; +use serde_json::Value; +use smol::fs::{self}; +use std::{ + any::Any, + ffi::OsString, + path::{Path, PathBuf}, + sync::Arc, +}; +use util::ResultExt; + +pub struct VueLspVersion { + vue_version: String, + ts_version: String, +} + +pub struct VueLspAdapter { + node: Arc, + typescript_install_path: Mutex>, +} + +impl VueLspAdapter { + const SERVER_PATH: &'static str = + "node_modules/@vue/language-server/bin/vue-language-server.js"; + // TODO: this can't be hardcoded, yet we have to figure out how to pass it in initialization_options. + const TYPESCRIPT_PATH: &'static str = "node_modules/typescript/lib"; + pub fn new(node: Arc) -> Self { + let typescript_install_path = Mutex::new(None); + Self { + node, + typescript_install_path, + } + } +} +#[async_trait] +impl super::LspAdapter for VueLspAdapter { + async fn name(&self) -> LanguageServerName { + LanguageServerName("vue-language-server".into()) + } + + fn short_name(&self) -> &'static str { + "vue-language-server" + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new(VueLspVersion { + vue_version: self + .node + .npm_package_latest_version("@vue/language-server") + .await?, + ts_version: self.node.npm_package_latest_version("typescript").await?, + }) as Box<_>) + } + async fn initialization_options(&self) -> Option { + let typescript_sdk_path = self.typescript_install_path.lock(); + let typescript_sdk_path = typescript_sdk_path + .as_ref() + .expect("initialization_options called without a container_dir for typescript"); + + Some(serde_json::json!({ + "typescript": { + "tsdk": typescript_sdk_path + } + })) + } + fn code_action_kinds(&self) -> Option> { + // REFACTOR is explicitly disabled, as vue-lsp does not adhere to LSP protocol for code actions with these - it + // sends back a CodeAction with neither `command` nor `edits` fields set, which is against the spec. + Some(vec![ + CodeActionKind::EMPTY, + CodeActionKind::QUICKFIX, + CodeActionKind::REFACTOR_REWRITE, + ]) + } + async fn fetch_server_binary( + &self, + version: Box, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + let version = version.downcast::().unwrap(); + let server_path = container_dir.join(Self::SERVER_PATH); + let ts_path = container_dir.join(Self::TYPESCRIPT_PATH); + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages( + &container_dir, + &[("@vue/language-server", version.vue_version.as_str())], + ) + .await?; + } + assert!(fs::metadata(&server_path).await.is_ok()); + if fs::metadata(&ts_path).await.is_err() { + self.node + .npm_install_packages( + &container_dir, + &[("typescript", version.ts_version.as_str())], + ) + .await?; + } + + assert!(fs::metadata(&ts_path).await.is_ok()); + *self.typescript_install_path.lock() = Some(ts_path); + Ok(LanguageServerBinary { + path: self.node.binary_path().await?, + arguments: vue_server_binary_arguments(&server_path), + }) + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + let (server, ts_path) = get_cached_server_binary(container_dir, self.node.clone()).await?; + *self.typescript_install_path.lock() = Some(ts_path); + Some(server) + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + let (server, ts_path) = get_cached_server_binary(container_dir, self.node.clone()) + .await + .map(|(mut binary, ts_path)| { + binary.arguments = vec!["--help".into()]; + (binary, ts_path) + })?; + *self.typescript_install_path.lock() = Some(ts_path); + Some(server) + } + + async fn label_for_completion( + &self, + item: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + use lsp::CompletionItemKind as Kind; + let len = item.label.len(); + let grammar = language.grammar()?; + let highlight_id = match item.kind? { + Kind::CLASS | Kind::INTERFACE => grammar.highlight_id_for_name("type"), + Kind::CONSTRUCTOR => grammar.highlight_id_for_name("type"), + Kind::CONSTANT => grammar.highlight_id_for_name("constant"), + Kind::FUNCTION | Kind::METHOD => grammar.highlight_id_for_name("function"), + Kind::PROPERTY | Kind::FIELD => grammar.highlight_id_for_name("tag"), + Kind::VARIABLE => grammar.highlight_id_for_name("type"), + Kind::KEYWORD => grammar.highlight_id_for_name("keyword"), + Kind::VALUE => grammar.highlight_id_for_name("tag"), + _ => None, + }?; + + let text = match &item.detail { + Some(detail) => format!("{} {}", item.label, detail), + None => item.label.clone(), + }; + + Some(language::CodeLabel { + text, + runs: vec![(0..len, highlight_id)], + filter_range: 0..len, + }) + } +} + +fn vue_server_binary_arguments(server_path: &Path) -> Vec { + vec![server_path.into(), "--stdio".into()] +} + +type TypescriptPath = PathBuf; +async fn get_cached_server_binary( + container_dir: PathBuf, + node: Arc, +) -> Option<(LanguageServerBinary, TypescriptPath)> { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(VueLspAdapter::SERVER_PATH); + let typescript_path = last_version_dir.join(VueLspAdapter::TYPESCRIPT_PATH); + if server_path.exists() && typescript_path.exists() { + Ok(( + LanguageServerBinary { + path: node.binary_path().await?, + arguments: vue_server_binary_arguments(&server_path), + }, + typescript_path, + )) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} diff --git a/crates/zed/src/languages/vue/brackets.scm b/crates/zed/src/languages/vue/brackets.scm new file mode 100644 index 0000000000000000000000000000000000000000..2d12b17daab82f7209c3b32cc4047d805e3b2415 --- /dev/null +++ b/crates/zed/src/languages/vue/brackets.scm @@ -0,0 +1,2 @@ +("<" @open ">" @close) +("\"" @open "\"" @close) diff --git a/crates/zed/src/languages/vue/config.toml b/crates/zed/src/languages/vue/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..c41a667b752c53bd2fed270490d2bc56fe1f04ed --- /dev/null +++ b/crates/zed/src/languages/vue/config.toml @@ -0,0 +1,14 @@ +name = "Vue.js" +path_suffixes = ["vue"] +block_comment = [""] +autoclose_before = ";:.,=}])>" +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = true, newline = true, not_in = ["string", "comment"] }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, + { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] }, + { start = "`", end = "`", close = true, newline = false, not_in = ["string"] }, +] +word_characters = ["-"] diff --git a/crates/zed/src/languages/vue/highlights.scm b/crates/zed/src/languages/vue/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..1a80c84f68edadf0417cd159c91840102719add7 --- /dev/null +++ b/crates/zed/src/languages/vue/highlights.scm @@ -0,0 +1,15 @@ +(attribute) @property +(directive_attribute) @property +(quoted_attribute_value) @string +(interpolation) @punctuation.special +(raw_text) @embedded + +((tag_name) @type + (#match? @type "^[A-Z]")) + +((directive_name) @keyword + (#match? @keyword "^v-")) + +(start_tag) @tag +(end_tag) @tag +(self_closing_tag) @tag diff --git a/crates/zed/src/languages/vue/injections.scm b/crates/zed/src/languages/vue/injections.scm new file mode 100644 index 0000000000000000000000000000000000000000..9084e373f217b95cf70bad9cc907d5d9cd127391 --- /dev/null +++ b/crates/zed/src/languages/vue/injections.scm @@ -0,0 +1,7 @@ +(script_element + (raw_text) @content + (#set! "language" "javascript")) + +(style_element + (raw_text) @content + (#set! "language" "css")) From a8e352a473eac6d3581ba3ee39bd0ee6da814752 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 13 Oct 2023 11:34:13 -0600 Subject: [PATCH 140/180] Rewrite get_user_channels with new permissions --- crates/channel/src/channel_store_tests.rs | 2 +- crates/collab/src/db/queries/channels.rs | 174 ++++++++++++++++++++-- 2 files changed, 159 insertions(+), 17 deletions(-) diff --git a/crates/channel/src/channel_store_tests.rs b/crates/channel/src/channel_store_tests.rs index faa0ade51d215f3ef5a78d001fd5779405a52c48..ea47c7c7b7fe0f5db2c8776cebc7791d9b306a91 100644 --- a/crates/channel/src/channel_store_tests.rs +++ b/crates/channel/src/channel_store_tests.rs @@ -3,7 +3,7 @@ use crate::channel_chat::ChannelChatEvent; use super::*; use client::{test::FakeServer, Client, UserStore}; use gpui::{AppContext, ModelHandle, TestAppContext}; -use rpc::proto::{self, ChannelRole}; +use rpc::proto::{self}; use settings::SettingsStore; use util::http::FakeHttpClient; diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 4cb3d00b167fa3d9d857f6dea8dc43ddbd7e343b..625655f27735f707d4f05e8b28041406b3f89be9 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -439,25 +439,108 @@ impl Database { channel_memberships: Vec, tx: &DatabaseTransaction, ) -> Result { - let parents_by_child_id = self - .get_channel_descendants(channel_memberships.iter().map(|m| m.channel_id), &*tx) + let mut edges = self + .get_channel_descendants_2(channel_memberships.iter().map(|m| m.channel_id), &*tx) .await?; - let channels_with_admin_privileges = channel_memberships - .iter() - .filter_map(|membership| { - if membership.role == Some(ChannelRole::Admin) || membership.admin { - Some(membership.channel_id) + let mut role_for_channel: HashMap = HashMap::default(); + + for membership in channel_memberships.iter() { + role_for_channel.insert( + membership.channel_id, + membership.role.unwrap_or(if membership.admin { + ChannelRole::Admin } else { - None + ChannelRole::Member + }), + ); + } + + for ChannelEdge { + parent_id, + channel_id, + } in edges.iter() + { + let parent_id = ChannelId::from_proto(*parent_id); + let channel_id = ChannelId::from_proto(*channel_id); + debug_assert!(role_for_channel.get(&parent_id).is_some()); + let parent_role = role_for_channel[&parent_id]; + if let Some(existing_role) = role_for_channel.get(&channel_id) { + if existing_role.should_override(parent_role) { + continue; } - }) - .collect(); + } + role_for_channel.insert(channel_id, parent_role); + } + + let mut channels: Vec = Vec::new(); + let mut channels_with_admin_privileges: HashSet = HashSet::default(); + let mut channels_to_remove: HashSet = HashSet::default(); - let graph = self - .get_channel_graph(parents_by_child_id, true, &tx) + let mut rows = channel::Entity::find() + .filter(channel::Column::Id.is_in(role_for_channel.keys().cloned())) + .stream(&*tx) .await?; + while let Some(row) = rows.next().await { + let channel = row?; + let role = role_for_channel[&channel.id]; + + if role == ChannelRole::Banned + || role == ChannelRole::Guest && channel.visibility != ChannelVisibility::Public + { + channels_to_remove.insert(channel.id.0 as u64); + continue; + } + + channels.push(Channel { + id: channel.id, + name: channel.name, + }); + + if role == ChannelRole::Admin { + channels_with_admin_privileges.insert(channel.id); + } + } + drop(rows); + + if !channels_to_remove.is_empty() { + // Note: this code assumes each channel has one parent. + let mut replacement_parent: HashMap = HashMap::default(); + for ChannelEdge { + parent_id, + channel_id, + } in edges.iter() + { + if channels_to_remove.contains(channel_id) { + replacement_parent.insert(*channel_id, *parent_id); + } + } + + let mut new_edges: Vec = Vec::new(); + 'outer: for ChannelEdge { + mut parent_id, + channel_id, + } in edges.iter() + { + if channels_to_remove.contains(channel_id) { + continue; + } + while channels_to_remove.contains(&parent_id) { + if let Some(new_parent_id) = replacement_parent.get(&parent_id) { + parent_id = *new_parent_id; + } else { + continue 'outer; + } + } + new_edges.push(ChannelEdge { + parent_id, + channel_id: *channel_id, + }) + } + edges = new_edges; + } + #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] enum QueryUserIdsAndChannelIds { ChannelId, @@ -468,7 +551,7 @@ impl Database { { let mut rows = room_participant::Entity::find() .inner_join(room::Entity) - .filter(room::Column::ChannelId.is_in(graph.channels.iter().map(|c| c.id))) + .filter(room::Column::ChannelId.is_in(channels.iter().map(|c| c.id))) .select_only() .column(room::Column::ChannelId) .column(room_participant::Column::UserId) @@ -481,7 +564,7 @@ impl Database { } } - let channel_ids = graph.channels.iter().map(|c| c.id).collect::>(); + let channel_ids = channels.iter().map(|c| c.id).collect::>(); let channel_buffer_changes = self .unseen_channel_buffer_changes(user_id, &channel_ids, &*tx) .await?; @@ -491,7 +574,7 @@ impl Database { .await?; Ok(ChannelsForUser { - channels: graph, + channels: ChannelGraph { channels, edges }, channel_participants, channels_with_admin_privileges, unseen_buffer_changes: channel_buffer_changes, @@ -842,7 +925,7 @@ impl Database { }) } - /// Returns the channel ancestors, deepest first + /// Returns the channel ancestors, include itself, deepest first pub async fn get_channel_ancestors( &self, channel_id: ChannelId, @@ -867,6 +950,65 @@ impl Database { Ok(channel_ids) } + // Returns the channel desendants as a sorted list of edges for further processing. + // The edges are sorted such that you will see unknown channel ids as children + // before you see them as parents. + async fn get_channel_descendants_2( + &self, + channel_ids: impl IntoIterator, + tx: &DatabaseTransaction, + ) -> Result> { + let mut values = String::new(); + for id in channel_ids { + if !values.is_empty() { + values.push_str(", "); + } + write!(&mut values, "({})", id).unwrap(); + } + + if values.is_empty() { + return Ok(vec![]); + } + + let sql = format!( + r#" + SELECT + descendant_paths.* + FROM + channel_paths parent_paths, channel_paths descendant_paths + WHERE + parent_paths.channel_id IN ({values}) AND + descendant_paths.id_path != parent_paths.id_path AND + descendant_paths.id_path LIKE (parent_paths.id_path || '%') + ORDER BY + descendant_paths.id_path + "# + ); + + let stmt = Statement::from_string(self.pool.get_database_backend(), sql); + + let mut paths = channel_path::Entity::find() + .from_raw_sql(stmt) + .stream(tx) + .await?; + + let mut results: Vec = Vec::new(); + while let Some(path) = paths.next().await { + let path = path?; + let ids: Vec<&str> = path.id_path.trim_matches('/').split('/').collect(); + + debug_assert!(ids.len() >= 2); + debug_assert!(ids[ids.len() - 1] == path.channel_id.to_string()); + + results.push(ChannelEdge { + parent_id: ids[ids.len() - 2].parse().unwrap(), + channel_id: ids[ids.len() - 1].parse().unwrap(), + }) + } + + Ok(results) + } + /// Returns the channel descendants, /// Structured as a map from child ids to their parent ids /// For example, the descendants of 'a' in this DAG: From 9c6f5de551ca9cf81ef08428d2ee5b24fe8e05a3 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 13 Oct 2023 13:17:19 -0600 Subject: [PATCH 141/180] Use new get_channel_descendants for delete --- crates/collab/src/db/queries/channels.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 625655f27735f707d4f05e8b28041406b3f89be9..0b97569ec44086093e1e168c544c54af25c458ac 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -125,17 +125,19 @@ impl Database { .await?; // Don't remove descendant channels that have additional parents. - let mut channels_to_remove = self.get_channel_descendants([channel_id], &*tx).await?; + let mut channels_to_remove: HashSet = HashSet::default(); + channels_to_remove.insert(channel_id); + + let graph = self.get_channel_descendants_2([channel_id], &*tx).await?; + for edge in graph.iter() { + channels_to_remove.insert(ChannelId::from_proto(edge.channel_id)); + } + { let mut channels_to_keep = channel_path::Entity::find() .filter( channel_path::Column::ChannelId - .is_in( - channels_to_remove - .keys() - .copied() - .filter(|&id| id != channel_id), - ) + .is_in(channels_to_remove.clone()) .and( channel_path::Column::IdPath .not_like(&format!("%/{}/%", channel_id)), @@ -160,7 +162,7 @@ impl Database { .await?; channel::Entity::delete_many() - .filter(channel::Column::Id.is_in(channels_to_remove.keys().copied())) + .filter(channel::Column::Id.is_in(channels_to_remove.clone())) .exec(&*tx) .await?; @@ -177,7 +179,7 @@ impl Database { ); tx.execute(channel_paths_stmt).await?; - Ok((channels_to_remove.into_keys().collect(), members_to_notify)) + Ok((channels_to_remove.into_iter().collect(), members_to_notify)) }) .await } From e050d168a726742dfe4e836c1bcbd758d4916ea0 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 13 Oct 2023 13:39:46 -0600 Subject: [PATCH 142/180] Delete some old code, reame ChannelMembers -> Members --- crates/collab/src/db/ids.rs | 8 +- crates/collab/src/db/queries/channels.rs | 183 +++-------------------- 2 files changed, 21 insertions(+), 170 deletions(-) diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index ee8c879ed3da3fc3ec68a1cece455095380f7768..b935c658dd1546ad94508360e4a494482aaef041 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -141,16 +141,16 @@ impl Into for ChannelRole { pub enum ChannelVisibility { #[sea_orm(string_value = "public")] Public, - #[sea_orm(string_value = "channel_members")] + #[sea_orm(string_value = "members")] #[default] - ChannelMembers, + Members, } impl From for ChannelVisibility { fn from(value: proto::ChannelVisibility) -> Self { match value { proto::ChannelVisibility::Public => ChannelVisibility::Public, - proto::ChannelVisibility::ChannelMembers => ChannelVisibility::ChannelMembers, + proto::ChannelVisibility::ChannelMembers => ChannelVisibility::Members, } } } @@ -159,7 +159,7 @@ impl Into for ChannelVisibility { fn into(self) -> proto::ChannelVisibility { match self { ChannelVisibility::Public => proto::ChannelVisibility::Public, - ChannelVisibility::ChannelMembers => proto::ChannelVisibility::ChannelMembers, + ChannelVisibility::Members => proto::ChannelVisibility::ChannelMembers, } } } diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 0b97569ec44086093e1e168c544c54af25c458ac..74d5b797b83bf851937b8fbdf82654409f8cb165 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -2,9 +2,6 @@ use std::cmp::Ordering; use super::*; use rpc::proto::{channel_member::Kind, ChannelEdge}; -use smallvec::SmallVec; - -type ChannelDescendants = HashMap>; impl Database { #[cfg(test)] @@ -41,7 +38,7 @@ impl Database { let channel = channel::ActiveModel { id: ActiveValue::NotSet, name: ActiveValue::Set(name.to_string()), - visibility: ActiveValue::Set(ChannelVisibility::ChannelMembers), + visibility: ActiveValue::Set(ChannelVisibility::Members), } .insert(&*tx) .await?; @@ -349,49 +346,6 @@ impl Database { .await } - async fn get_channel_graph( - &self, - parents_by_child_id: ChannelDescendants, - trim_dangling_parents: bool, - tx: &DatabaseTransaction, - ) -> Result { - let mut channels = Vec::with_capacity(parents_by_child_id.len()); - { - let mut rows = channel::Entity::find() - .filter(channel::Column::Id.is_in(parents_by_child_id.keys().copied())) - .stream(&*tx) - .await?; - while let Some(row) = rows.next().await { - let row = row?; - channels.push(Channel { - id: row.id, - name: row.name, - }) - } - } - - let mut edges = Vec::with_capacity(parents_by_child_id.len()); - for (channel, parents) in parents_by_child_id.iter() { - for parent in parents.into_iter() { - if trim_dangling_parents { - if parents_by_child_id.contains_key(parent) { - edges.push(ChannelEdge { - channel_id: channel.to_proto(), - parent_id: parent.to_proto(), - }); - } - } else { - edges.push(ChannelEdge { - channel_id: channel.to_proto(), - parent_id: parent.to_proto(), - }); - } - } - } - - Ok(ChannelGraph { channels, edges }) - } - pub async fn get_channels_for_user(&self, user_id: UserId) -> Result { self.transaction(|tx| async move { let tx = tx; @@ -637,7 +591,7 @@ impl Database { .one(&*tx) .await? .map(|channel| channel.visibility) - .unwrap_or(ChannelVisibility::ChannelMembers); + .unwrap_or(ChannelVisibility::Members); #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] enum QueryMemberDetails { @@ -1011,79 +965,6 @@ impl Database { Ok(results) } - /// Returns the channel descendants, - /// Structured as a map from child ids to their parent ids - /// For example, the descendants of 'a' in this DAG: - /// - /// /- b -\ - /// a -- c -- d - /// - /// would be: - /// { - /// a: [], - /// b: [a], - /// c: [a], - /// d: [a, c], - /// } - async fn get_channel_descendants( - &self, - channel_ids: impl IntoIterator, - tx: &DatabaseTransaction, - ) -> Result { - let mut values = String::new(); - for id in channel_ids { - if !values.is_empty() { - values.push_str(", "); - } - write!(&mut values, "({})", id).unwrap(); - } - - if values.is_empty() { - return Ok(HashMap::default()); - } - - let sql = format!( - r#" - SELECT - descendant_paths.* - FROM - channel_paths parent_paths, channel_paths descendant_paths - WHERE - parent_paths.channel_id IN ({values}) AND - descendant_paths.id_path LIKE (parent_paths.id_path || '%') - "# - ); - - let stmt = Statement::from_string(self.pool.get_database_backend(), sql); - - let mut parents_by_child_id: ChannelDescendants = HashMap::default(); - let mut paths = channel_path::Entity::find() - .from_raw_sql(stmt) - .stream(tx) - .await?; - - while let Some(path) = paths.next().await { - let path = path?; - let ids = path.id_path.trim_matches('/').split('/'); - let mut parent_id = None; - for id in ids { - if let Ok(id) = id.parse() { - let id = ChannelId::from_proto(id); - if id == path.channel_id { - break; - } - parent_id = Some(id); - } - } - let entry = parents_by_child_id.entry(path.channel_id).or_default(); - if let Some(parent_id) = parent_id { - entry.insert(parent_id); - } - } - - Ok(parents_by_child_id) - } - /// Returns the channel with the given ID and: /// - true if the user is a member /// - false if the user hasn't accepted the invitation yet @@ -1242,18 +1123,23 @@ impl Database { .await?; } - let mut channel_descendants = self.get_channel_descendants([channel], &*tx).await?; - if let Some(channel) = channel_descendants.get_mut(&channel) { - // Remove the other parents - channel.clear(); - channel.insert(new_parent); - } - - let channels = self - .get_channel_graph(channel_descendants, false, &*tx) + let membership = channel_member::Entity::find() + .filter( + channel_member::Column::ChannelId + .eq(channel) + .and(channel_member::Column::UserId.eq(user)), + ) + .all(tx) .await?; - Ok(channels) + let mut channel_info = self.get_user_channels(user, membership, &*tx).await?; + + channel_info.channels.edges.push(ChannelEdge { + channel_id: channel.to_proto(), + parent_id: new_parent.to_proto(), + }); + + Ok(channel_info.channels) } /// Unlink a channel from a given parent. This will add in a root edge if @@ -1405,38 +1291,3 @@ impl PartialEq for ChannelGraph { self.channels == other.channels && self.edges == other.edges } } - -struct SmallSet(SmallVec<[T; 1]>); - -impl Deref for SmallSet { - type Target = [T]; - - fn deref(&self) -> &Self::Target { - self.0.deref() - } -} - -impl Default for SmallSet { - fn default() -> Self { - Self(SmallVec::new()) - } -} - -impl SmallSet { - fn insert(&mut self, value: T) -> bool - where - T: Ord, - { - match self.binary_search(&value) { - Ok(_) => false, - Err(ix) => { - self.0.insert(ix, value); - true - } - } - } - - fn clear(&mut self) { - self.0.clear(); - } -} From bb408936e9aed78c73bf9345746439327e876b24 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 13 Oct 2023 14:08:40 -0600 Subject: [PATCH 143/180] Ignore old admin column --- crates/collab/src/db/ids.rs | 3 +- crates/collab/src/db/queries/channels.rs | 62 ++++--------------- crates/collab/src/db/tables/channel_member.rs | 4 +- 3 files changed, 14 insertions(+), 55 deletions(-) diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index b935c658dd1546ad94508360e4a494482aaef041..6dd1f2f596087d1db6b2180c11cd03acc1576ac1 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -82,12 +82,13 @@ id_type!(UserId); id_type!(ChannelBufferCollaboratorId); id_type!(FlagId); -#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum)] +#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum ChannelRole { #[sea_orm(string_value = "admin")] Admin, #[sea_orm(string_value = "member")] + #[default] Member, #[sea_orm(string_value = "guest")] Guest, diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 74d5b797b83bf851937b8fbdf82654409f8cb165..e7db0d4cfc32197034c7c03ccd464d8a239b2fa0 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -78,8 +78,7 @@ impl Database { channel_id: ActiveValue::Set(channel.id), user_id: ActiveValue::Set(creator_id), accepted: ActiveValue::Set(true), - admin: ActiveValue::Set(true), - role: ActiveValue::Set(Some(ChannelRole::Admin)), + role: ActiveValue::Set(ChannelRole::Admin), } .insert(&*tx) .await?; @@ -197,8 +196,7 @@ impl Database { channel_id: ActiveValue::Set(channel_id), user_id: ActiveValue::Set(invitee_id), accepted: ActiveValue::Set(false), - admin: ActiveValue::Set(role == ChannelRole::Admin), - role: ActiveValue::Set(Some(role)), + role: ActiveValue::Set(role), } .insert(&*tx) .await?; @@ -402,14 +400,7 @@ impl Database { let mut role_for_channel: HashMap = HashMap::default(); for membership in channel_memberships.iter() { - role_for_channel.insert( - membership.channel_id, - membership.role.unwrap_or(if membership.admin { - ChannelRole::Admin - } else { - ChannelRole::Member - }), - ); + role_for_channel.insert(membership.channel_id, membership.role); } for ChannelEdge { @@ -561,8 +552,7 @@ impl Database { .and(channel_member::Column::UserId.eq(for_user)), ) .set(channel_member::ActiveModel { - admin: ActiveValue::set(role == ChannelRole::Admin), - role: ActiveValue::set(Some(role)), + role: ActiveValue::set(role), ..Default::default() }) .exec(&*tx) @@ -596,7 +586,6 @@ impl Database { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] enum QueryMemberDetails { UserId, - Admin, Role, IsDirectMember, Accepted, @@ -610,7 +599,6 @@ impl Database { .filter(channel_member::Column::ChannelId.is_in(ancestor_ids.iter().copied())) .select_only() .column(channel_member::Column::UserId) - .column(channel_member::Column::Admin) .column(channel_member::Column::Role) .column_as( channel_member::Column::ChannelId.eq(channel_id), @@ -629,17 +617,9 @@ impl Database { let mut user_details: HashMap = HashMap::default(); while let Some(row) = stream.next().await { - let ( - user_id, - is_admin, - channel_role, - is_direct_member, - is_invite_accepted, - visibility, - ): ( + let (user_id, channel_role, is_direct_member, is_invite_accepted, visibility): ( UserId, - bool, - Option, + ChannelRole, bool, bool, ChannelVisibility, @@ -650,11 +630,6 @@ impl Database { (false, true) => proto::channel_member::Kind::AncestorMember, (false, false) => continue, }; - let channel_role = channel_role.unwrap_or(if is_admin { - ChannelRole::Admin - } else { - ChannelRole::Member - }); if channel_role == ChannelRole::Guest && visibility != ChannelVisibility::Public @@ -797,7 +772,6 @@ impl Database { enum QueryChannelMembership { ChannelId, Role, - Admin, Visibility, } @@ -811,7 +785,6 @@ impl Database { .select_only() .column(channel_member::Column::ChannelId) .column(channel_member::Column::Role) - .column(channel_member::Column::Admin) .column(channel::Column::Visibility) .into_values::<_, QueryChannelMembership>() .stream(&*tx) @@ -826,29 +799,16 @@ impl Database { // note these channels are not iterated in any particular order, // our current logic takes the highest permission available. while let Some(row) = rows.next().await { - let (ch_id, role, admin, visibility): ( - ChannelId, - Option, - bool, - ChannelVisibility, - ) = row?; + let (ch_id, role, visibility): (ChannelId, ChannelRole, ChannelVisibility) = row?; match role { - Some(ChannelRole::Admin) => is_admin = true, - Some(ChannelRole::Member) => is_member = true, - Some(ChannelRole::Guest) => { + ChannelRole::Admin => is_admin = true, + ChannelRole::Member => is_member = true, + ChannelRole::Guest => { if visibility == ChannelVisibility::Public { is_participant = true } } - Some(ChannelRole::Banned) => is_banned = true, - None => { - // rows created from pre-role collab server. - if admin { - is_admin = true - } else { - is_member = true - } - } + ChannelRole::Banned => is_banned = true, } if channel_id == ch_id { current_channel_visibility = Some(visibility); diff --git a/crates/collab/src/db/tables/channel_member.rs b/crates/collab/src/db/tables/channel_member.rs index e8162bfcbd133fe647dbbfa13cab1136dcbe441e..5498a008565e4db681ac588f49cf4a3332121b9a 100644 --- a/crates/collab/src/db/tables/channel_member.rs +++ b/crates/collab/src/db/tables/channel_member.rs @@ -9,9 +9,7 @@ pub struct Model { pub channel_id: ChannelId, pub user_id: UserId, pub accepted: bool, - pub admin: bool, - // only optional while migrating - pub role: Option, + pub role: ChannelRole, } impl ActiveModelBehavior for ActiveModel {} From e20bc87152291ee37f967a72103cb2bb39bcf9a5 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 13 Oct 2023 14:30:20 -0600 Subject: [PATCH 144/180] Add some sanity checks for new user channel graph --- crates/collab/src/db/tests/channel_tests.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 2044310d8e025119de85dbb49cde3149ced93c6c..b96971123238d5c17c88e75fc90019eeabf70450 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -895,6 +895,18 @@ async fn test_user_is_channel_participant(db: &Arc) { ] ); + db.respond_to_channel_invite(vim_channel, guest, true) + .await + .unwrap(); + + let channels = db.get_channels_for_user(guest).await.unwrap().channels; + assert_dag(channels, &[(vim_channel, None)]); + let channels = db.get_channels_for_user(member).await.unwrap().channels; + assert_dag( + channels, + &[(active_channel, None), (vim_channel, Some(active_channel))], + ); + db.set_channel_member_role(vim_channel, admin, guest, ChannelRole::Banned) .await .unwrap(); @@ -926,7 +938,7 @@ async fn test_user_is_channel_participant(db: &Arc) { }, proto::ChannelMember { user_id: guest.to_proto(), - kind: proto::channel_member::Kind::Invitee.into(), + kind: proto::channel_member::Kind::Member.into(), role: proto::ChannelRole::Banned.into(), }, ] @@ -1015,6 +1027,12 @@ async fn test_user_is_channel_participant(db: &Arc) { }, ] ); + + let channels = db.get_channels_for_user(guest).await.unwrap().channels; + assert_dag( + channels, + &[(zed_channel, None), (vim_channel, Some(zed_channel))], + ) } #[track_caller] From af11cc6cfdd4d16e29e62864f749710ffecf4006 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 13 Oct 2023 15:07:49 -0600 Subject: [PATCH 145/180] show warnings by default --- Procfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Procfile b/Procfile index 2eb7de20fb7e9cae34375dc130c6d27aea01012e..3f42c3a9677477bd7dcd04ca61a749206559be40 100644 --- a/Procfile +++ b/Procfile @@ -1,4 +1,4 @@ web: cd ../zed.dev && PORT=3000 npm run dev -collab: cd crates/collab && RUST_LOG=${RUST_LOG:-collab=info} cargo run serve +collab: cd crates/collab && RUST_LOG=${RUST_LOG:-warn,collab=info} cargo run serve livekit: livekit-server --dev postgrest: postgrest crates/collab/admin_api.conf From f8fd77b83e80743d12a38a47e960fd98e0bfddf4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 13 Oct 2023 15:08:09 -0600 Subject: [PATCH 146/180] fix migration --- crates/collab/migrations/20231011214412_add_guest_role.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/migrations/20231011214412_add_guest_role.sql b/crates/collab/migrations/20231011214412_add_guest_role.sql index bd178ec63d4723a2f16bec51bdc71ab22fa13a3a..17135471583a03bd6b39e82b3644b683cfc96d57 100644 --- a/crates/collab/migrations/20231011214412_add_guest_role.sql +++ b/crates/collab/migrations/20231011214412_add_guest_role.sql @@ -1,4 +1,4 @@ ALTER TABLE channel_members ADD COLUMN role TEXT; UPDATE channel_members SET role = CASE WHEN admin THEN 'admin' ELSE 'member' END; -ALTER TABLE channels ADD COLUMN visibility TEXT NOT NULL DEFAULT 'channel_members'; +ALTER TABLE channels ADD COLUMN visibility TEXT NOT NULL DEFAULT 'members'; From f6f9b5c8cbe153c63e0bfb6431f2ea62318011d3 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 13 Oct 2023 16:59:30 -0600 Subject: [PATCH 147/180] Wire through public access toggle --- crates/channel/src/channel_store.rs | 23 ++++- .../src/channel_store/channel_index.rs | 2 + crates/collab/src/db.rs | 1 + crates/collab/src/db/ids.rs | 6 +- crates/collab/src/db/queries/channels.rs | 19 +++-- crates/collab/src/db/tests.rs | 1 + crates/collab/src/rpc.rs | 69 ++++++++++----- .../collab/src/tests/channel_buffer_tests.rs | 6 +- .../src/collab_panel/channel_modal.rs | 83 ++++++++++++++++++- crates/rpc/proto/zed.proto | 10 ++- crates/rpc/src/proto.rs | 2 + crates/theme/src/theme.rs | 2 + styles/src/style_tree/collab_modals.ts | 23 ++++- 13 files changed, 209 insertions(+), 38 deletions(-) diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 64c76a0a390c3dc5ae6ba1ef402b16d8b58efdb8..3e8fbafb6ad0fce58d8232fedf0ac95b8cf1d625 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -9,7 +9,7 @@ use db::RELEASE_CHANNEL; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle}; use rpc::{ - proto::{self, ChannelEdge, ChannelPermission, ChannelRole}, + proto::{self, ChannelEdge, ChannelPermission, ChannelRole, ChannelVisibility}, TypedEnvelope, }; use serde_derive::{Deserialize, Serialize}; @@ -49,6 +49,7 @@ pub type ChannelData = (Channel, ChannelPath); pub struct Channel { pub id: ChannelId, pub name: String, + pub visibility: proto::ChannelVisibility, pub unseen_note_version: Option<(u64, clock::Global)>, pub unseen_message_id: Option, } @@ -508,6 +509,25 @@ impl ChannelStore { }) } + pub fn set_channel_visibility( + &mut self, + channel_id: ChannelId, + visibility: ChannelVisibility, + cx: &mut ModelContext, + ) -> Task> { + let client = self.client.clone(); + cx.spawn(|_, _| async move { + let _ = client + .request(proto::SetChannelVisibility { + channel_id, + visibility: visibility.into(), + }) + .await?; + + Ok(()) + }) + } + pub fn invite_member( &mut self, channel_id: ChannelId, @@ -869,6 +889,7 @@ impl ChannelStore { ix, Arc::new(Channel { id: channel.id, + visibility: channel.visibility(), name: channel.name, unseen_note_version: None, unseen_message_id: None, diff --git a/crates/channel/src/channel_store/channel_index.rs b/crates/channel/src/channel_store/channel_index.rs index bf0de1b644cee0df8df3948e504e406a8f9cd3e7..7b54d5dcd904f8fa181a9ebcc20a0ff1e2c1e4d5 100644 --- a/crates/channel/src/channel_store/channel_index.rs +++ b/crates/channel/src/channel_store/channel_index.rs @@ -123,12 +123,14 @@ impl<'a> ChannelPathsInsertGuard<'a> { pub fn insert(&mut self, channel_proto: proto::Channel) { if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) { + Arc::make_mut(existing_channel).visibility = channel_proto.visibility(); Arc::make_mut(existing_channel).name = channel_proto.name; } else { self.channels_by_id.insert( channel_proto.id, Arc::new(Channel { id: channel_proto.id, + visibility: channel_proto.visibility(), name: channel_proto.name, unseen_note_version: None, unseen_message_id: None, diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index e60b7cc33dfb22f030c5600cfa0d8d0794438c1d..08f78c685dc8a28392733816a8e0473c2d2ca63a 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -432,6 +432,7 @@ pub struct NewUserResult { pub struct Channel { pub id: ChannelId, pub name: String, + pub visibility: ChannelVisibility, } #[derive(Debug, PartialEq)] diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 6dd1f2f596087d1db6b2180c11cd03acc1576ac1..970d66d4cbcf45b2dd559222fb46e8be93c723b9 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -137,7 +137,7 @@ impl Into for ChannelRole { } } -#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default)] +#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum ChannelVisibility { #[sea_orm(string_value = "public")] @@ -151,7 +151,7 @@ impl From for ChannelVisibility { fn from(value: proto::ChannelVisibility) -> Self { match value { proto::ChannelVisibility::Public => ChannelVisibility::Public, - proto::ChannelVisibility::ChannelMembers => ChannelVisibility::Members, + proto::ChannelVisibility::Members => ChannelVisibility::Members, } } } @@ -160,7 +160,7 @@ impl Into for ChannelVisibility { fn into(self) -> proto::ChannelVisibility { match self { ChannelVisibility::Public => proto::ChannelVisibility::Public, - ChannelVisibility::Members => proto::ChannelVisibility::ChannelMembers, + ChannelVisibility::Members => proto::ChannelVisibility::Members, } } } diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index e7db0d4cfc32197034c7c03ccd464d8a239b2fa0..0b7e9eb2d828ed78da7f2baaa20636f3bce5e74d 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -93,12 +93,12 @@ impl Database { channel_id: ChannelId, visibility: ChannelVisibility, user_id: UserId, - ) -> Result<()> { + ) -> Result { self.transaction(move |tx| async move { self.check_user_is_channel_admin(channel_id, user_id, &*tx) .await?; - channel::ActiveModel { + let channel = channel::ActiveModel { id: ActiveValue::Unchanged(channel_id), visibility: ActiveValue::Set(visibility), ..Default::default() @@ -106,7 +106,7 @@ impl Database { .update(&*tx) .await?; - Ok(()) + Ok(channel) }) .await } @@ -219,14 +219,14 @@ impl Database { channel_id: ChannelId, user_id: UserId, new_name: &str, - ) -> Result { + ) -> Result { self.transaction(move |tx| async move { let new_name = Self::sanitize_channel_name(new_name)?.to_string(); self.check_user_is_channel_admin(channel_id, user_id, &*tx) .await?; - channel::ActiveModel { + let channel = channel::ActiveModel { id: ActiveValue::Unchanged(channel_id), name: ActiveValue::Set(new_name.clone()), ..Default::default() @@ -234,7 +234,11 @@ impl Database { .update(&*tx) .await?; - Ok(new_name) + Ok(Channel { + id: channel.id, + name: channel.name, + visibility: channel.visibility, + }) }) .await } @@ -336,6 +340,7 @@ impl Database { .map(|channel| Channel { id: channel.id, name: channel.name, + visibility: channel.visibility, }) .collect(); @@ -443,6 +448,7 @@ impl Database { channels.push(Channel { id: channel.id, name: channel.name, + visibility: channel.visibility, }); if role == ChannelRole::Admin { @@ -963,6 +969,7 @@ impl Database { Ok(Some(( Channel { id: channel.id, + visibility: channel.visibility, name: channel.name, }, is_accepted, diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index 6a91fd6ffe145c1a31f9b5264029a04ddb1ef1de..99a605106eafab55f50b2b37a0a5a027b1cb9457 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -159,6 +159,7 @@ fn graph(channels: &[(ChannelId, &'static str)], edges: &[(ChannelId, ChannelId) graph.channels.push(Channel { id: *id, name: name.to_string(), + visibility: ChannelVisibility::Members, }) } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index f8ac77325c951633df3227eab72c92f08081893b..c3d8a25ab7fb709750f1043b966dc0e1281c63e9 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3,8 +3,8 @@ mod connection_pool; use crate::{ auth, db::{ - self, BufferId, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId, - ServerId, User, UserId, + self, BufferId, ChannelId, ChannelVisibility, ChannelsForUser, Database, MessageId, + ProjectId, RoomId, ServerId, User, UserId, }, executor::Executor, AppState, Result, @@ -38,8 +38,8 @@ use lazy_static::lazy_static; use prometheus::{register_int_gauge, IntGauge}; use rpc::{ proto::{ - self, Ack, AnyTypedEnvelope, ChannelEdge, ChannelVisibility, EntityMessage, - EnvelopedMessage, LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators, + self, Ack, AnyTypedEnvelope, ChannelEdge, EntityMessage, EnvelopedMessage, + LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators, }, Connection, ConnectionId, Peer, Receipt, TypedEnvelope, }; @@ -255,6 +255,7 @@ impl Server { .add_request_handler(invite_channel_member) .add_request_handler(remove_channel_member) .add_request_handler(set_channel_member_role) + .add_request_handler(set_channel_visibility) .add_request_handler(rename_channel) .add_request_handler(join_channel_buffer) .add_request_handler(leave_channel_buffer) @@ -2210,8 +2211,7 @@ async fn create_channel( let channel = proto::Channel { id: id.to_proto(), name: request.name, - // TODO: Visibility - visibility: proto::ChannelVisibility::ChannelMembers as i32, + visibility: proto::ChannelVisibility::Members as i32, }; response.send(proto::CreateChannelResponse { @@ -2300,9 +2300,8 @@ async fn invite_channel_member( let mut update = proto::UpdateChannels::default(); update.channel_invitations.push(proto::Channel { id: channel.id.to_proto(), + visibility: channel.visibility.into(), name: channel.name, - // TODO: Visibility - visibility: proto::ChannelVisibility::ChannelMembers as i32, }); for connection_id in session .connection_pool() @@ -2343,6 +2342,39 @@ async fn remove_channel_member( Ok(()) } +async fn set_channel_visibility( + request: proto::SetChannelVisibility, + response: Response, + session: Session, +) -> Result<()> { + let db = session.db().await; + let channel_id = ChannelId::from_proto(request.channel_id); + let visibility = request.visibility().into(); + + let channel = db + .set_channel_visibility(channel_id, visibility, session.user_id) + .await?; + + let mut update = proto::UpdateChannels::default(); + update.channels.push(proto::Channel { + id: channel.id.to_proto(), + name: channel.name, + visibility: channel.visibility.into(), + }); + + let member_ids = db.get_channel_members(channel_id).await?; + + let connection_pool = session.connection_pool().await; + for member_id in member_ids { + for connection_id in connection_pool.user_connection_ids(member_id) { + session.peer.send(connection_id, update.clone())?; + } + } + + response.send(proto::Ack {})?; + Ok(()) +} + async fn set_channel_member_role( request: proto::SetChannelMemberRole, response: Response, @@ -2391,15 +2423,14 @@ async fn rename_channel( ) -> Result<()> { let db = session.db().await; let channel_id = ChannelId::from_proto(request.channel_id); - let new_name = db + let channel = db .rename_channel(channel_id, session.user_id, &request.name) .await?; let channel = proto::Channel { - id: request.channel_id, - name: new_name, - // TODO: Visibility - visibility: proto::ChannelVisibility::ChannelMembers as i32, + id: channel.id.to_proto(), + name: channel.name, + visibility: channel.visibility.into(), }; response.send(proto::RenameChannelResponse { channel: Some(channel.clone()), @@ -2437,9 +2468,8 @@ async fn link_channel( .into_iter() .map(|channel| proto::Channel { id: channel.id.to_proto(), + visibility: channel.visibility.into(), name: channel.name, - // TODO: Visibility - visibility: proto::ChannelVisibility::ChannelMembers as i32, }) .collect(), insert_edge: channels_to_send.edges, @@ -2530,9 +2560,8 @@ async fn move_channel( .into_iter() .map(|channel| proto::Channel { id: channel.id.to_proto(), + visibility: channel.visibility.into(), name: channel.name, - // TODO: Visibility - visibility: proto::ChannelVisibility::ChannelMembers as i32, }) .collect(), insert_edge: channels_to_send.edges, @@ -2588,9 +2617,8 @@ async fn respond_to_channel_invite( .into_iter() .map(|channel| proto::Channel { id: channel.id.to_proto(), + visibility: channel.visibility.into(), name: channel.name, - // TODO: Visibility - visibility: ChannelVisibility::ChannelMembers.into(), }), ); update.unseen_channel_messages = result.channel_messages; @@ -3094,8 +3122,7 @@ fn build_initial_channels_update( update.channels.push(proto::Channel { id: channel.id.to_proto(), name: channel.name, - // TODO: Visibility - visibility: ChannelVisibility::Public.into(), + visibility: channel.visibility.into(), }); } diff --git a/crates/collab/src/tests/channel_buffer_tests.rs b/crates/collab/src/tests/channel_buffer_tests.rs index a0b9b524841f19e4eb6537348317ae38d23627c2..14ae159ab80fd5b46359ef1183e7ea18478f3f08 100644 --- a/crates/collab/src/tests/channel_buffer_tests.rs +++ b/crates/collab/src/tests/channel_buffer_tests.rs @@ -11,7 +11,10 @@ use collections::HashMap; use editor::{Anchor, Editor, ToOffset}; use futures::future; use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext}; -use rpc::{proto::PeerId, RECEIVE_TIMEOUT}; +use rpc::{ + proto::{self, PeerId}, + RECEIVE_TIMEOUT, +}; use serde_json::json; use std::{ops::Range, sync::Arc}; @@ -445,6 +448,7 @@ fn channel(id: u64, name: &'static str) -> Channel { Channel { id, name: name.to_string(), + visibility: proto::ChannelVisibility::Members, unseen_note_version: None, unseen_message_id: None, } diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 16d5e48f452b7acd4b220076ccb73ac7c8308854..bf04e4f7e6b257d40d52c4da9a2bcc97237684b3 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -1,6 +1,6 @@ -use channel::{ChannelId, ChannelMembership, ChannelStore}; +use channel::{Channel, ChannelId, ChannelMembership, ChannelStore}; use client::{ - proto::{self, ChannelRole}, + proto::{self, ChannelRole, ChannelVisibility}, User, UserId, UserStore, }; use context_menu::{ContextMenu, ContextMenuItem}; @@ -9,7 +9,8 @@ use gpui::{ actions, elements::*, platform::{CursorStyle, MouseButton}, - AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle, + AppContext, ClipboardItem, Entity, ModelHandle, MouseState, Task, View, ViewContext, + ViewHandle, }; use picker::{Picker, PickerDelegate, PickerEvent}; use std::sync::Arc; @@ -185,6 +186,81 @@ impl View for ChannelModal { .into_any() } + fn render_visibility( + channel_id: ChannelId, + visibility: ChannelVisibility, + theme: &theme::TabbedModal, + cx: &mut ViewContext, + ) -> AnyElement { + enum TogglePublic {} + + if visibility == ChannelVisibility::Members { + return Flex::row() + .with_child( + MouseEventHandler::new::(0, cx, move |state, _| { + let style = theme.visibility_toggle.style_for(state); + Label::new(format!("{}", "Public access: OFF"), style.text.clone()) + .contained() + .with_style(style.container.clone()) + }) + .on_click(MouseButton::Left, move |_, this, cx| { + this.channel_store + .update(cx, |channel_store, cx| { + channel_store.set_channel_visibility( + channel_id, + ChannelVisibility::Public, + cx, + ) + }) + .detach_and_log_err(cx); + }) + .with_cursor_style(CursorStyle::PointingHand), + ) + .into_any(); + } + + Flex::row() + .with_child( + MouseEventHandler::new::(0, cx, move |state, _| { + let style = theme.visibility_toggle.style_for(state); + Label::new(format!("{}", "Public access: ON"), style.text.clone()) + .contained() + .with_style(style.container.clone()) + }) + .on_click(MouseButton::Left, move |_, this, cx| { + this.channel_store + .update(cx, |channel_store, cx| { + channel_store.set_channel_visibility( + channel_id, + ChannelVisibility::Members, + cx, + ) + }) + .detach_and_log_err(cx); + }) + .with_cursor_style(CursorStyle::PointingHand), + ) + .with_spacing(14.0) + .with_child( + MouseEventHandler::new::(1, cx, move |state, _| { + let style = theme.channel_link.style_for(state); + Label::new(format!("{}", "copy link"), style.text.clone()) + .contained() + .with_style(style.container.clone()) + }) + .on_click(MouseButton::Left, move |_, this, cx| { + if let Some(channel) = + this.channel_store.read(cx).channel_for_id(channel_id) + { + let item = ClipboardItem::new(channel.link()); + cx.write_to_clipboard(item); + } + }) + .with_cursor_style(CursorStyle::PointingHand), + ) + .into_any() + } + Flex::column() .with_child( Flex::column() @@ -193,6 +269,7 @@ impl View for ChannelModal { .contained() .with_style(theme.title.container.clone()), ) + .with_child(render_visibility(channel.id, channel.visibility, theme, cx)) .with_child(Flex::row().with_children([ render_mode_button::( Mode::InviteMembers, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 90e425a39ffef4ab04b05f65e94951296768124e..f6d0dfa5d9ff2b28e6c9d6fa8fd8b31645275b7d 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -170,7 +170,8 @@ message Envelope { LinkChannel link_channel = 140; UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; // current max: 145 + MoveChannel move_channel = 142; + SetChannelVisibility set_channel_visibility = 146; // current max: 146 } } @@ -1049,6 +1050,11 @@ message SetChannelMemberRole { ChannelRole role = 3; } +message SetChannelVisibility { + uint64 channel_id = 1; + ChannelVisibility visibility = 2; +} + message RenameChannel { uint64 channel_id = 1; string name = 2; @@ -1542,7 +1548,7 @@ message Nonce { enum ChannelVisibility { Public = 0; - ChannelMembers = 1; + Members = 1; } message Channel { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 57292a52ca21db846ca426945b40b2dd08738370..c60e99602e9a4c42b8f6bf857c7f9a43cd069883 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -231,6 +231,7 @@ messages!( (RenameChannel, Foreground), (RenameChannelResponse, Foreground), (SetChannelMemberRole, Foreground), + (SetChannelVisibility, Foreground), (SearchProject, Background), (SearchProjectResponse, Background), (ShareProject, Foreground), @@ -327,6 +328,7 @@ request_messages!( (RespondToContactRequest, Ack), (RespondToChannelInvite, Ack), (SetChannelMemberRole, Ack), + (SetChannelVisibility, Ack), (SendChannelMessage, SendChannelMessageResponse), (GetChannelMessages, GetChannelMessagesResponse), (GetChannelMembers, GetChannelMembersResponse), diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index e534ba4260e11b89acf2eb625701bfea921ff411..fa3db613288f162372ba200a073a2007bb01a6ce 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -286,6 +286,8 @@ pub struct TabbedModal { pub header: ContainerStyle, pub body: ContainerStyle, pub title: ContainedText, + pub visibility_toggle: Interactive, + pub channel_link: Interactive, pub picker: Picker, pub max_height: f32, pub max_width: f32, diff --git a/styles/src/style_tree/collab_modals.ts b/styles/src/style_tree/collab_modals.ts index f9b22b686799d450a0d0abebd0df6243982dc96f..586e7be3f0bb3c68a14e198ee93f1c89b35cf6fb 100644 --- a/styles/src/style_tree/collab_modals.ts +++ b/styles/src/style_tree/collab_modals.ts @@ -1,10 +1,11 @@ -import { useTheme } from "../theme" +import { StyleSet, StyleSets, Styles, useTheme } from "../theme" import { background, border, foreground, text } from "./components" import picker from "./picker" import { input } from "../component/input" import contact_finder from "./contact_finder" import { tab } from "../component/tab" import { icon_button } from "../component/icon_button" +import { interactive } from "../element/interactive" export default function channel_modal(): any { const theme = useTheme() @@ -27,6 +28,24 @@ export default function channel_modal(): any { const picker_input = input() + const interactive_text = (styleset: StyleSets) => + interactive({ + base: { + padding: { + left: 8, + top: 8 + }, + ...text(theme.middle, "sans", styleset, "default"), + }, state: { + hovered: { + ...text(theme.middle, "sans", styleset, "hovered"), + }, + clicked: { + ...text(theme.middle, "sans", styleset, "active"), + } + } + }); + const member_icon_style = icon_button({ variant: "ghost", size: "sm", @@ -88,6 +107,8 @@ export default function channel_modal(): any { left: BUTTON_OFFSET, }, }, + visibility_toggle: interactive_text("base"), + channel_link: interactive_text("accent"), picker: { empty_container: {}, item: { From 6f4008ebabd33793cd70a8f042c2c9510680a89b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Sun, 15 Oct 2023 17:27:36 +0200 Subject: [PATCH 148/180] copilot: Propagate action if suggest_next is not possible. (#3129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One of our users ran into an issue where typing "true quote" characters (option-[ for „ and option-] for ‚) was not possible; I've narrowed it down to a collision with Copilot's NextSuggestion and PreviousSuggestion action default keybinds. I explicitly did not want to alter the key bindings, so I've went with a more neutral fix - one that propagates the keystroke if there's no Copilot action to be taken (user is not using Copilot etc). Note however that typing true quotes while using a Copilot is still not possible, as for that we'd have to change a keybind. Fixes zed-industries/community#2072 Release Notes: - Fixed Copilot's "Suggest next" and "Suggest previous" actions colliding with true quotes key bindings (`option-[` and `option-]`). The keystrokes are now propagated if there's no Copilot action to be taken at cursor's position. --- crates/editor/src/editor.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bb6d693d82548f61573c39ceb6d287d3fb1d8684..7aca4ab98fff05a73dcacdb55bf97913c164146f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4158,7 +4158,10 @@ impl Editor { if self.has_active_copilot_suggestion(cx) { self.cycle_copilot_suggestions(Direction::Next, cx); } else { - self.refresh_copilot_suggestions(false, cx); + let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none(); + if is_copilot_disabled { + cx.propagate_action(); + } } } @@ -4170,7 +4173,10 @@ impl Editor { if self.has_active_copilot_suggestion(cx) { self.cycle_copilot_suggestions(Direction::Prev, cx); } else { - self.refresh_copilot_suggestions(false, cx); + let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none(); + if is_copilot_disabled { + cx.propagate_action(); + } } } From cc335db9e0e7ce677591d544140760ed8c080eec Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:17:44 +0200 Subject: [PATCH 149/180] editor/language: hoist out non-generic parts of edit functions. (#3130) This reduces LLVM IR size of editor (that's one of the heaviest crates to build) by almost 5%. LLVM IR size of `editor` before this PR: 3280386 LLVM IR size with `editor::edit` changed: 3227092 LLVM IR size with `editor::edit` and `language::edit` changed: 3146807 Release Notes: - N/A --- crates/editor/src/multi_buffer.rs | 140 ++++++++++++++------------- crates/language/src/buffer.rs | 151 ++++++++++++++++-------------- 2 files changed, 159 insertions(+), 132 deletions(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index c5d17dfd2eb8ac67f81a19f63ccbb7f32f9c1c62..23a117405cda1a42c4b16663edc7dc0ccf84c3dd 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -498,77 +498,91 @@ impl MultiBuffer { } } - for (buffer_id, mut edits) in buffer_edits { - edits.sort_unstable_by_key(|edit| edit.range.start); - self.buffers.borrow()[&buffer_id] - .buffer - .update(cx, |buffer, cx| { - let mut edits = edits.into_iter().peekable(); - let mut insertions = Vec::new(); - let mut original_indent_columns = Vec::new(); - let mut deletions = Vec::new(); - let empty_str: Arc = "".into(); - while let Some(BufferEdit { - mut range, - new_text, - mut is_insertion, - original_indent_column, - }) = edits.next() - { + drop(cursor); + drop(snapshot); + // Non-generic part of edit, hoisted out to avoid blowing up LLVM IR. + fn tail( + this: &mut MultiBuffer, + buffer_edits: HashMap>, + autoindent_mode: Option, + edited_excerpt_ids: Vec, + cx: &mut ModelContext, + ) { + for (buffer_id, mut edits) in buffer_edits { + edits.sort_unstable_by_key(|edit| edit.range.start); + this.buffers.borrow()[&buffer_id] + .buffer + .update(cx, |buffer, cx| { + let mut edits = edits.into_iter().peekable(); + let mut insertions = Vec::new(); + let mut original_indent_columns = Vec::new(); + let mut deletions = Vec::new(); + let empty_str: Arc = "".into(); while let Some(BufferEdit { - range: next_range, - is_insertion: next_is_insertion, - .. - }) = edits.peek() + mut range, + new_text, + mut is_insertion, + original_indent_column, + }) = edits.next() { - if range.end >= next_range.start { - range.end = cmp::max(next_range.end, range.end); - is_insertion |= *next_is_insertion; - edits.next(); - } else { - break; + while let Some(BufferEdit { + range: next_range, + is_insertion: next_is_insertion, + .. + }) = edits.peek() + { + if range.end >= next_range.start { + range.end = cmp::max(next_range.end, range.end); + is_insertion |= *next_is_insertion; + edits.next(); + } else { + break; + } } - } - if is_insertion { - original_indent_columns.push(original_indent_column); - insertions.push(( - buffer.anchor_before(range.start)..buffer.anchor_before(range.end), - new_text.clone(), - )); - } else if !range.is_empty() { - deletions.push(( - buffer.anchor_before(range.start)..buffer.anchor_before(range.end), - empty_str.clone(), - )); + if is_insertion { + original_indent_columns.push(original_indent_column); + insertions.push(( + buffer.anchor_before(range.start) + ..buffer.anchor_before(range.end), + new_text.clone(), + )); + } else if !range.is_empty() { + deletions.push(( + buffer.anchor_before(range.start) + ..buffer.anchor_before(range.end), + empty_str.clone(), + )); + } } - } - let deletion_autoindent_mode = - if let Some(AutoindentMode::Block { .. }) = autoindent_mode { - Some(AutoindentMode::Block { - original_indent_columns: Default::default(), - }) - } else { - None - }; - let insertion_autoindent_mode = - if let Some(AutoindentMode::Block { .. }) = autoindent_mode { - Some(AutoindentMode::Block { - original_indent_columns, - }) - } else { - None - }; + let deletion_autoindent_mode = + if let Some(AutoindentMode::Block { .. }) = autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns: Default::default(), + }) + } else { + None + }; + let insertion_autoindent_mode = + if let Some(AutoindentMode::Block { .. }) = autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns, + }) + } else { + None + }; - buffer.edit(deletions, deletion_autoindent_mode, cx); - buffer.edit(insertions, insertion_autoindent_mode, cx); - }) - } + buffer.edit(deletions, deletion_autoindent_mode, cx); + buffer.edit(insertions, insertion_autoindent_mode, cx); + }) + } - cx.emit(Event::ExcerptsEdited { - ids: edited_excerpt_ids, - }); + cx.emit(Event::ExcerptsEdited { + ids: edited_excerpt_ids, + }); + } + tail(self, buffer_edits, autoindent_mode, edited_excerpt_ids, cx); } pub fn start_transaction(&mut self, cx: &mut ModelContext) -> Option { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d8ebc1d445d8ae358dd40537e5e81c0eb3b1b039..78562ba8c4408d4d1803d5fe6a5a9e39a6905c0c 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1448,82 +1448,95 @@ impl Buffer { return None; } - self.start_transaction(); - self.pending_autoindent.take(); - let autoindent_request = autoindent_mode - .and_then(|mode| self.language.as_ref().map(|_| (self.snapshot(), mode))); - - let edit_operation = self.text.edit(edits.iter().cloned()); - let edit_id = edit_operation.timestamp(); + // Non-generic part hoisted out to reduce LLVM IR size. + fn tail( + this: &mut Buffer, + edits: Vec<(Range, Arc)>, + autoindent_mode: Option, + cx: &mut ModelContext, + ) -> Option { + this.start_transaction(); + this.pending_autoindent.take(); + let autoindent_request = autoindent_mode + .and_then(|mode| this.language.as_ref().map(|_| (this.snapshot(), mode))); + + let edit_operation = this.text.edit(edits.iter().cloned()); + let edit_id = edit_operation.timestamp(); + + if let Some((before_edit, mode)) = autoindent_request { + let mut delta = 0isize; + let entries = edits + .into_iter() + .enumerate() + .zip(&edit_operation.as_edit().unwrap().new_text) + .map(|((ix, (range, _)), new_text)| { + let new_text_length = new_text.len(); + let old_start = range.start.to_point(&before_edit); + let new_start = (delta + range.start as isize) as usize; + delta += + new_text_length as isize - (range.end as isize - range.start as isize); + + let mut range_of_insertion_to_indent = 0..new_text_length; + let mut first_line_is_new = false; + let mut original_indent_column = None; + + // When inserting an entire line at the beginning of an existing line, + // treat the insertion as new. + if new_text.contains('\n') + && old_start.column + <= before_edit.indent_size_for_line(old_start.row).len + { + first_line_is_new = true; + } - if let Some((before_edit, mode)) = autoindent_request { - let mut delta = 0isize; - let entries = edits - .into_iter() - .enumerate() - .zip(&edit_operation.as_edit().unwrap().new_text) - .map(|((ix, (range, _)), new_text)| { - let new_text_length = new_text.len(); - let old_start = range.start.to_point(&before_edit); - let new_start = (delta + range.start as isize) as usize; - delta += new_text_length as isize - (range.end as isize - range.start as isize); - - let mut range_of_insertion_to_indent = 0..new_text_length; - let mut first_line_is_new = false; - let mut original_indent_column = None; - - // When inserting an entire line at the beginning of an existing line, - // treat the insertion as new. - if new_text.contains('\n') - && old_start.column <= before_edit.indent_size_for_line(old_start.row).len - { - first_line_is_new = true; - } + // When inserting text starting with a newline, avoid auto-indenting the + // previous line. + if new_text.starts_with('\n') { + range_of_insertion_to_indent.start += 1; + first_line_is_new = true; + } - // When inserting text starting with a newline, avoid auto-indenting the - // previous line. - if new_text.starts_with('\n') { - range_of_insertion_to_indent.start += 1; - first_line_is_new = true; - } + // Avoid auto-indenting after the insertion. + if let AutoindentMode::Block { + original_indent_columns, + } = &mode + { + original_indent_column = Some( + original_indent_columns.get(ix).copied().unwrap_or_else(|| { + indent_size_for_text( + new_text[range_of_insertion_to_indent.clone()].chars(), + ) + .len + }), + ); + if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') { + range_of_insertion_to_indent.end -= 1; + } + } - // Avoid auto-indenting after the insertion. - if let AutoindentMode::Block { - original_indent_columns, - } = &mode - { - original_indent_column = - Some(original_indent_columns.get(ix).copied().unwrap_or_else(|| { - indent_size_for_text( - new_text[range_of_insertion_to_indent.clone()].chars(), - ) - .len - })); - if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') { - range_of_insertion_to_indent.end -= 1; + AutoindentRequestEntry { + first_line_is_new, + original_indent_column, + indent_size: before_edit.language_indent_size_at(range.start, cx), + range: this + .anchor_before(new_start + range_of_insertion_to_indent.start) + ..this.anchor_after(new_start + range_of_insertion_to_indent.end), } - } + }) + .collect(); - AutoindentRequestEntry { - first_line_is_new, - original_indent_column, - indent_size: before_edit.language_indent_size_at(range.start, cx), - range: self.anchor_before(new_start + range_of_insertion_to_indent.start) - ..self.anchor_after(new_start + range_of_insertion_to_indent.end), - } - }) - .collect(); + this.autoindent_requests.push(Arc::new(AutoindentRequest { + before_edit, + entries, + is_block_mode: matches!(mode, AutoindentMode::Block { .. }), + })); + } - self.autoindent_requests.push(Arc::new(AutoindentRequest { - before_edit, - entries, - is_block_mode: matches!(mode, AutoindentMode::Block { .. }), - })); + this.end_transaction(cx); + this.send_operation(Operation::Buffer(edit_operation), cx); + Some(edit_id) } - - self.end_transaction(cx); - self.send_operation(Operation::Buffer(edit_operation), cx); - Some(edit_id) + tail(self, edits, autoindent_mode, cx) } fn did_edit( From 5e1e0b475936872077126419b29418c0e51231ff Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 16 Oct 2023 09:55:45 -0400 Subject: [PATCH 150/180] remove print from prompts --- crates/assistant/src/prompts.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 3550c4223ca1ac8b8980179e6d9771aa01306896..2fdca046ad00381c522546056d7c986463e5d960 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -245,8 +245,8 @@ pub fn generate_content_prompt( )); } prompts.push("Never make remarks about the output.".to_string()); - prompts.push("DO NOT return any text, except the generated code.".to_string()); - prompts.push("DO NOT wrap your text in a Markdown block".to_string()); + prompts.push("Do not return any text, except the generated code.".to_string()); + prompts.push("Do not wrap your text in a Markdown block".to_string()); let current_messages = [ChatCompletionRequestMessage { role: "user".to_string(), @@ -300,9 +300,7 @@ pub fn generate_content_prompt( } } - let prompt = prompts.join("\n"); - println!("PROMPT: {:?}", prompt); - prompt + prompts.join("\n") } #[cfg(test)] From 29f45a2e384e4eaf5d43e099f4d75c4a84e4adb4 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 16 Oct 2023 10:02:11 -0400 Subject: [PATCH 151/180] clean up warnings --- crates/assistant/src/prompts.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 2fdca046ad00381c522546056d7c986463e5d960..7aafe75920b351e7244f14858036a4aa9af64f6f 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -1,13 +1,11 @@ use crate::codegen::CodegenKind; -use gpui::{AppContext, AsyncAppContext}; -use language::{BufferSnapshot, Language, OffsetRangeExt, ToOffset}; +use gpui::AsyncAppContext; +use language::{BufferSnapshot, OffsetRangeExt, ToOffset}; use semantic_index::SearchResult; -use std::borrow::Cow; use std::cmp::{self, Reverse}; use std::fmt::Write; use std::ops::Range; use std::path::PathBuf; -use std::sync::Arc; use tiktoken_rs::ChatCompletionRequestMessage; pub struct PromptCodeSnippet { @@ -19,7 +17,7 @@ pub struct PromptCodeSnippet { impl PromptCodeSnippet { pub fn new(search_result: SearchResult, cx: &AsyncAppContext) -> Self { let (content, language_name, file_path) = - search_result.buffer.read_with(cx, |buffer, cx| { + search_result.buffer.read_with(cx, |buffer, _| { let snapshot = buffer.snapshot(); let content = snapshot .text_for_range(search_result.range.clone()) @@ -29,7 +27,6 @@ impl PromptCodeSnippet { .language() .and_then(|language| Some(language.name().to_string())); - let language = buffer.language(); let file_path = buffer .file() .and_then(|file| Some(file.path().to_path_buf())); From 75fbf2ca78a35f80fd0b0b263f8c856d5f173b00 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 16 Oct 2023 12:45:01 -0400 Subject: [PATCH 152/180] Fix telemetry-related crash on start up --- crates/client/src/telemetry.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 70878bf2e46117c787cc0074f77b30494ae6f544..fd93aaeec890437968d50854ca83534c62ed4d36 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -4,7 +4,9 @@ use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration}; -use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; +use sysinfo::{ + CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt, +}; use tempfile::NamedTempFile; use util::http::HttpClient; use util::{channel::ReleaseChannel, TryFutureExt}; @@ -166,8 +168,16 @@ impl Telemetry { let this = self.clone(); cx.spawn(|mut cx| async move { - let mut system = System::new_all(); - system.refresh_all(); + // Avoiding calling `System::new_all()`, as there have been crashes related to it + let refresh_kind = RefreshKind::new() + .with_memory() // For memory usage + .with_processes(ProcessRefreshKind::everything()) // For process usage + .with_cpu(CpuRefreshKind::everything()); // For core count + + let mut system = System::new_with_specifics(refresh_kind); + + // Avoiding calling `refresh_all()`, just update what we need + system.refresh_specifics(refresh_kind); loop { // Waiting some amount of time before the first query is important to get a reasonable value @@ -175,8 +185,7 @@ impl Telemetry { const DURATION_BETWEEN_SYSTEM_EVENTS: Duration = Duration::from_secs(60); smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await; - system.refresh_memory(); - system.refresh_processes(); + system.refresh_specifics(refresh_kind); let current_process = Pid::from_u32(std::process::id()); let Some(process) = system.processes().get(¤t_process) else { From 247728b723d752ed1b2e00dcbd79f8bf8bb356c2 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 16 Oct 2023 15:53:29 -0400 Subject: [PATCH 153/180] Update indexing icon Co-Authored-By: Kyle Caverly <22121886+KCaverly@users.noreply.github.com> --- assets/icons/update.svg | 8 ++++++++ crates/assistant/src/assistant_panel.rs | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 assets/icons/update.svg diff --git a/assets/icons/update.svg b/assets/icons/update.svg new file mode 100644 index 0000000000000000000000000000000000000000..b529b2b08b42a17027566a47d20f8ae93d61ae35 --- /dev/null +++ b/assets/icons/update.svg @@ -0,0 +1,8 @@ + + + diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index e8edf70498c14e7324073b732cae6b887f3131f9..65edb1832fcfa629a3f03cad439ccbf87cbdc176 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -3223,7 +3223,7 @@ impl InlineAssistant { } } Some( - Svg::new("icons/bolt.svg") + Svg::new("icons/update.svg") .with_color(theme.assistant.inline.context_status.in_progress_icon.color) .constrained() .with_width(theme.assistant.inline.context_status.in_progress_icon.width) @@ -3241,7 +3241,7 @@ impl InlineAssistant { ) } SemanticIndexStatus::Indexed {} => Some( - Svg::new("icons/circle_check.svg") + Svg::new("icons/check.svg") .with_color(theme.assistant.inline.context_status.complete_icon.color) .constrained() .with_width(theme.assistant.inline.context_status.complete_icon.width) @@ -3249,7 +3249,7 @@ impl InlineAssistant { .with_style(theme.assistant.inline.context_status.complete_icon.container) .with_tooltip::( self.id, - "Indexing Complete", + "Index up to date", None, theme.tooltip.clone(), cx, From 4e7b35c917745e8946bed4052351d93b45f0d3f8 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 16 Oct 2023 13:27:05 -0600 Subject: [PATCH 154/180] Make joining a channel as a guest always succeed --- crates/channel/src/channel_store.rs | 1 + crates/collab/src/db/queries/channels.rs | 129 +++++++++--- crates/collab/src/db/queries/rooms.rs | 184 +++++++++++------- crates/collab/src/db/tests/channel_tests.rs | 15 +- crates/collab/src/rpc.rs | 158 +++++++++------ crates/collab/src/tests/channel_tests.rs | 52 +++++ .../src/collab_panel/channel_modal.rs | 2 +- 7 files changed, 370 insertions(+), 171 deletions(-) diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 3e8fbafb6ad0fce58d8232fedf0ac95b8cf1d625..57b183f7debeff01350a87a887373f5dfb43a894 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -972,6 +972,7 @@ impl ChannelStore { let mut all_user_ids = Vec::new(); let channel_participants = payload.channel_participants; + dbg!(&channel_participants); for entry in &channel_participants { for user_id in entry.participant_user_ids.iter() { if let Err(ix) = all_user_ids.binary_search(user_id) { diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 0b7e9eb2d828ed78da7f2baaa20636f3bce5e74d..d4276603f915f7bd7a864d2730a2e9e49330114f 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -88,6 +88,84 @@ impl Database { .await } + pub async fn join_channel_internal( + &self, + channel_id: ChannelId, + user_id: UserId, + connection: ConnectionId, + environment: &str, + tx: &DatabaseTransaction, + ) -> Result<(JoinRoom, bool)> { + let mut joined = false; + + let channel = channel::Entity::find() + .filter(channel::Column::Id.eq(channel_id)) + .one(&*tx) + .await?; + + let mut role = self + .channel_role_for_user(channel_id, user_id, &*tx) + .await?; + + if role.is_none() { + if channel.as_ref().map(|c| c.visibility) == Some(ChannelVisibility::Public) { + channel_member::Entity::insert(channel_member::ActiveModel { + id: ActiveValue::NotSet, + channel_id: ActiveValue::Set(channel_id), + user_id: ActiveValue::Set(user_id), + accepted: ActiveValue::Set(true), + role: ActiveValue::Set(ChannelRole::Guest), + }) + .on_conflict( + OnConflict::columns([ + channel_member::Column::UserId, + channel_member::Column::ChannelId, + ]) + .update_columns([channel_member::Column::Accepted]) + .to_owned(), + ) + .exec(&*tx) + .await?; + + debug_assert!( + self.channel_role_for_user(channel_id, user_id, &*tx) + .await? + == Some(ChannelRole::Guest) + ); + + role = Some(ChannelRole::Guest); + joined = true; + } + } + + if channel.is_none() || role.is_none() || role == Some(ChannelRole::Banned) { + Err(anyhow!("no such channel, or not allowed"))? + } + + let live_kit_room = format!("channel-{}", nanoid::nanoid!(30)); + let room_id = self + .get_or_create_channel_room(channel_id, &live_kit_room, environment, &*tx) + .await?; + + self.join_channel_room_internal(channel_id, room_id, user_id, connection, &*tx) + .await + .map(|jr| (jr, joined)) + } + + pub async fn join_channel( + &self, + channel_id: ChannelId, + user_id: UserId, + connection: ConnectionId, + environment: &str, + ) -> Result<(JoinRoom, bool)> { + self.transaction(move |tx| async move { + self.join_channel_internal(channel_id, user_id, connection, environment, &*tx) + .await + }) + .await + } + pub async fn set_channel_visibility( &self, channel_id: ChannelId, @@ -981,38 +1059,39 @@ impl Database { .await } - pub async fn get_or_create_channel_room( + pub(crate) async fn get_or_create_channel_room( &self, channel_id: ChannelId, live_kit_room: &str, - enviroment: &str, + environment: &str, + tx: &DatabaseTransaction, ) -> Result { - self.transaction(|tx| async move { - let tx = tx; - - let room = room::Entity::find() - .filter(room::Column::ChannelId.eq(channel_id)) - .one(&*tx) - .await?; + let room = room::Entity::find() + .filter(room::Column::ChannelId.eq(channel_id)) + .one(&*tx) + .await?; - let room_id = if let Some(room) = room { - room.id - } else { - let result = room::Entity::insert(room::ActiveModel { - channel_id: ActiveValue::Set(Some(channel_id)), - live_kit_room: ActiveValue::Set(live_kit_room.to_string()), - enviroment: ActiveValue::Set(Some(enviroment.to_string())), - ..Default::default() - }) - .exec(&*tx) - .await?; + let room_id = if let Some(room) = room { + if let Some(env) = room.enviroment { + if &env != environment { + Err(anyhow!("must join using the {} release", env))?; + } + } + room.id + } else { + let result = room::Entity::insert(room::ActiveModel { + channel_id: ActiveValue::Set(Some(channel_id)), + live_kit_room: ActiveValue::Set(live_kit_room.to_string()), + enviroment: ActiveValue::Set(Some(environment.to_string())), + ..Default::default() + }) + .exec(&*tx) + .await?; - result.last_insert_id - }; + result.last_insert_id + }; - Ok(room_id) - }) - .await + Ok(room_id) } // Insert an edge from the given channel to the given other channel. diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 625615db5f2b5316dc9f2fbb7a9ba2ac0e98d242..d2120495b05063550765eeb7fbe151512d524403 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -300,99 +300,139 @@ impl Database { } } - #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] - enum QueryParticipantIndices { - ParticipantIndex, - } - let existing_participant_indices: Vec = room_participant::Entity::find() - .filter( - room_participant::Column::RoomId - .eq(room_id) - .and(room_participant::Column::ParticipantIndex.is_not_null()), - ) - .select_only() - .column(room_participant::Column::ParticipantIndex) - .into_values::<_, QueryParticipantIndices>() - .all(&*tx) - .await?; - - let mut participant_index = 0; - while existing_participant_indices.contains(&participant_index) { - participant_index += 1; + if channel_id.is_some() { + Err(anyhow!("tried to join channel call directly"))? } - if let Some(channel_id) = channel_id { - self.check_user_is_channel_member(channel_id, user_id, &*tx) - .await?; + let participant_index = self + .get_next_participant_index_internal(room_id, &*tx) + .await?; - room_participant::Entity::insert_many([room_participant::ActiveModel { - room_id: ActiveValue::set(room_id), - user_id: ActiveValue::set(user_id), + let result = room_participant::Entity::update_many() + .filter( + Condition::all() + .add(room_participant::Column::RoomId.eq(room_id)) + .add(room_participant::Column::UserId.eq(user_id)) + .add(room_participant::Column::AnsweringConnectionId.is_null()), + ) + .set(room_participant::ActiveModel { + participant_index: ActiveValue::Set(Some(participant_index)), answering_connection_id: ActiveValue::set(Some(connection.id as i32)), answering_connection_server_id: ActiveValue::set(Some(ServerId( connection.owner_id as i32, ))), answering_connection_lost: ActiveValue::set(false), - calling_user_id: ActiveValue::set(user_id), - calling_connection_id: ActiveValue::set(connection.id as i32), - calling_connection_server_id: ActiveValue::set(Some(ServerId( - connection.owner_id as i32, - ))), - participant_index: ActiveValue::Set(Some(participant_index)), ..Default::default() - }]) - .on_conflict( - OnConflict::columns([room_participant::Column::UserId]) - .update_columns([ - room_participant::Column::AnsweringConnectionId, - room_participant::Column::AnsweringConnectionServerId, - room_participant::Column::AnsweringConnectionLost, - room_participant::Column::ParticipantIndex, - ]) - .to_owned(), - ) + }) .exec(&*tx) .await?; - } else { - let result = room_participant::Entity::update_many() - .filter( - Condition::all() - .add(room_participant::Column::RoomId.eq(room_id)) - .add(room_participant::Column::UserId.eq(user_id)) - .add(room_participant::Column::AnsweringConnectionId.is_null()), - ) - .set(room_participant::ActiveModel { - participant_index: ActiveValue::Set(Some(participant_index)), - answering_connection_id: ActiveValue::set(Some(connection.id as i32)), - answering_connection_server_id: ActiveValue::set(Some(ServerId( - connection.owner_id as i32, - ))), - answering_connection_lost: ActiveValue::set(false), - ..Default::default() - }) - .exec(&*tx) - .await?; - if result.rows_affected == 0 { - Err(anyhow!("room does not exist or was already joined"))?; - } + if result.rows_affected == 0 { + Err(anyhow!("room does not exist or was already joined"))?; } let room = self.get_room(room_id, &tx).await?; - let channel_members = if let Some(channel_id) = channel_id { - self.get_channel_participants_internal(channel_id, &tx) - .await? - } else { - Vec::new() - }; Ok(JoinRoom { room, - channel_id, - channel_members, + channel_id: None, + channel_members: vec![], }) }) .await } + async fn get_next_participant_index_internal( + &self, + room_id: RoomId, + tx: &DatabaseTransaction, + ) -> Result { + #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] + enum QueryParticipantIndices { + ParticipantIndex, + } + let existing_participant_indices: Vec = room_participant::Entity::find() + .filter( + room_participant::Column::RoomId + .eq(room_id) + .and(room_participant::Column::ParticipantIndex.is_not_null()), + ) + .select_only() + .column(room_participant::Column::ParticipantIndex) + .into_values::<_, QueryParticipantIndices>() + .all(&*tx) + .await?; + + let mut participant_index = 0; + while existing_participant_indices.contains(&participant_index) { + participant_index += 1; + } + + Ok(participant_index) + } + + pub async fn channel_id_for_room(&self, room_id: RoomId) -> Result> { + self.transaction(|tx| async move { + let room: Option = room::Entity::find() + .filter(room::Column::Id.eq(room_id)) + .one(&*tx) + .await?; + + Ok(room.and_then(|room| room.channel_id)) + }) + .await + } + + pub(crate) async fn join_channel_room_internal( + &self, + channel_id: ChannelId, + room_id: RoomId, + user_id: UserId, + connection: ConnectionId, + tx: &DatabaseTransaction, + ) -> Result { + let participant_index = self + .get_next_participant_index_internal(room_id, &*tx) + .await?; + + room_participant::Entity::insert_many([room_participant::ActiveModel { + room_id: ActiveValue::set(room_id), + user_id: ActiveValue::set(user_id), + answering_connection_id: ActiveValue::set(Some(connection.id as i32)), + answering_connection_server_id: ActiveValue::set(Some(ServerId( + connection.owner_id as i32, + ))), + answering_connection_lost: ActiveValue::set(false), + calling_user_id: ActiveValue::set(user_id), + calling_connection_id: ActiveValue::set(connection.id as i32), + calling_connection_server_id: ActiveValue::set(Some(ServerId( + connection.owner_id as i32, + ))), + participant_index: ActiveValue::Set(Some(participant_index)), + ..Default::default() + }]) + .on_conflict( + OnConflict::columns([room_participant::Column::UserId]) + .update_columns([ + room_participant::Column::AnsweringConnectionId, + room_participant::Column::AnsweringConnectionServerId, + room_participant::Column::AnsweringConnectionLost, + room_participant::Column::ParticipantIndex, + ]) + .to_owned(), + ) + .exec(&*tx) + .await?; + + let room = self.get_room(room_id, &tx).await?; + let channel_members = self + .get_channel_participants_internal(channel_id, &tx) + .await?; + Ok(JoinRoom { + room, + channel_id: Some(channel_id), + channel_members, + }) + } + pub async fn rejoin_room( &self, rejoin_room: proto::RejoinRoom, diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index b96971123238d5c17c88e75fc90019eeabf70450..9b6d8d1525b6a7f3b9a817be4a3164d2a6dd028b 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -8,7 +8,7 @@ use crate::{ db::{ queries::channels::ChannelGraph, tests::{graph, TEST_RELEASE_CHANNEL}, - ChannelId, ChannelRole, Database, NewUserParams, UserId, + ChannelId, ChannelRole, Database, NewUserParams, RoomId, UserId, }, test_both_dbs, }; @@ -207,15 +207,11 @@ async fn test_joining_channels(db: &Arc) { .user_id; let channel_1 = db.create_root_channel("channel_1", user_1).await.unwrap(); - let room_1 = db - .get_or_create_channel_room(channel_1, "1", TEST_RELEASE_CHANNEL) - .await - .unwrap(); // can join a room with membership to its channel - let joined_room = db - .join_room( - room_1, + let (joined_room, _) = db + .join_channel( + channel_1, user_1, ConnectionId { owner_id, id: 1 }, TEST_RELEASE_CHANNEL, @@ -224,11 +220,12 @@ async fn test_joining_channels(db: &Arc) { .unwrap(); assert_eq!(joined_room.room.participants.len(), 1); + let room_id = RoomId::from_proto(joined_room.room.id); drop(joined_room); // cannot join a room without membership to its channel assert!(db .join_room( - room_1, + room_id, user_2, ConnectionId { owner_id, id: 1 }, TEST_RELEASE_CHANNEL diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index c3d8a25ab7fb709750f1043b966dc0e1281c63e9..26ad2f281a0ac0d2d6c5e33e9f5dff5b5503125e 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -38,7 +38,7 @@ use lazy_static::lazy_static; use prometheus::{register_int_gauge, IntGauge}; use rpc::{ proto::{ - self, Ack, AnyTypedEnvelope, ChannelEdge, EntityMessage, EnvelopedMessage, + self, Ack, AnyTypedEnvelope, ChannelEdge, EntityMessage, EnvelopedMessage, JoinRoom, LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators, }, Connection, ConnectionId, Peer, Receipt, TypedEnvelope, @@ -977,6 +977,13 @@ async fn join_room( session: Session, ) -> Result<()> { let room_id = RoomId::from_proto(request.id); + + let channel_id = session.db().await.channel_id_for_room(room_id).await?; + + if let Some(channel_id) = channel_id { + return join_channel_internal(channel_id, Box::new(response), session).await; + } + let joined_room = { let room = session .db() @@ -992,16 +999,6 @@ async fn join_room( room.into_inner() }; - if let Some(channel_id) = joined_room.channel_id { - channel_updated( - channel_id, - &joined_room.room, - &joined_room.channel_members, - &session.peer, - &*session.connection_pool().await, - ) - } - for connection_id in session .connection_pool() .await @@ -1039,7 +1036,7 @@ async fn join_room( response.send(proto::JoinRoomResponse { room: Some(joined_room.room), - channel_id: joined_room.channel_id.map(|id| id.to_proto()), + channel_id: None, live_kit_connection_info, })?; @@ -2602,54 +2599,68 @@ async fn respond_to_channel_invite( db.respond_to_channel_invite(channel_id, session.user_id, request.accept) .await?; + if request.accept { + channel_membership_updated(db, channel_id, &session).await?; + } else { + let mut update = proto::UpdateChannels::default(); + update + .remove_channel_invitations + .push(channel_id.to_proto()); + session.peer.send(session.connection_id, update)?; + } + response.send(proto::Ack {})?; + + Ok(()) +} + +async fn channel_membership_updated( + db: tokio::sync::MutexGuard<'_, DbHandle>, + channel_id: ChannelId, + session: &Session, +) -> Result<(), crate::Error> { let mut update = proto::UpdateChannels::default(); update .remove_channel_invitations .push(channel_id.to_proto()); - if request.accept { - let result = db.get_channel_for_user(channel_id, session.user_id).await?; - update + + let result = db.get_channel_for_user(channel_id, session.user_id).await?; + update.channels.extend( + result .channels - .extend( - result - .channels - .channels - .into_iter() - .map(|channel| proto::Channel { - id: channel.id.to_proto(), - visibility: channel.visibility.into(), - name: channel.name, - }), - ); - update.unseen_channel_messages = result.channel_messages; - update.unseen_channel_buffer_changes = result.unseen_buffer_changes; - update.insert_edge = result.channels.edges; - update - .channel_participants - .extend( - result - .channel_participants - .into_iter() - .map(|(channel_id, user_ids)| proto::ChannelParticipants { - channel_id: channel_id.to_proto(), - participant_user_ids: user_ids.into_iter().map(UserId::to_proto).collect(), - }), - ); - update - .channel_permissions - .extend( - result - .channels_with_admin_privileges - .into_iter() - .map(|channel_id| proto::ChannelPermission { - channel_id: channel_id.to_proto(), - role: proto::ChannelRole::Admin.into(), - }), - ); - } + .channels + .into_iter() + .map(|channel| proto::Channel { + id: channel.id.to_proto(), + visibility: channel.visibility.into(), + name: channel.name, + }), + ); + update.unseen_channel_messages = result.channel_messages; + update.unseen_channel_buffer_changes = result.unseen_buffer_changes; + update.insert_edge = result.channels.edges; + update + .channel_participants + .extend( + result + .channel_participants + .into_iter() + .map(|(channel_id, user_ids)| proto::ChannelParticipants { + channel_id: channel_id.to_proto(), + participant_user_ids: user_ids.into_iter().map(UserId::to_proto).collect(), + }), + ); + update + .channel_permissions + .extend( + result + .channels_with_admin_privileges + .into_iter() + .map(|channel_id| proto::ChannelPermission { + channel_id: channel_id.to_proto(), + role: proto::ChannelRole::Admin.into(), + }), + ); session.peer.send(session.connection_id, update)?; - response.send(proto::Ack {})?; - Ok(()) } @@ -2659,19 +2670,35 @@ async fn join_channel( session: Session, ) -> Result<()> { let channel_id = ChannelId::from_proto(request.channel_id); - let live_kit_room = format!("channel-{}", nanoid::nanoid!(30)); + join_channel_internal(channel_id, Box::new(response), session).await +} + +trait JoinChannelInternalResponse { + fn send(self, result: proto::JoinRoomResponse) -> Result<()>; +} +impl JoinChannelInternalResponse for Response { + fn send(self, result: proto::JoinRoomResponse) -> Result<()> { + Response::::send(self, result) + } +} +impl JoinChannelInternalResponse for Response { + fn send(self, result: proto::JoinRoomResponse) -> Result<()> { + Response::::send(self, result) + } +} +async fn join_channel_internal( + channel_id: ChannelId, + response: Box, + session: Session, +) -> Result<()> { let joined_room = { leave_room_for_session(&session).await?; let db = session.db().await; - let room_id = db - .get_or_create_channel_room(channel_id, &live_kit_room, &*RELEASE_CHANNEL_NAME) - .await?; - - let joined_room = db - .join_room( - room_id, + let (joined_room, joined_channel) = db + .join_channel( + channel_id, session.user_id, session.connection_id, RELEASE_CHANNEL_NAME.as_str(), @@ -2698,9 +2725,13 @@ async fn join_channel( live_kit_connection_info, })?; + if joined_channel { + channel_membership_updated(db, channel_id, &session).await? + } + room_updated(&joined_room.room, &session.peer); - joined_room.into_inner() + joined_room }; channel_updated( @@ -2712,7 +2743,6 @@ async fn join_channel( ); update_user_contacts(session.user_id, &session).await?; - Ok(()) } diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 95a672e76c7b70e18c7deadbc1a19193655a8dcd..1700dfc5d3dd46544b5a4767198c57c603d8825b 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -912,6 +912,58 @@ async fn test_lost_channel_creation( ], ); } +#[gpui::test] +async fn test_guest_access( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + deterministic.forbid_parking(); + + let mut server = TestServer::start(&deterministic).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + + let channels = server + .make_channel_tree(&[("channel-a", None)], (&client_a, cx_a)) + .await; + let channel_a_id = channels[0]; + + let active_call_b = cx_b.read(ActiveCall::global); + + // should not be allowed to join + assert!(active_call_b + .update(cx_b, |call, cx| call.join_channel(channel_a_id, cx)) + .await + .is_err()); + + client_a + .channel_store() + .update(cx_a, |channel_store, cx| { + channel_store.set_channel_visibility(channel_a_id, proto::ChannelVisibility::Public, cx) + }) + .await + .unwrap(); + + active_call_b + .update(cx_b, |call, cx| call.join_channel(channel_a_id, cx)) + .await + .unwrap(); + + deterministic.run_until_parked(); + + assert!(client_b + .channel_store() + .update(cx_b, |channel_store, _| channel_store + .channel_for_id(channel_a_id) + .is_some())); + + client_a.channel_store().update(cx_a, |channel_store, _| { + let participants = channel_store.channel_participants(channel_a_id); + assert_eq!(participants.len(), 1); + assert_eq!(participants[0].id, client_b.user_id().unwrap()); + }) +} #[gpui::test] async fn test_channel_moving( diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index bf04e4f7e6b257d40d52c4da9a2bcc97237684b3..da6edbde696ae011895142b95641f969538da28e 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -1,4 +1,4 @@ -use channel::{Channel, ChannelId, ChannelMembership, ChannelStore}; +use channel::{ChannelId, ChannelMembership, ChannelStore}; use client::{ proto::{self, ChannelRole, ChannelVisibility}, User, UserId, UserStore, From 2feb091961b2c0b719cb546c39cd1752590aea38 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 16 Oct 2023 16:11:00 -0600 Subject: [PATCH 155/180] Ensure that invitees do not have permissions They have to accept the invite, (which joining the channel will do), first. --- crates/collab/src/db/queries/channels.rs | 228 +++++++++++--------- crates/collab/src/db/tests/channel_tests.rs | 72 +++---- crates/collab/src/rpc.rs | 35 ++- crates/collab/src/tests/channel_tests.rs | 63 +++++- 4 files changed, 238 insertions(+), 160 deletions(-) diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index d4276603f915f7bd7a864d2730a2e9e49330114f..e3a6170452cd21920819da7a261e248b87632b63 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -88,80 +88,87 @@ impl Database { .await } - pub async fn join_channel_internal( + pub async fn join_channel( &self, channel_id: ChannelId, user_id: UserId, connection: ConnectionId, environment: &str, - tx: &DatabaseTransaction, - ) -> Result<(JoinRoom, bool)> { - let mut joined = false; + ) -> Result<(JoinRoom, Option)> { + self.transaction(move |tx| async move { + let mut joined_channel_id = None; - let channel = channel::Entity::find() - .filter(channel::Column::Id.eq(channel_id)) - .one(&*tx) - .await?; + let channel = channel::Entity::find() + .filter(channel::Column::Id.eq(channel_id)) + .one(&*tx) + .await?; - let mut role = self - .channel_role_for_user(channel_id, user_id, &*tx) - .await?; + let mut role = self + .channel_role_for_user(channel_id, user_id, &*tx) + .await?; + + if role.is_none() && channel.is_some() { + if let Some(invitation) = self + .pending_invite_for_channel(channel_id, user_id, &*tx) + .await? + { + // note, this may be a parent channel + joined_channel_id = Some(invitation.channel_id); + role = Some(invitation.role); + + channel_member::Entity::update(channel_member::ActiveModel { + accepted: ActiveValue::Set(true), + ..invitation.into_active_model() + }) + .exec(&*tx) + .await?; + + debug_assert!( + self.channel_role_for_user(channel_id, user_id, &*tx) + .await? + == role + ); + } + } + if role.is_none() + && channel.as_ref().map(|c| c.visibility) == Some(ChannelVisibility::Public) + { + let channel_id_to_join = self + .most_public_ancestor_for_channel(channel_id, &*tx) + .await? + .unwrap_or(channel_id); + role = Some(ChannelRole::Guest); + joined_channel_id = Some(channel_id_to_join); - if role.is_none() { - if channel.as_ref().map(|c| c.visibility) == Some(ChannelVisibility::Public) { channel_member::Entity::insert(channel_member::ActiveModel { id: ActiveValue::NotSet, - channel_id: ActiveValue::Set(channel_id), + channel_id: ActiveValue::Set(channel_id_to_join), user_id: ActiveValue::Set(user_id), accepted: ActiveValue::Set(true), role: ActiveValue::Set(ChannelRole::Guest), }) - .on_conflict( - OnConflict::columns([ - channel_member::Column::UserId, - channel_member::Column::ChannelId, - ]) - .update_columns([channel_member::Column::Accepted]) - .to_owned(), - ) .exec(&*tx) .await?; debug_assert!( self.channel_role_for_user(channel_id, user_id, &*tx) .await? - == Some(ChannelRole::Guest) + == role ); - - role = Some(ChannelRole::Guest); - joined = true; } - } - - if channel.is_none() || role.is_none() || role == Some(ChannelRole::Banned) { - Err(anyhow!("no such channel, or not allowed"))? - } - let live_kit_room = format!("channel-{}", nanoid::nanoid!(30)); - let room_id = self - .get_or_create_channel_room(channel_id, &live_kit_room, environment, &*tx) - .await?; + if channel.is_none() || role.is_none() || role == Some(ChannelRole::Banned) { + Err(anyhow!("no such channel, or not allowed"))? + } - self.join_channel_room_internal(channel_id, room_id, user_id, connection, &*tx) - .await - .map(|jr| (jr, joined)) - } + let live_kit_room = format!("channel-{}", nanoid::nanoid!(30)); + let room_id = self + .get_or_create_channel_room(channel_id, &live_kit_room, environment, &*tx) + .await?; - pub async fn join_channel( - &self, - channel_id: ChannelId, - user_id: UserId, - connection: ConnectionId, - environment: &str, - ) -> Result<(JoinRoom, bool)> { - self.transaction(move |tx| async move { - self.join_channel_internal(channel_id, user_id, connection, environment, &*tx) + self.join_channel_room_internal(channel_id, room_id, user_id, connection, &*tx) .await + .map(|jr| (jr, joined_channel_id)) }) .await } @@ -624,29 +631,29 @@ impl Database { admin_id: UserId, for_user: UserId, role: ChannelRole, - ) -> Result<()> { + ) -> Result { self.transaction(|tx| async move { self.check_user_is_channel_admin(channel_id, admin_id, &*tx) .await?; - let result = channel_member::Entity::update_many() + let membership = channel_member::Entity::find() .filter( channel_member::Column::ChannelId .eq(channel_id) .and(channel_member::Column::UserId.eq(for_user)), ) - .set(channel_member::ActiveModel { - role: ActiveValue::set(role), - ..Default::default() - }) - .exec(&*tx) + .one(&*tx) .await?; - if result.rows_affected == 0 { - Err(anyhow!("no such member"))?; - } + let Some(membership) = membership else { + Err(anyhow!("no such member"))? + }; - Ok(()) + let mut update = membership.into_active_model(); + update.role = ActiveValue::Set(role); + let updated = channel_member::Entity::update(update).exec(&*tx).await?; + + Ok(updated) }) .await } @@ -844,6 +851,52 @@ impl Database { } } + pub async fn pending_invite_for_channel( + &self, + channel_id: ChannelId, + user_id: UserId, + tx: &DatabaseTransaction, + ) -> Result> { + let channel_ids = self.get_channel_ancestors(channel_id, tx).await?; + + let row = channel_member::Entity::find() + .filter(channel_member::Column::ChannelId.is_in(channel_ids)) + .filter(channel_member::Column::UserId.eq(user_id)) + .filter(channel_member::Column::Accepted.eq(false)) + .one(&*tx) + .await?; + + Ok(row) + } + + pub async fn most_public_ancestor_for_channel( + &self, + channel_id: ChannelId, + tx: &DatabaseTransaction, + ) -> Result> { + let channel_ids = self.get_channel_ancestors(channel_id, tx).await?; + + let rows = channel::Entity::find() + .filter(channel::Column::Id.is_in(channel_ids.clone())) + .filter(channel::Column::Visibility.eq(ChannelVisibility::Public)) + .all(&*tx) + .await?; + + let mut visible_channels: HashSet = HashSet::default(); + + for row in rows { + visible_channels.insert(row.id); + } + + for ancestor in channel_ids.into_iter().rev() { + if visible_channels.contains(&ancestor) { + return Ok(Some(ancestor)); + } + } + + Ok(None) + } + pub async fn channel_role_for_user( &self, channel_id: ChannelId, @@ -864,7 +917,8 @@ impl Database { .filter( channel_member::Column::ChannelId .is_in(channel_ids) - .and(channel_member::Column::UserId.eq(user_id)), + .and(channel_member::Column::UserId.eq(user_id)) + .and(channel_member::Column::Accepted.eq(true)), ) .select_only() .column(channel_member::Column::ChannelId) @@ -1009,52 +1063,22 @@ impl Database { Ok(results) } - /// Returns the channel with the given ID and: - /// - true if the user is a member - /// - false if the user hasn't accepted the invitation yet - pub async fn get_channel( - &self, - channel_id: ChannelId, - user_id: UserId, - ) -> Result> { + /// Returns the channel with the given ID + pub async fn get_channel(&self, channel_id: ChannelId, user_id: UserId) -> Result { self.transaction(|tx| async move { - let tx = tx; + self.check_user_is_channel_participant(channel_id, user_id, &*tx) + .await?; let channel = channel::Entity::find_by_id(channel_id).one(&*tx).await?; + let Some(channel) = channel else { + Err(anyhow!("no such channel"))? + }; - if let Some(channel) = channel { - if self - .check_user_is_channel_member(channel_id, user_id, &*tx) - .await - .is_err() - { - return Ok(None); - } - - let channel_membership = channel_member::Entity::find() - .filter( - channel_member::Column::ChannelId - .eq(channel_id) - .and(channel_member::Column::UserId.eq(user_id)), - ) - .one(&*tx) - .await?; - - let is_accepted = channel_membership - .map(|membership| membership.accepted) - .unwrap_or(false); - - Ok(Some(( - Channel { - id: channel.id, - visibility: channel.visibility, - name: channel.name, - }, - is_accepted, - ))) - } else { - Ok(None) - } + Ok(Channel { + id: channel.id, + visibility: channel.visibility, + name: channel.name, + }) }) .await } diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 9b6d8d1525b6a7f3b9a817be4a3164d2a6dd028b..f08b1554bc0062e080e1bd5fdd8eee146f090c9d 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -51,7 +51,7 @@ async fn test_channels(db: &Arc) { let zed_id = db.create_root_channel("zed", a_id).await.unwrap(); // Make sure that people cannot read channels they haven't been invited to - assert!(db.get_channel(zed_id, b_id).await.unwrap().is_none()); + assert!(db.get_channel(zed_id, b_id).await.is_err()); db.invite_channel_member(zed_id, b_id, a_id, ChannelRole::Member) .await @@ -157,7 +157,7 @@ async fn test_channels(db: &Arc) { // Remove a single channel db.delete_channel(crdb_id, a_id).await.unwrap(); - assert!(db.get_channel(crdb_id, a_id).await.unwrap().is_none()); + assert!(db.get_channel(crdb_id, a_id).await.is_err()); // Remove a channel tree let (mut channel_ids, user_ids) = db.delete_channel(rust_id, a_id).await.unwrap(); @@ -165,9 +165,9 @@ async fn test_channels(db: &Arc) { assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]); assert_eq!(user_ids, &[a_id]); - assert!(db.get_channel(rust_id, a_id).await.unwrap().is_none()); - assert!(db.get_channel(cargo_id, a_id).await.unwrap().is_none()); - assert!(db.get_channel(cargo_ra_id, a_id).await.unwrap().is_none()); + assert!(db.get_channel(rust_id, a_id).await.is_err()); + assert!(db.get_channel(cargo_id, a_id).await.is_err()); + assert!(db.get_channel(cargo_ra_id, a_id).await.is_err()); } test_both_dbs!( @@ -381,11 +381,7 @@ async fn test_channel_renames(db: &Arc) { let zed_archive_id = zed_id; - let (channel, _) = db - .get_channel(zed_archive_id, user_1) - .await - .unwrap() - .unwrap(); + let channel = db.get_channel(zed_archive_id, user_1).await.unwrap(); assert_eq!(channel.name, "zed-archive"); let non_permissioned_rename = db @@ -860,12 +856,6 @@ async fn test_user_is_channel_participant(db: &Arc) { }) .await .unwrap(); - db.transaction(|tx| async move { - db.check_user_is_channel_participant(vim_channel, guest, &*tx) - .await - }) - .await - .unwrap(); let members = db .get_channel_participant_details(vim_channel, admin) @@ -896,6 +886,13 @@ async fn test_user_is_channel_participant(db: &Arc) { .await .unwrap(); + db.transaction(|tx| async move { + db.check_user_is_channel_participant(vim_channel, guest, &*tx) + .await + }) + .await + .unwrap(); + let channels = db.get_channels_for_user(guest).await.unwrap().channels; assert_dag(channels, &[(vim_channel, None)]); let channels = db.get_channels_for_user(member).await.unwrap().channels; @@ -953,29 +950,7 @@ async fn test_user_is_channel_participant(db: &Arc) { .await .unwrap(); - db.transaction(|tx| async move { - db.check_user_is_channel_participant(zed_channel, guest, &*tx) - .await - }) - .await - .unwrap(); - assert!(db - .transaction(|tx| async move { - db.check_user_is_channel_participant(active_channel, guest, &*tx) - .await - }) - .await - .is_err(),); - - db.transaction(|tx| async move { - db.check_user_is_channel_participant(vim_channel, guest, &*tx) - .await - }) - .await - .unwrap(); - // currently people invited to parent channels are not shown here - // (though they *do* have permissions!) let members = db .get_channel_participant_details(vim_channel, admin) .await @@ -1000,6 +975,27 @@ async fn test_user_is_channel_participant(db: &Arc) { .await .unwrap(); + db.transaction(|tx| async move { + db.check_user_is_channel_participant(zed_channel, guest, &*tx) + .await + }) + .await + .unwrap(); + assert!(db + .transaction(|tx| async move { + db.check_user_is_channel_participant(active_channel, guest, &*tx) + .await + }) + .await + .is_err(),); + + db.transaction(|tx| async move { + db.check_user_is_channel_participant(vim_channel, guest, &*tx) + .await + }) + .await + .unwrap(); + let members = db .get_channel_participant_details(vim_channel, admin) .await diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 26ad2f281a0ac0d2d6c5e33e9f5dff5b5503125e..4b33550c39e66a0f8e62b3757c535fb6988a68a8 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -38,7 +38,7 @@ use lazy_static::lazy_static; use prometheus::{register_int_gauge, IntGauge}; use rpc::{ proto::{ - self, Ack, AnyTypedEnvelope, ChannelEdge, EntityMessage, EnvelopedMessage, JoinRoom, + self, Ack, AnyTypedEnvelope, ChannelEdge, EntityMessage, EnvelopedMessage, LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators, }, Connection, ConnectionId, Peer, Receipt, TypedEnvelope, @@ -2289,10 +2289,7 @@ async fn invite_channel_member( ) .await?; - let (channel, _) = db - .get_channel(channel_id, session.user_id) - .await? - .ok_or_else(|| anyhow!("channel not found"))?; + let channel = db.get_channel(channel_id, session.user_id).await?; let mut update = proto::UpdateChannels::default(); update.channel_invitations.push(proto::Channel { @@ -2380,21 +2377,19 @@ async fn set_channel_member_role( let db = session.db().await; let channel_id = ChannelId::from_proto(request.channel_id); let member_id = UserId::from_proto(request.user_id); - db.set_channel_member_role( - channel_id, - session.user_id, - member_id, - request.role().into(), - ) - .await?; + let channel_member = db + .set_channel_member_role( + channel_id, + session.user_id, + member_id, + request.role().into(), + ) + .await?; - let (channel, has_accepted) = db - .get_channel(channel_id, member_id) - .await? - .ok_or_else(|| anyhow!("channel not found"))?; + let channel = db.get_channel(channel_id, session.user_id).await?; let mut update = proto::UpdateChannels::default(); - if has_accepted { + if channel_member.accepted { update.channel_permissions.push(proto::ChannelPermission { channel_id: channel.id.to_proto(), role: request.role, @@ -2724,9 +2719,11 @@ async fn join_channel_internal( channel_id: joined_room.channel_id.map(|id| id.to_proto()), live_kit_connection_info, })?; + dbg!("Joined channel", &joined_channel); - if joined_channel { - channel_membership_updated(db, channel_id, &session).await? + if let Some(joined_channel) = joined_channel { + dbg!("CMU"); + channel_membership_updated(db, joined_channel, &session).await? } room_updated(&joined_room.room, &session.peer); diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 1700dfc5d3dd46544b5a4767198c57c603d8825b..1bb8c92ac80ac45b78ccff76de8f15c1468acaef 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -7,7 +7,7 @@ use channel::{ChannelId, ChannelMembership, ChannelStore}; use client::User; use gpui::{executor::Deterministic, ModelHandle, TestAppContext}; use rpc::{ - proto::{self}, + proto::{self, ChannelRole}, RECEIVE_TIMEOUT, }; use std::sync::Arc; @@ -965,6 +965,67 @@ async fn test_guest_access( }) } +#[gpui::test] +async fn test_invite_access( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + deterministic.forbid_parking(); + + let mut server = TestServer::start(&deterministic).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + + let channels = server + .make_channel_tree( + &[("channel-a", None), ("channel-b", Some("channel-a"))], + (&client_a, cx_a), + ) + .await; + let channel_a_id = channels[0]; + let channel_b_id = channels[0]; + + let active_call_b = cx_b.read(ActiveCall::global); + + // should not be allowed to join + assert!(active_call_b + .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx)) + .await + .is_err()); + + client_a + .channel_store() + .update(cx_a, |channel_store, cx| { + channel_store.invite_member( + channel_a_id, + client_b.user_id().unwrap(), + ChannelRole::Member, + cx, + ) + }) + .await + .unwrap(); + + active_call_b + .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx)) + .await + .unwrap(); + + deterministic.run_until_parked(); + + client_b.channel_store().update(cx_b, |channel_store, _| { + assert!(channel_store.channel_for_id(channel_b_id).is_some()); + assert!(channel_store.channel_for_id(channel_a_id).is_some()); + }); + + client_a.channel_store().update(cx_a, |channel_store, _| { + let participants = channel_store.channel_participants(channel_b_id); + assert_eq!(participants.len(), 1); + assert_eq!(participants[0].id, client_b.user_id().unwrap()); + }) +} + #[gpui::test] async fn test_channel_moving( deterministic: Arc, From 6ffbc3a0f52bd94751393ad1f0217b9692cfa230 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 16 Oct 2023 20:03:44 -0600 Subject: [PATCH 156/180] Allow pasting ZED urls in the command palette in development --- Cargo.lock | 2 + crates/collab/src/db/queries/channels.rs | 2 +- crates/command_palette/Cargo.toml | 1 + crates/command_palette/src/command_palette.rs | 17 +- crates/workspace/src/workspace.rs | 1 + crates/zed-actions/Cargo.toml | 1 + crates/zed-actions/src/lib.rs | 15 +- crates/zed/src/main.rs | 206 +----------------- crates/zed/src/open_listener.rs | 202 ++++++++++++++++- crates/zed/src/zed.rs | 6 + 10 files changed, 245 insertions(+), 208 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72ee771f5d96093685abc019f8d1f3f5d1201c22..f68cd22ae7de47e94b488ae6c69871793f596191 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1623,6 +1623,7 @@ dependencies = [ "theme", "util", "workspace", + "zed-actions", ] [[package]] @@ -10213,6 +10214,7 @@ name = "zed-actions" version = "0.1.0" dependencies = [ "gpui", + "serde", ] [[package]] diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index e3a6170452cd21920819da7a261e248b87632b63..b10cbd14f18c701a9415bb2705f884e8c760594f 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -979,7 +979,7 @@ impl Database { }) } - /// Returns the channel ancestors, include itself, deepest first + /// Returns the channel ancestors in arbitrary order pub async fn get_channel_ancestors( &self, channel_id: ChannelId, diff --git a/crates/command_palette/Cargo.toml b/crates/command_palette/Cargo.toml index 95ba452c142dc2fa2c615b000984e2d627a1a2e8..b42a3b5f41ae780ddd877af28e3e58b3a2cba332 100644 --- a/crates/command_palette/Cargo.toml +++ b/crates/command_palette/Cargo.toml @@ -19,6 +19,7 @@ settings = { path = "../settings" } util = { path = "../util" } theme = { path = "../theme" } workspace = { path = "../workspace" } +zed-actions = { path = "../zed-actions" } [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 10c9ba7b8670e197e3d4fc805dbd15681a9edd30..9b74c13a71e70ea66b292d2232f0e4f55dc6b451 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -6,8 +6,12 @@ use gpui::{ }; use picker::{Picker, PickerDelegate, PickerEvent}; use std::cmp::{self, Reverse}; -use util::ResultExt; +use util::{ + channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL, RELEASE_CHANNEL_NAME}, + ResultExt, +}; use workspace::Workspace; +use zed_actions::OpenZedURL; pub fn init(cx: &mut AppContext) { cx.add_action(toggle_command_palette); @@ -167,13 +171,22 @@ impl PickerDelegate for CommandPaletteDelegate { ) .await }; - let intercept_result = cx.read(|cx| { + let mut intercept_result = cx.read(|cx| { if cx.has_global::() { cx.global::()(&query, cx) } else { None } }); + if *RELEASE_CHANNEL == ReleaseChannel::Dev { + if parse_zed_link(&query).is_some() { + intercept_result = Some(CommandInterceptResult { + action: OpenZedURL { url: query.clone() }.boxed_clone(), + string: query.clone(), + positions: vec![], + }) + } + } if let Some(CommandInterceptResult { action, string, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8b068fa10cf984a5301cd9e6d7d73dc118218f6f..710883d7cc428eaceca51835ac54c24aee4c5b9f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -288,6 +288,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_global_action(restart); cx.add_async_action(Workspace::save_all); cx.add_action(Workspace::add_folder_to_project); + cx.add_action( |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext| { let pane = workspace.active_pane().clone(); diff --git a/crates/zed-actions/Cargo.toml b/crates/zed-actions/Cargo.toml index b3fe3cbb53c46457c6855a916dcc6ef650e6be50..353041264a93f2f66cf4daae93b121d1933b4e48 100644 --- a/crates/zed-actions/Cargo.toml +++ b/crates/zed-actions/Cargo.toml @@ -8,3 +8,4 @@ publish = false [dependencies] gpui = { path = "../gpui" } +serde.workspace = true diff --git a/crates/zed-actions/src/lib.rs b/crates/zed-actions/src/lib.rs index bcd086924d2d0b36000bd4b67d2567dbc19c589d..df6405a4b1819900aaa43a6d335339baf5ff92b6 100644 --- a/crates/zed-actions/src/lib.rs +++ b/crates/zed-actions/src/lib.rs @@ -1,4 +1,7 @@ -use gpui::actions; +use std::sync::Arc; + +use gpui::{actions, impl_actions}; +use serde::Deserialize; actions!( zed, @@ -26,3 +29,13 @@ actions!( ResetDatabase, ] ); + +#[derive(Deserialize, Clone, PartialEq)] +pub struct OpenBrowser { + pub url: Arc, +} +#[derive(Deserialize, Clone, PartialEq)] +pub struct OpenZedURL { + pub url: String, +} +impl_actions!(zed, [OpenBrowser, OpenZedURL]); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f89a880c715ce645cae4dbd988051b196a7f5c7a..0e3bb6ef4349f7b3eab8c7a9ea3be9a211785b7f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -3,22 +3,16 @@ use anyhow::{anyhow, Context, Result}; use backtrace::Backtrace; -use cli::{ - ipc::{self, IpcSender}, - CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME, -}; +use cli::FORCE_CLI_MODE_ENV_VAR_NAME; use client::{ self, Client, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN, }; use db::kvp::KEY_VALUE_STORE; -use editor::{scroll::autoscroll::Autoscroll, Editor}; -use futures::{ - channel::{mpsc, oneshot}, - FutureExt, SinkExt, StreamExt, -}; +use editor::Editor; +use futures::StreamExt; use gpui::{Action, App, AppContext, AssetSource, AsyncAppContext, Task}; use isahc::{config::Configurable, Request}; -use language::{LanguageRegistry, Point}; +use language::LanguageRegistry; use log::LevelFilter; use node_runtime::RealNodeRuntime; use parking_lot::Mutex; @@ -28,7 +22,6 @@ use settings::{default_settings, handle_settings_file_changes, watch_config_file use simplelog::ConfigBuilder; use smol::process::Command; use std::{ - collections::HashMap, env, ffi::OsStr, fs::OpenOptions, @@ -42,11 +35,9 @@ use std::{ thread, time::{Duration, SystemTime, UNIX_EPOCH}, }; -use sum_tree::Bias; use util::{ channel::{parse_zed_link, ReleaseChannel}, http::{self, HttpClient}, - paths::PathLikeWithPosition, }; use uuid::Uuid; use welcome::{show_welcome_experience, FIRST_OPEN}; @@ -58,12 +49,9 @@ use zed::{ assets::Assets, build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus, only_instance::{ensure_only_instance, IsOnlyInstance}, + open_listener::{handle_cli_connection, OpenListener, OpenRequest}, }; -use crate::open_listener::{OpenListener, OpenRequest}; - -mod open_listener; - fn main() { let http = http::client(); init_paths(); @@ -113,6 +101,7 @@ fn main() { app.run(move |cx| { cx.set_global(*RELEASE_CHANNEL); + cx.set_global(listener.clone()); let mut store = SettingsStore::default(); store @@ -729,189 +718,6 @@ async fn watch_languages(_: Arc, _: Arc) -> Option<()> #[cfg(not(debug_assertions))] fn watch_file_types(_fs: Arc, _cx: &mut AppContext) {} -fn connect_to_cli( - server_name: &str, -) -> Result<(mpsc::Receiver, IpcSender)> { - let handshake_tx = cli::ipc::IpcSender::::connect(server_name.to_string()) - .context("error connecting to cli")?; - let (request_tx, request_rx) = ipc::channel::()?; - let (response_tx, response_rx) = ipc::channel::()?; - - handshake_tx - .send(IpcHandshake { - requests: request_tx, - responses: response_rx, - }) - .context("error sending ipc handshake")?; - - let (mut async_request_tx, async_request_rx) = - futures::channel::mpsc::channel::(16); - thread::spawn(move || { - while let Ok(cli_request) = request_rx.recv() { - if smol::block_on(async_request_tx.send(cli_request)).is_err() { - break; - } - } - Ok::<_, anyhow::Error>(()) - }); - - Ok((async_request_rx, response_tx)) -} - -async fn handle_cli_connection( - (mut requests, responses): (mpsc::Receiver, IpcSender), - app_state: Arc, - mut cx: AsyncAppContext, -) { - if let Some(request) = requests.next().await { - match request { - CliRequest::Open { paths, wait } => { - let mut caret_positions = HashMap::new(); - - let paths = if paths.is_empty() { - workspace::last_opened_workspace_paths() - .await - .map(|location| location.paths().to_vec()) - .unwrap_or_default() - } else { - paths - .into_iter() - .filter_map(|path_with_position_string| { - let path_with_position = PathLikeWithPosition::parse_str( - &path_with_position_string, - |path_str| { - Ok::<_, std::convert::Infallible>( - Path::new(path_str).to_path_buf(), - ) - }, - ) - .expect("Infallible"); - let path = path_with_position.path_like; - if let Some(row) = path_with_position.row { - if path.is_file() { - let row = row.saturating_sub(1); - let col = - path_with_position.column.unwrap_or(0).saturating_sub(1); - caret_positions.insert(path.clone(), Point::new(row, col)); - } - } - Some(path) - }) - .collect() - }; - - let mut errored = false; - match cx - .update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .await - { - Ok((workspace, items)) => { - let mut item_release_futures = Vec::new(); - - for (item, path) in items.into_iter().zip(&paths) { - match item { - Some(Ok(item)) => { - if let Some(point) = caret_positions.remove(path) { - if let Some(active_editor) = item.downcast::() { - active_editor - .downgrade() - .update(&mut cx, |editor, cx| { - let snapshot = - editor.snapshot(cx).display_snapshot; - let point = snapshot - .buffer_snapshot - .clip_point(point, Bias::Left); - editor.change_selections( - Some(Autoscroll::center()), - cx, - |s| s.select_ranges([point..point]), - ); - }) - .log_err(); - } - } - - let released = oneshot::channel(); - cx.update(|cx| { - item.on_release( - cx, - Box::new(move |_| { - let _ = released.0.send(()); - }), - ) - .detach(); - }); - item_release_futures.push(released.1); - } - Some(Err(err)) => { - responses - .send(CliResponse::Stderr { - message: format!("error opening {:?}: {}", path, err), - }) - .log_err(); - errored = true; - } - None => {} - } - } - - if wait { - let background = cx.background(); - let wait = async move { - if paths.is_empty() { - let (done_tx, done_rx) = oneshot::channel(); - if let Some(workspace) = workspace.upgrade(&cx) { - let _subscription = cx.update(|cx| { - cx.observe_release(&workspace, move |_, _| { - let _ = done_tx.send(()); - }) - }); - drop(workspace); - let _ = done_rx.await; - } - } else { - let _ = - futures::future::try_join_all(item_release_futures).await; - }; - } - .fuse(); - futures::pin_mut!(wait); - - loop { - // Repeatedly check if CLI is still open to avoid wasting resources - // waiting for files or workspaces to close. - let mut timer = background.timer(Duration::from_secs(1)).fuse(); - futures::select_biased! { - _ = wait => break, - _ = timer => { - if responses.send(CliResponse::Ping).is_err() { - break; - } - } - } - } - } - } - Err(error) => { - errored = true; - responses - .send(CliResponse::Stderr { - message: format!("error opening {:?}: {}", paths, error), - }) - .log_err(); - } - } - - responses - .send(CliResponse::Exit { - status: i32::from(errored), - }) - .log_err(); - } - } - } -} - pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] { &[ ("Go to file", &file_finder::Toggle), diff --git a/crates/zed/src/open_listener.rs b/crates/zed/src/open_listener.rs index 9b416e14be4b601cad8c6b4555a9c9459757de84..578d8cd69f1992e9e6165b303ff2f2cb7765a7ac 100644 --- a/crates/zed/src/open_listener.rs +++ b/crates/zed/src/open_listener.rs @@ -1,15 +1,26 @@ -use anyhow::anyhow; +use anyhow::{anyhow, Context, Result}; +use cli::{ipc, IpcHandshake}; use cli::{ipc::IpcSender, CliRequest, CliResponse}; -use futures::channel::mpsc; +use editor::scroll::autoscroll::Autoscroll; +use editor::Editor; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; +use futures::channel::{mpsc, oneshot}; +use futures::{FutureExt, SinkExt, StreamExt}; +use gpui::AsyncAppContext; +use language::{Bias, Point}; +use std::collections::HashMap; use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; +use std::path::Path; use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::thread; +use std::time::Duration; use std::{path::PathBuf, sync::atomic::AtomicBool}; use util::channel::parse_zed_link; +use util::paths::PathLikeWithPosition; use util::ResultExt; - -use crate::connect_to_cli; +use workspace::AppState; pub enum OpenRequest { Paths { @@ -96,3 +107,186 @@ impl OpenListener { Some(OpenRequest::Paths { paths }) } } + +fn connect_to_cli( + server_name: &str, +) -> Result<(mpsc::Receiver, IpcSender)> { + let handshake_tx = cli::ipc::IpcSender::::connect(server_name.to_string()) + .context("error connecting to cli")?; + let (request_tx, request_rx) = ipc::channel::()?; + let (response_tx, response_rx) = ipc::channel::()?; + + handshake_tx + .send(IpcHandshake { + requests: request_tx, + responses: response_rx, + }) + .context("error sending ipc handshake")?; + + let (mut async_request_tx, async_request_rx) = + futures::channel::mpsc::channel::(16); + thread::spawn(move || { + while let Ok(cli_request) = request_rx.recv() { + if smol::block_on(async_request_tx.send(cli_request)).is_err() { + break; + } + } + Ok::<_, anyhow::Error>(()) + }); + + Ok((async_request_rx, response_tx)) +} + +pub async fn handle_cli_connection( + (mut requests, responses): (mpsc::Receiver, IpcSender), + app_state: Arc, + mut cx: AsyncAppContext, +) { + if let Some(request) = requests.next().await { + match request { + CliRequest::Open { paths, wait } => { + let mut caret_positions = HashMap::new(); + + let paths = if paths.is_empty() { + workspace::last_opened_workspace_paths() + .await + .map(|location| location.paths().to_vec()) + .unwrap_or_default() + } else { + paths + .into_iter() + .filter_map(|path_with_position_string| { + let path_with_position = PathLikeWithPosition::parse_str( + &path_with_position_string, + |path_str| { + Ok::<_, std::convert::Infallible>( + Path::new(path_str).to_path_buf(), + ) + }, + ) + .expect("Infallible"); + let path = path_with_position.path_like; + if let Some(row) = path_with_position.row { + if path.is_file() { + let row = row.saturating_sub(1); + let col = + path_with_position.column.unwrap_or(0).saturating_sub(1); + caret_positions.insert(path.clone(), Point::new(row, col)); + } + } + Some(path) + }) + .collect() + }; + + let mut errored = false; + match cx + .update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .await + { + Ok((workspace, items)) => { + let mut item_release_futures = Vec::new(); + + for (item, path) in items.into_iter().zip(&paths) { + match item { + Some(Ok(item)) => { + if let Some(point) = caret_positions.remove(path) { + if let Some(active_editor) = item.downcast::() { + active_editor + .downgrade() + .update(&mut cx, |editor, cx| { + let snapshot = + editor.snapshot(cx).display_snapshot; + let point = snapshot + .buffer_snapshot + .clip_point(point, Bias::Left); + editor.change_selections( + Some(Autoscroll::center()), + cx, + |s| s.select_ranges([point..point]), + ); + }) + .log_err(); + } + } + + let released = oneshot::channel(); + cx.update(|cx| { + item.on_release( + cx, + Box::new(move |_| { + let _ = released.0.send(()); + }), + ) + .detach(); + }); + item_release_futures.push(released.1); + } + Some(Err(err)) => { + responses + .send(CliResponse::Stderr { + message: format!("error opening {:?}: {}", path, err), + }) + .log_err(); + errored = true; + } + None => {} + } + } + + if wait { + let background = cx.background(); + let wait = async move { + if paths.is_empty() { + let (done_tx, done_rx) = oneshot::channel(); + if let Some(workspace) = workspace.upgrade(&cx) { + let _subscription = cx.update(|cx| { + cx.observe_release(&workspace, move |_, _| { + let _ = done_tx.send(()); + }) + }); + drop(workspace); + let _ = done_rx.await; + } + } else { + let _ = + futures::future::try_join_all(item_release_futures).await; + }; + } + .fuse(); + futures::pin_mut!(wait); + + loop { + // Repeatedly check if CLI is still open to avoid wasting resources + // waiting for files or workspaces to close. + let mut timer = background.timer(Duration::from_secs(1)).fuse(); + futures::select_biased! { + _ = wait => break, + _ = timer => { + if responses.send(CliResponse::Ping).is_err() { + break; + } + } + } + } + } + } + Err(error) => { + errored = true; + responses + .send(CliResponse::Stderr { + message: format!("error opening {:?}: {}", paths, error), + }) + .log_err(); + } + } + + responses + .send(CliResponse::Exit { + status: i32::from(errored), + }) + .log_err(); + } + } + } +} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 4e9a34c2699ab977eca0ed07b4784cc52f55922d..c2a218acae9b6d72098135e402c3aac6fa6cf6e7 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2,6 +2,7 @@ pub mod assets; pub mod languages; pub mod menus; pub mod only_instance; +pub mod open_listener; #[cfg(any(test, feature = "test-support"))] pub mod test; @@ -28,6 +29,7 @@ use gpui::{ AppContext, AsyncAppContext, Task, ViewContext, WeakViewHandle, }; pub use lsp; +use open_listener::OpenListener; pub use project; use project_panel::ProjectPanel; use quick_action_bar::QuickActionBar; @@ -87,6 +89,10 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { }, ); cx.add_global_action(quit); + cx.add_global_action(move |action: &OpenZedURL, cx| { + cx.global::>() + .open_urls(vec![action.url.clone()]) + }); cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { theme::adjust_font_size(cx, |size| *size += 1.0) From c12f0d26978420479c47c6e2e35463323aa88c75 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Oct 2023 15:33:59 -0600 Subject: [PATCH 157/180] Provisioning profiles for stable and preview --- .../{ => dev}/embedded.provisionprofile | Bin .../preview/embedded.provisionprofile | Bin 0 -> 12478 bytes ...able_Provisioning_Profile.provisionprofile | Bin 0 -> 12441 bytes script/bundle | 27 +++++++++++++----- 4 files changed, 20 insertions(+), 7 deletions(-) rename crates/zed/contents/{ => dev}/embedded.provisionprofile (100%) create mode 100644 crates/zed/contents/preview/embedded.provisionprofile create mode 100644 crates/zed/contents/stable/Zed_Stable_Provisioning_Profile.provisionprofile diff --git a/crates/zed/contents/embedded.provisionprofile b/crates/zed/contents/dev/embedded.provisionprofile similarity index 100% rename from crates/zed/contents/embedded.provisionprofile rename to crates/zed/contents/dev/embedded.provisionprofile diff --git a/crates/zed/contents/preview/embedded.provisionprofile b/crates/zed/contents/preview/embedded.provisionprofile new file mode 100644 index 0000000000000000000000000000000000000000..6eea317c373c93336526bb5403001254622237bd GIT binary patch literal 12478 zcmdUV36v9Mwm;qMy9n+ApmQE$9ByP|uj)3dn z3L=UNBkt?CF>X(B14Us35oZK-6dV=xF$^yMTS>1VGw*-SJM-S_bLw>JtFON0e)s;Bv8iJeA|;wE2DJ4rANdbH~h{LAY>V*i9-5 zLP{(t+jGgm)XB=dI%D~) z+)-4zi}?aqEY%z^SO>h&885}#V4CLWgO&rW@l>odcg*P78Ll=LrlSZao8@LJUE>}t=Qd|-GCQ{YDQP^UU9XK4#=yZXI z!x>=)j;8GECQptRxyGObTk2m<9B$^S5w)g6g>u|e0AVer8XNNHXm41u zp0lHx4_I<0f9MvG)6Y&#CgfigfJ;; z@$=rGF6~H@4jRogo?%HE4+Ln*nv6N=G%2}!q(rBwI!(~JV52Q8w^e)Ew81ovocaR8Q3e0XH*`iFAxl7s;;0wRy_oUW4tqfp)~D6JVHDh5ThDQh@~)_ z2ge%wV1v$N+#*z>={z6I1~MRza>N>_$E~(PBt(nyeYmFO?c|`b3$%xpNTx?7Xu;|C z`Bed-K>LJ(hxQ83s07dLXdcd;A)JYl}ik2rn|UUK9mgbK|ET_r&C#X%^V1nY_*KZ)n1A@eHA8eBBSYWDds>qlP|7R zsnwV-o5)qOMF%PgU4cN_W{z+gl+)Vc4oQk&Dvu#mBWt0wI-5z07KF4~<7yR4mLhDe zLe;Hi0>`W+YdT%WRP|5}F;js^&|QmKi9&?3Ib+pC$ma{XC{-yC60)3RucJJTp{OOz z^OO&44JYa-sG2ii^+0>VNTO*J8jP~;#wm#j(-@1I&9RVyb+|RwS~%|^c%?&cb$IJF zZ8h$vNz{VNw#WF*m_%Y2!GM0F62|jIj3><)6O+(zIKx^!NQ}r=9BNZKra{Z$roEKI zYE!AFO`>b6Oe7R3L8O>$*n8jR+TQht(u!l`$eE+kZ?W_wyuUSqA0`)S@Ap z4})z`7H0+83k_S$(Xf^<5B4HB@I%%jMb$8#2G-Dg?-1;YS#8O%#shOU=Yx4lk)RHV z)wGh64^wt2jA4x5it=R8o=qZgE2?+YOq#eRc97f$;i3>8fQgPe8f@r=*Wb_!f9(UNAK@dt|*hGQ`jtxM6cCQx(4(vf725EFjCMDUGsbfb(_DH}G*a}6EC zQ%wvAs~HRGB}tNz+@L!(+K1}kl4wG{Fu+1M+kt%SVcSMX*YebgR5JXDw^c69KDZ#0FHysaH~XA1^@%pm1N z#K1+-6m7GFxVYWUGFYKftprs$N5V{29a^Jah0zgByy`dlbdo(EY_F%Y1|R8mur?ebjHc|2Swn#C_Hw+MW!h~A;kr^g!uXJgn#;BF{vr>W0r0qB!k7g1|F=xk0YO*Vjs6_^$5F4XsG-yek zG;tK?jVVoWPfVHhSzXnzE?wm5Ixv-nn+E#OOG0{>1VMT_NSU&6@ebfsiIgyij5IvE z(>pwlYofydyz} zDO?RGMxgN^San_Y)bJh#nnB}$KYqV69|<~(Fg|D8RQx_+%sr#)Sw9UW)+AfC{pgwRuQgG8**L zxT+vTaKOB5K+OgN8XC*cW;0fcY9fW08oY4MiA60{QbxJ3I#8D_g2oVGXw5P6FiVRVBk&s^)-aP9I%!21#AS z7te;%sDZ!&^7#l+i%9}~8pHyDF-a89@E%;L$$ETxmNf}oAzM4G$CHM#FXF=kRWZTl zv(aoxWi>|)SXkmYKOAGA49Jf}dy!g$V=5F_P87%LX$%K94*G@kqjSSM_OaCxJMg@D zAiM!m!+=ke;2O;#)S?cEy%^EvBLwX-1W z2E3Pvs#QrgsH+nw*#(QHDfQvs1+SBgdR2u8ctO}3=tH1(h#tcEo3kZ>3p(@Rpip)M z&1qThV>ML{&RP|-0;A{Qhz9LP+Xc8lx$0o!85$U&)s_bpMHvdmagA9?rM*>uIY6U% z@c7_CXsrp%ngAHDTIy(|ml2bhhW-eY*;a{a7@k2$-r>cpbe+u7ICvb07H~<5_cC`z z9jG<;(ZiTi7#0tC@_+{gU<6gNj(VD7=Yfg$jNN3{ajsu_aq9uxq)&Ct)N<57+7#{<6 zm3Fxq3d4ARk!Ki`;+-iJ^@jCcqlvNFGlmGAiseb4)rl}d!fMVVx~c|af@;Pf6nKAE zT^^4ha;eS3u~ zSJa}I>8hjd21Ds#wwMnPTgF>d+k79`s!{@{08u8EZm^r4eFH<0oOzVM1T+mUs6B^O zu?Pp|i&&hwl0oSa2_Ca}4e5lK345xH(xl6E(Rnsftcw((vei`?V+1K%iixfi9YE7q zDU#1KwG!i0kxFOUPq&w-Lb~8{mbGdPYc|9)R%6m<{$8CP#0n3Mmir?^JJ@^g*tTevF4K%C;mvt1q+|>D>nI04r|PT& zt4mYzRj1Q77TVyy%=Ezcds&{98n7O^e?4ZW4p_Ai^qUy zk*HngRapp2N)zQva5_dtFlueZG;6S?6NHttX}WUhe9qLRiQ%+`tW$2Xpj2k9beFbJ zvoNN3uH-2=Dcqr9+rzlsV~qRt@puiN;T}*?olYlGm_EMj( z!?2uz*L8`}0Ax%C`8u5bUu-MQOc&?_LDs;wYrr#mGF>sr*~osuGl=ejY0O1?^OC)p zc_Rqi4DqMO`_FhGWp)-KYF#0kjs5@mV{=9#rLa}b9M;naa0!T#o4>&)#0Z8*NuNW7 zSL>WDB$@RzuF**rvXbjkt1v(BQkA;`dcout!9)uJlZud5qDYj25sM*J7Bk*ZUG1QJ zsLIvlateI8j@zkVK9j5I^e!Cr8{u5dkP(y`ey7KVaT-Mrv0pC3>0MMlU?cgIM^v|` ztN9F9XFxqO5+kp-?4gi`6f)%!p4SPZ=cHoD8npWDxpa()8*BkGnX>uKF0&BAD&{CDhDy;|4%b$J^M>RM7$RrDeOUYd zVta>F{Xm?8%x)03cu})}N^(r^hh(m`(8y-@XMAmF9WwX5Sv&~=A4*DAbYN~EW2PA+ z@MYi&KC}v)T??_Bi+pj$nd)2Lj4{h)rxl1gE-UD5)liyivh%S*WIvzs_2^HkDP$INJoL^E1H$3g`u z970`g-mEIB-DQ&P%IGlB5SDU@lFDwfn1X6AqQxnT%b&9P!i2`s?oK&szEZ*!swd1s zQp;)>ja5qdxri-N!e}qmm)#lN|2GDHHfmmOH-jg+Sf;OZ(_7~0$pDeCAPDucP}=G* z@AMSSU<`Sj=0H$?>#4-WdrL!|tSIJVzaVJG!U{U;T)Y$Nc96C@xNbLS8mP6!lCZK4HWuSVv4bz>%Y^~U?5kwK zHhU_6111`@`o83uER%mOm=eck|7&|0C>}XKH?UqwRtu@(VXK5%m0Tr6jEAcd{)<8< z6g=}4h*R=8sDR2h&E((U>Stdc5fua|38p}Xy$3V9R4n(`3w^NT{sL)VpFZ&(D%=4( z@0V;tXXUXDw|XiUFUxD>#4{9BF4j446uGYIOJzA<-jS;GCo)sH6327Jxq6)enXT-- z>(PI|pCbBJ+p~E+qS9#+dP9ea)#*F5MxCyMjYicSI*m@F*0Cnekcb{UZ=hKQ?;EOj z8r6Uqdrx<^vQev^yQ+J_y-4>kKhiyP&Z?oUEv>Cn&boNPn7_T(ddFvbKD=q`8~2~K z;@i|RWPD%oy=5#^Qv-pxpC~|sm;dz zDlQzj0xg%4`C_V6Qyq;=k^2puJigh_lh2nFIEqX=X1p3vsa1NFNn_OMBgc%_$hS!I z58`AMfnbd{$DP%DGng+5aS7~K(N{WFcsQt3!-S&ZaP!Nb8OZA#*J{!A?PT1*o%Hl* z>=o&5X&Wewmf=H&cDGD~4-{I)w05_&47vThZPO3@h+4ueJ)8@t} zC2swC-nzBg=kGgXMsCZrWiu{6qx9m0BiQ?9$7~ndcCIgP+4$0mQPvC6PoMtC>xrM% zFFyL+M~BVserAF2$uDDyyYK(`th{yI$|aNN8!meEf@_tnCpv$m*8M5}glk>()onW- zef#yTiAiTgz8Fo4SGwPKev%x1@wV~`d&`5{c5cj;=pEj7HXm>7KJ%U1cAj_o1&=KJ z^7NTw56r)C(UAwfiMmF7?s#|l*6hl!#BJ3X<8Lc$H3zRfpTF!yQ5v%*8h!G?!lGx( zCtdkK?LR-e>;0cDJykvCtdp-lS8>ORca;l{UH;MRv7bNj)1C#viN~xwyZwrp&+cm- z+A^eN^}?@_g?o@AL9kONx3v5+Y!osA{6E9LK5Qm(3Ubn_8LOr*n%eVftSAh4Q|{dfO~_Qa|Ab*j51Vq_lAErwoy%5( z-{1Wk-I0q;qjo-AMkdIQC(1h+239fxIby_^#y2am_=wh)VXa^igDnJfqGd?r72Ta% zKO3{sbLWumHtFr6^4k4v4^27ct^3Ac^RIvD`SR4=jyI0_^y17pN6b5LE?FA?P5q+Z zUbkUKYzcPdrei-w9}BGbACH!S;Z2vyyvEsbiCxMO6J$u>H z#z^(SNmkFZ<8@1x?RcwnYw`zb{<8b`u6gXJMc2Ob7J@H&@6$pn^Z3FS*4*^|mtQ`- zbo4o28-Mrc+igR4^EkJj1YmgK%4qn%4@G1hQtQ+zEuv|lhz?OBrk>k> z3zGdS$k=t=??+F+?!##o_wM0e&8uD3v0Hy<(Ba>+wEp$jxqo+$ ze(w@=&CUDYeIv2r=e~cmLG$J-6X^|8Qt!TgT=9qV zCTzas{N;O38TyR>8v4BP-^O((9c4c%JoT|<`~Gvq%8l-4UJSYUYeqe;*!SlBXDIT8 zC(mv{mOh1VoOs)u@7k%;j$i-th9{n$uyx*vTi%@W#F-C#KXJm4>&HGa0;dq+X`C&_6(z2ig%@&hMJ%6-r<%SkpHP%V?uyk>6c%6`3bko zu^9ILe)&09pYi@9*B<%7y6?s1f7=wF_-4(e{ou~I`>VfX){oqCk?GPUUd4OHS04G} zt*$YdU)p~R-#dKtg~yx|m^7{Q-cw6XI?J+pbN=K{cE9^n;p2(qUuM4XZMOQ==T97S z%$n^Nx9Q$EW%JgFTQcw8dGbk<_T6#+HR8hguby!3Xyx^f{++OXGvB!Ks2}E>$}1LY z()-4DZ``qawQ|2VeQx}Pm`2)n=EySwySGi8F=5*?;&HAgw(2i=@e2RedvAGKjUD)4 zbJxm?@9;cz!t=kwPCjzhYZIqFNw1g}TW?=*jQhUDo44)y@tx5l4G)*gGm!4#O8~;e zCVU-n6r9SjubyjITf4sV*37!|2Z~JQME^s4M5ak<#E7WXDwPp20!>ou5u*l}65JXh zhzGt;JOlK7*wAsO&}>ZM@+v~VO1u@`6fYC;{{{}U_tf)3qX1Aeznx&%aqWm*-P0eM zxoPORp7b+UzC7#u6TA0!dzVZexik9VOYd&i44Kh+`fK=|o3M|jUNmaN?!alw)~&|w zKOr|~!^9gupQ$_j?#Wkt`og}J)27W$uKI$q;g9|0II z@~##4bgaw0KI#wD+~b~GaKh8u7md+wdFg{upFR1_;G(8aQ}O?4684+HF^DU7KEJe0AfZm-l1# z@7BNA18A*Aq`Os%jOzoiR%9^$`Bkb>=!FyDKXD|AboAaMt&3*#k{B>(aR7~ha%7R$ zK~P#mRDIoB7M=1>=~DA0@Q&Rr4fZ1g;uN?!0D3h$-N~sXtH$4B zJhHuhy!+EO`&VmTctO$LzU$4<**{HK^YHv#w;V`5F!Sqocg}uz=_!k6-8lEJUn|Cb zGWq*8x=+U3wtVTlz0a)~G5SAF`_tAp|KPb~`U@X%S%v1t6NX>j(H*$1@SA(h!Oj~W zd*#6m3vSc39pClG3$)X>T@t;f?d017V@*fTd*S9$$1VKgy{9i+T=?kjpH$0)d#tv6kd)%~Qn(wOD|JLyze_gsX`+~u@v-;-RD=uB+-TJXTe%Wb+`uEp8ycG$HSj|Xw@?_@7^R;&a=&$JXO`*dM((~Qus6+ zS$O6D5ZtyN>IV8hQeU-jJ92Dapgel0YHZ)l$QIpnko-TU%R)EC5f|f8TNrGP3#E{m8-Xrww#?Fa@B+kk5x}Zul8} z^X6wSoWJVoyFa_|%>BQ)(OBFu>-j5QU-Ry|z`{OVBK?0sv0{_eT#iJ!ds!|F@#B{oVeFaLP+XzzumZ`kv| z&X=y78!-Q)>*w~beoOB>YVQXR{WLA|$Mv~sTh>0aeG+@oPd`t+?B%(aZT!}>X7?rA zF4+HQXv15Ii@V&*zx(u*U01EYb?)Z*H}9L|U$P a*7NeWFDQ&(klM8L$X$DlFNp3R9ByoeQ zVg;;Akyb%OQA^zw(c(hg_XS0)fYR2jRj?@4QcA7wGs#^+TK{<8{(kTMeCBiWJoD^x z&i8!JISb&zSQbto23dLkDJHJgg7i!Z8Sw0s_X2tn!Ou*J*X*++xn0eDNFB*vW za7{jwEXo=mgSZJ))7GKY!ufng&}y*&rt!H6e?SAq&}wl{o2D%x%lUJ)T18Rja%!u& zd`?mu7PY=YE-w`1sv8W}0Xoc$$+0#tO=I*y%K^2qBrh)*GkRWHs4f805txUQgn3%E z2x^OR0W5X_E5zn$dw%z~%_MnF23sRyLRL=3lS08)5aP*7UvI8Z;06vuQYKT7b$eK9 z;Aq;uX6mG9L8uRkadQ7MVxX`<59tjZI;cZu2KxT=L*s> z3+UU3*B?Iq)I>21K8`?gSx!_p+;awTE>je=y=R08Wg(*;Fbd8XY#JPaQ0!~lyOq)N zz`D2vBth6@3`G%VRfH7+fklFFCIqkD8`o!9Qo*Q*)0Jb1M49)%6z)bSSP=raOrk|Q ztYdIkQPQ+MsAof!GH(x72|R-nxF^GB={zfCQjsv7kLryg6%In2-CE_sw2Aab6ek?P zf)wS(5FNrhkRt0xFgEOjxDcC<=xyM?wM5`HUCa(4x9Paqfyr7PZ02CvWG8JR2s9YB^FhiY|Q6*m;sB6`blh6FBc-0*o%0 z$&-Oho+fEXu`Bg=E)1bLHbklWPRGI?2%JrZuPVnq2T{b6Bn;V6f0x})L=6~)BQV9l zu#rG81y;a&7n}p%ox(hE$;M?xA{+6=1YXQR5!&Pp#**$zI^O94`i}-gC{*b(paE^n zmC}b~9T7!|G)HGpL7Owuk**+TuXIs)%8Y8=SeCbB&Hik`U`%B>!EZAQj-oe17mJ)R zjlc#dNasUtH(4}h@^UWb*T<`wY&>O&k=9zqCfA@^*;+*ma>1ofcywsMU9E(bn!z1r zttFX)ZDg0g=fa7g7(%0kTq-H~soz;w{#w;oi7_M& z+fh~bD8_~;ID%jlkZ)K)M6rN~xDBCr1&&10oP&mVELV2xtts9Bmm&>)DMj>Fxu;Gc ztGYBBX7elt>%rO(hbn(GBqHE9*eMFrb@>46O?M^INxN0=Nh1`lh;gGL^5lSZ5GYC5 z^%!BVF+eA{kPXbkh^pQL{mt`LBsEaCfvzV7Kaz-M(;{!TRb!5{7zul7v5*ePGLnes z0e(2>L{#!*2)k7x9CQx!0r(Lt5q6ux+bb$YBvt+A4Qe|&!AlgFD_D(&NihO+gRpzb zpgv#M#ViTd5)pyEWZdX6Yf++lFrERdq0#Rc?BX5HM8rUYIUDnVd1N+Zg7~UY@rV(^ zr9=>fVjQrDhg?zuiaB7jyJ|JW4E#~&J_r{xkpYVp6*1mAXGTMBB(s>r}cV+pD>umq;ykhx&Wb#-P0R}k_k zH7H~1%*(i>OL*+&vcKj`8x)ttlE{&E-e)#u@uU_j`f#G1;O%nM>hX&Cpj)3P6&w&! zPT)SB-|4AJon|b^&`Ju02+3E`IuMMrrH};f#{AJFjEl~8)Fyy7-m%sPZ*4Y3Mi378aDF4SI2Nu2@Q=jN)EAENVRw3N2l z{g$dBqwVy_)?xulOJ>4XtWk!Nhj)Yly1PoTibSqn4$nJ84r z$0P=Y6H*tj@ah;vz#62e!6}n41BSH*LKqytV3U*AB~np6oe8K26N+V67+5n5W`R|t zc*&~g!XCgjOf@Q0DXOdle6I`-P*MxD2ZJjZF9jRcIZcEAOc)CQW)OfYWDPfZ0!)?E zqV6c%4h&m6LBb0(g3{Eg?$R_uk1M_d#fkIG9m~lAEfI$^mhRy?&t8yVHU_*de zWM~s_XGkPXJDGG43u15+FC5s2Gtd8N!BCqXst5>p?#WFp(;-50X{l zLX`|)aHJjTL(@9r@`vb5w87jmd_Gj9T(^jQ3{~a^?p;(ugTA%j1T1BwBmmiUXYv`ahXRJOgT`YA zuwb`jtMS0{0!s)1wkET7s~ibsO1z>Vt`rbN)N}m4SvnI5K}WCWXxYnQ2wPyonG|QY z<)h%N>sHf1%B;azj)jpL3B(T@%rZ-^E)*z#5wJcdrds?e2ee1TQCJk^O1w%y#h^PA zs9PRj@l#$O1*}I6$&z$Yu{YQ$h9S;g#_o^mtq@@Q2!TQ(T_Y>b6r64_E_S z?$^b&Ok7SmlVLuaPuOH1iCgH9h*?86k<7;#pBsl9bj737=Y?=aZ>fa6P$3&Gie78d z6!c^SiKEO6m8;|+z&!z=t7~@jFpN?sCgg%*E$Qq^C-YgMTrd<0)`HvVDk_-jjnGh_ z!7v!$N7OOa+vsnpvjr9U!9EpKWk`*E18U5sbV-}k?AhDmb1gYY_34$R7P$}0ox|&`@COL zF_hZfR+2-FN3UIBYj_j@Z(UY^=j-p46-Kg_V<_O_e$`6ywf~xj8{k#%wpxE`M78eW z)(B9C1Lbcpx=;$K4p!>aecn3I4Pe9m-|K}ft>DTaz37z{ z45oOD0=j`nrF2lTTC>$ST}yG^bV3f*f|Q=c3(kx^$+j2MV!Y(mg54lW2#2$sLmQa@$vDQ$;iow3R%=UO=NC4m=V9m#PWbTq; z=(0sfW0K1gU96!1sGnKORjd92iAM%_LaIlWQ@p^V8o=IFkF4uQdSr=uG$gEI6rGBM zfGlBr-8*zBz|#XV>2|?gITT2Azo(JnC8gi4)jhlh9syflj@dm4wnmmB24|5(p!!&t zNQDrDHq)IsV=}>{{ixGki<*7*B;DzO$u2Gc|DY}d40AQ2L9rAIM^K!iP&FcSw2rd? z5m=Z|Y*aO$DsttFlquv*THt_DSSVC2F)}Sya)HCM^SC(^z!+sSYNKb*Ac%MzSS#~nd$Hq6Ujx80SGwpYX~ z?@w!kP9J8e76@SLNV}@%g??K{vF%1LSFr0MsiSP&|7Kl5j19huQ6PE+bUP*LyuqAW zXCN^K@J%(!#VDjLrvy@X7m13!IF$(MVmOnmbH84WoR3Pp*vB#bNPr8)N?gbUY<*uO z>POgPgjKyoqzXJs(r5)-0_Z}|Zv}V25CJ22)k+n4Lo#SVgcK~d^GL#NGRra*BrrMJ zl@apsK&QRHxmbpfXhbn9ok58%84Cp`p0QX#Kt}Ndx>6}N;w~!5q^-;8w3Gx8QL*_{ z4&(nZ5fw+?QA#CUHa|@e>8Piivv5vF5rV7vJc+Ao9vD#pX9&2(QTm(t(fX?goH77( z72r#z;faHYirot$ByR8!M@LhH8e$qS7Ybxv#_Te~*)uf|#TXck0MQUo=b+1k-I*ak zR8frtvSHv8f#00Ur$Hozm8@05&gww)q|WIVdBm;;!1Ph=HDJ<1V%5b)Sw&7B&|4KOi;gm|mF7DSD1pDAj@?MPS6QmeIF zemMF7Tl)M-5PcwV<%e*Cz-WLQG)##8jLuqdvhD)ali_ zN+ciZ6ulM*CG1JeWO3;#x`YnR8x6Qas0L{wk_;k&1RGSMl~BcP>kL`)N(uO`h+g$? zV|D*miT^LwcfiR9;?%Kzx(h|j0XWhpPYQ&f6j?zB`}_!rD1B!SqQ6cA1Uvv=t^(ex z0ACGoXhuh-JRFJU2DJnBKSm;e>wyeFgkT9c#9#>Urw|HF490sxaSxg^jgx)?C$Orm zLRT$aPOkvjqK-r*$h`;Cs84X80Vm0VJo+Dwf*B+cwc`Aa73M~=| z*a?*d$f0-I+EY@+PwTwKTDGc3-BB5-w9^(?%w)U;tmu_vAvlS{WkqMm!+M7g10dC2 zkZ6;(Ogby=Wz67)p^%5-ize6&21CgCeUY5e{eNS?&w5_V=L4rI@aeuZO>au4C)$I9 z^py;uyp86IzxvM;^G~g(F%3&vAEF^k6KN- z=rO6_U?aobH&pL5x&brxe%yK5dK!AchVBW!g1Uz>Q1{UJ8-_MFH8)Q>@6x4X{<5R_ zw$JwO`NghR?>&3n*U8n;_`bY)(^!ybkJj_3t}h6Z$7Yi+damo?w&I}``#I=r(ZcoerCc6+}-nd=cUe%UoO4(*0bwI zIW9_VJ?EiU;@`czV)EM$51ZHh_|nWr-;XKmyZ2w`nc z+wXE)yxS@-?%wO&A4Ha=C*ZjYnPt7_QQE&|Fq@1{YyhF zr>sA}{i<0{9BLlgG^AfKdSA!*kGFb(+>;v!I#KnH#2WXk9+7rz>B`4Ctx0o(rm=X=?ik!zK@# zG-c&4u5m8nDxq)g{E6wL71mK7KUjh$sIObpoeTq7G6Fhb#F+YZ6h1bhxoKE4P>I1B z0%W3T2>ZP0_FbQiSx?_Sq`OUdqoBR+aN7fuX1;#+7-Y!}&puU}w$J_Q$)8@DUU*r~B@>Z0hIhE`M+J zh3=?zh3Ec>*Ia`B=7YueZaa11!OvReTrlzHUtc6_dN$$M`~4$p){Z-(H?Lnm{>BR} zV^*$t=cLSIE64uqcMJCCn(ls+#Fy;3qAMQ#bcz7IvEqB&elTTc+)xuWpKhZ%yG2XOt_1@Rzn-kv>OIF`|aMR|KmtVL2bqHO)FTQwi)#!x>EI)tvjkcltL{xYg2f*?K0G5pnU}-U9(HBE9eGb1 z)cogD7yR8fddFq(re7X@`_=fm8R-+ZZ+zmD(`P@|dgJ=qxI64C7ri^V)9~8!E##Y% zl5f8zL`xlU*lArfpt*=x^)RzvX-UnXpg1cGM%9 zL$BRCN0ZAxdVUkM>M``KmRsikwVgQow3q+&=9aA!b}gRz-fQ!>oO|ClEfa>^F!rJ8 zljq7i=Z)VJoqW&6t{d!r=9vey@aDfC`8fZ^XB*x4rLFF?Zw-9)^Kx|T375lH8CEQN z2nz3Khv`=(HeWk!uTcD6Tr50EykEZ6FiE&gS^Y{!12VdsY5+vb$EaG1s(N)084e8v z8aO~ze-JFIjjB|s2;RoHLQEqI1qpP>#x#5`8%s89TQ06C6@@v2Cd%hP zUv)CjvTuIX^yUj3F9RB-jwv_T_S~aXvkqTBG2i6ws66)NsF=IVlA&#y~g)%UvRkcefs5*`!BIxzS6JR zVR`<(cQ%H}1^M_-NnTkL7>gg8y;Wt6xi% zFaNaVlv6f+aA}+A)tNhYwY-;p_x2fQo_OfCd#^1nTk_)Ri$-g2c=+#_^(TKj z|143n!jL*NzWc4c`!;G1`%{ZzPxA)l(77Y$1o!Q3nLc6nsL2z z)gwpV-`TbP(%b0APJim>$c&R_ztl4AQF7g4{${T z;*;*y2WGu9bP=6;{Oadse>1iFaJPTu#E~CI_dNUd2ZkZj=brNtdiy)bhtn<@HDX`z z?A6;gBKMx2o&RRbnm^4lopa~Jt3G}DP}A9GEJ$qloN%I>|2X6HX@3~~_=Tx&*Kcm^ z`o~FuH%H#F?yin)*;hvWhFCD=$)%@n{b2bR<9pA(KkBnbznXaAo^K{S@!UVx(YC7RQKFB>AcQ)^KZTQ62_L_dt}GqukXA4{5xL47ddvnbZ6(v z+8^UPAA!*YjOI{IP@bHbyzgM%IjvUBRzKc4X3uR8pLNduKj_=Px@Y$_yY5M?w!HY( z^5+gCuD`zgOb?(nTfiMvBQ&lLz?z}K{O1R$M!pwL0RM?WQK+N03^gyG-b-SDLKg?n z7?9B`h$i5R79d?;^QPr9|1Di=bOP3~yQ$88R6v{r3MYVP+#o>w*U=z1?*C7aGxwei zfc4ow4D0<+DR8|m3IwTNuyxou5TKJA0G(Xk+WQ>_k;$H~FqlrR*biSh#U9XJRTaP9 zJNlbRZQBKJN+%#hJ3upgs*K5C_FKd1JLqXCQ)|@{4%8u^f_4%Lt z$`+ctX7ls+zq#}lQ`>1>zrD!Vy8E)|wQVzQ4UV-=Ui|bgM@?Dw`Hrm@ugHIR=Xbi* znY$eO4w5$?*t8*3az0{RcShivSN_uR!k<^IlAi7id|Y|$o~tfj?%(x$SL}+jG5xQv z-}>f~{n;}{4L|?lE5_ZVc}l@&eGYGYN&MNiyMI6Y(FZm>KI_hRl=216*%PPfx|^>9 z`m_q%`i7QW{XYb^&BvO7{`=<}mVE%7+UF>b9;zGLR~Xp@P38*umiwgLVJ z@JS)MziEpOhfrwDh*6CT^g~Ai@$3EF+}u25DX?Vy<)($u$i|KOk%Q~c9%%5u6aX!T zZ0$M}yYaO7uWhn0w@lb|?UtVp{dHs%G@kExb?u2?zOeA|8Gm_e?p3DZCD(NPwEIlY z_^GXXcC4A=W)7^KIivf1^q%Wq^v_u8tIXPd&AzcQL(|tCBS%bvhrIK}x{XVBv|RgL z_LYm{vkrXz-HMZDe{{NM{XMQL=NYem;mFv(z4pPDU$#VBXTCG9>+th;=^r+K|Bs8N zJ{e3hD6#NcdHJNPY`cg2c254gr|w-v&3^m)=hwgd5pG(j``sV+jh!-b!Tpb&*T!sX zdFqCBMs&@&$-w$e-%j0g_Vd?Y$XN^fjIV!w%`Mkm@kOQd(F;2t{bcJ{WyCGIOWz-A cUAg3u$o2~kcb@V%dGX4G?XS|brIU642YUB#a{vGU literal 0 HcmV?d00001 diff --git a/script/bundle b/script/bundle index dc5022bea502da7b8af349d5fe840a931fbf8faa..4775e15837bf2bf8bd53ceb65e7f1ae1353dd3d0 100755 --- a/script/bundle +++ b/script/bundle @@ -134,7 +134,7 @@ else cp -R target/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" fi -cp crates/zed/contents/embedded.provisionprofile "${app_path}/Contents/" +cp crates/zed/contents/$channel/embedded.provisionprofile "${app_path}/Contents/" if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then echo "Signing bundle with Apple-issued certificate" @@ -147,17 +147,30 @@ if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTAR security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CERTIFICATE_PASSWORD" zed.keychain # sequence of codesign commands modeled after this example: https://developer.apple.com/forums/thread/701514 - /usr/bin/codesign --force --timestamp --sign "Zed Industries, Inc." "${app_path}/Contents/Frameworks" -v + /usr/bin/codesign --force --timestamp --sign "Zed Industries, Inc." "${app_path}/Contents/Frameworks/WebRTC.framework" -v /usr/bin/codesign --force --timestamp --options runtime --sign "Zed Industries, Inc." "${app_path}/Contents/MacOS/cli" -v - /usr/bin/codesign --force --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "Zed Industries, Inc." "${app_path}/Contents/MacOS/zed" -v + /usr/bin/codesign --force --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "Zed Industries, Inc." "${app_path}" -v security default-keychain -s login.keychain else echo "One or more of the following variables are missing: MACOS_CERTIFICATE, MACOS_CERTIFICATE_PASSWORD, APPLE_NOTARIZATION_USERNAME, APPLE_NOTARIZATION_PASSWORD" - echo "Performing an ad-hoc signature, but this bundle should not be distributed" - echo "If you see 'The application cannot be opened for an unexpected reason,' you likely don't have the necessary entitlements to run the application in your signing keychain" - echo "You will need to download a new signing key from developer.apple.com, add it to keychain, and export MACOS_SIGNING_KEY=" - codesign --force --deep --entitlements crates/zed/resources/zed.entitlements --sign ${MACOS_SIGNING_KEY:- -} "${app_path}" -v + if [[ "$local_only" = false ]]; then + echo "To create a self-signed local build use ./scripts/build.sh -ldf" + exit 1 + fi + + echo "====== WARNING ======" + echo "This bundle is being signed without all entitlements, some features (e.g. universal links) will not work" + echo "====== WARNING ======" + + # NOTE: if you need to test universal links you have a few paths forward: + # - create a PR and tag it with the `run-build-dmg` label, and download the .dmg file from there. + # - get a signing key for the MQ55VZLNZQ team from Nathan. + # - create your own signing key, and update references to MQ55VZLNZQ to your own team ID + # then comment out this line. + cat crates/zed/resources/zed.entitlements | sed '/com.apple.developer.associated-domains/,+1d' > "${app_path}/Contents/Resources/zed.entitlements" + + codesign --force --deep --entitlements "${app_path}/Contents/Resources/zed.entitlements" --sign ${MACOS_SIGNING_KEY:- -} "${app_path}" -v fi if [[ "$target_dir" = "debug" && "$local_only" = false ]]; then From 162f6257165942371cf2ee13b8b12d4594cfb74f Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 17 Oct 2023 02:16:17 -0700 Subject: [PATCH 158/180] Adjust chat permisisons to allow deletion for channel admins --- crates/collab/src/db/queries/messages.rs | 16 +++++++++++++++- crates/collab_ui/src/chat_panel.rs | 20 +++++++++++++------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/crates/collab/src/db/queries/messages.rs b/crates/collab/src/db/queries/messages.rs index a48d425d90ee6021dd13f3c06febba15beec041f..aee67ec9436d91ab41acca12e1029cefd098dfc3 100644 --- a/crates/collab/src/db/queries/messages.rs +++ b/crates/collab/src/db/queries/messages.rs @@ -337,8 +337,22 @@ impl Database { .filter(channel_message::Column::SenderId.eq(user_id)) .exec(&*tx) .await?; + if result.rows_affected == 0 { - Err(anyhow!("no such message"))?; + if self + .check_user_is_channel_admin(channel_id, user_id, &*tx) + .await + .is_ok() + { + let result = channel_message::Entity::delete_by_id(message_id) + .exec(&*tx) + .await?; + if result.rows_affected == 0 { + Err(anyhow!("no such message"))?; + } + } else { + Err(anyhow!("operation could not be completed"))?; + } } Ok(participant_connection_ids) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 1a17b48f19c303d9cd26915ff331c2fcc340cc89..a8c4006cb8ce7c18e3a2eaa09eb1dafc98becd26 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -355,8 +355,12 @@ impl ChatPanel { } fn render_message(&mut self, ix: usize, cx: &mut ViewContext) -> AnyElement { - let (message, is_continuation, is_last) = { + let (message, is_continuation, is_last, is_admin) = { let active_chat = self.active_chat.as_ref().unwrap().0.read(cx); + let is_admin = self + .channel_store + .read(cx) + .is_user_admin(active_chat.channel().id); let last_message = active_chat.message(ix.saturating_sub(1)); let this_message = active_chat.message(ix); let is_continuation = last_message.id != this_message.id @@ -366,6 +370,7 @@ impl ChatPanel { active_chat.message(ix).clone(), is_continuation, active_chat.message_count() == ix + 1, + is_admin, ) }; @@ -386,12 +391,13 @@ impl ChatPanel { }; let belongs_to_user = Some(message.sender.id) == self.client.user_id(); - let message_id_to_remove = - if let (ChannelMessageId::Saved(id), true) = (message.id, belongs_to_user) { - Some(id) - } else { - None - }; + let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) = + (message.id, belongs_to_user || is_admin) + { + Some(id) + } else { + None + }; enum MessageBackgroundHighlight {} MouseEventHandler::new::(ix, cx, |state, cx| { From a81484f13ff56f519fd98291c11c3ef20ddfd48a Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 17 Oct 2023 02:22:34 -0700 Subject: [PATCH 159/180] Update IDs on interactive elements in LSP log viewer --- crates/language_tools/src/lsp_log.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index faed37a97cfd5bf9d3a15c218b82c9f2be960ef4..383ca94851409cfd28530c62e3d786e1b1c72061 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -685,6 +685,7 @@ impl View for LspLogToolbarItemView { }); let server_selected = current_server.is_some(); + enum LspLogScroll {} enum Menu {} let lsp_menu = Stack::new() .with_child(Self::render_language_server_menu_header( @@ -697,7 +698,7 @@ impl View for LspLogToolbarItemView { Overlay::new( MouseEventHandler::new::(0, cx, move |_, cx| { Flex::column() - .scrollable::(0, None, cx) + .scrollable::(0, None, cx) .with_children(menu_rows.into_iter().map(|row| { Self::render_language_server_menu_item( row.server_id, @@ -876,6 +877,7 @@ impl LspLogToolbarItemView { ) -> impl Element { enum ActivateLog {} enum ActivateRpcTrace {} + enum LanguageServerCheckbox {} Flex::column() .with_child({ @@ -921,7 +923,7 @@ impl LspLogToolbarItemView { .with_height(theme.toolbar_dropdown_menu.row_height), ) .with_child( - ui::checkbox_with_label::( + ui::checkbox_with_label::( Empty::new(), &theme.welcome.checkbox, rpc_trace_enabled, From 465d726bd4508490b993689073f08a764d178eca Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 17 Oct 2023 03:05:01 -0700 Subject: [PATCH 160/180] Minor adjustments --- .cargo/config.toml | 2 +- crates/channel/src/channel_store.rs | 1 - .../src/channel_store/channel_index.rs | 5 +- crates/collab/src/db/ids.rs | 9 +++ crates/collab/src/db/queries/channels.rs | 61 +++++++++---------- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index e22bdb0f2c70a1ffda714674253cc533e9e7c1d1..9da6b3be080072d89d16a199e2d60d527eeacd07 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] +rustflags = ["-C", "symbol-mangling-version=v0"] diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 57b183f7debeff01350a87a887373f5dfb43a894..3e8fbafb6ad0fce58d8232fedf0ac95b8cf1d625 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -972,7 +972,6 @@ impl ChannelStore { let mut all_user_ids = Vec::new(); let channel_participants = payload.channel_participants; - dbg!(&channel_participants); for entry in &channel_participants { for user_id in entry.participant_user_ids.iter() { if let Err(ix) = all_user_ids.binary_search(user_id) { diff --git a/crates/channel/src/channel_store/channel_index.rs b/crates/channel/src/channel_store/channel_index.rs index 7b54d5dcd904f8fa181a9ebcc20a0ff1e2c1e4d5..36379a3942be2f2df3a24b70eb8833aabc214af2 100644 --- a/crates/channel/src/channel_store/channel_index.rs +++ b/crates/channel/src/channel_store/channel_index.rs @@ -123,8 +123,9 @@ impl<'a> ChannelPathsInsertGuard<'a> { pub fn insert(&mut self, channel_proto: proto::Channel) { if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) { - Arc::make_mut(existing_channel).visibility = channel_proto.visibility(); - Arc::make_mut(existing_channel).name = channel_proto.name; + let existing_channel = Arc::make_mut(existing_channel); + existing_channel.visibility = channel_proto.visibility(); + existing_channel.name = channel_proto.name; } else { self.channels_by_id.insert( channel_proto.id, diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 970d66d4cbcf45b2dd559222fb46e8be93c723b9..38240fd4c418c63f25a3fe3253c5fdda42bb03e7 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -106,6 +106,15 @@ impl ChannelRole { Guest => false, } } + + pub fn max(&self, other: Self) -> Self { + match (self, other) { + (ChannelRole::Admin, _) | (_, ChannelRole::Admin) => ChannelRole::Admin, + (ChannelRole::Member, _) | (_, ChannelRole::Member) => ChannelRole::Member, + (ChannelRole::Banned, _) | (_, ChannelRole::Banned) => ChannelRole::Banned, + (ChannelRole::Guest, _) | (_, ChannelRole::Guest) => ChannelRole::Guest, + } + } } impl From for ChannelRole { diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index b10cbd14f18c701a9415bb2705f884e8c760594f..0dc197aa0b6e5a36e40d2ee0d50a2bace91f95c3 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -209,7 +209,7 @@ impl Database { let mut channels_to_remove: HashSet = HashSet::default(); channels_to_remove.insert(channel_id); - let graph = self.get_channel_descendants_2([channel_id], &*tx).await?; + let graph = self.get_channel_descendants([channel_id], &*tx).await?; for edge in graph.iter() { channels_to_remove.insert(ChannelId::from_proto(edge.channel_id)); } @@ -218,7 +218,7 @@ impl Database { let mut channels_to_keep = channel_path::Entity::find() .filter( channel_path::Column::ChannelId - .is_in(channels_to_remove.clone()) + .is_in(channels_to_remove.iter().copied()) .and( channel_path::Column::IdPath .not_like(&format!("%/{}/%", channel_id)), @@ -243,7 +243,7 @@ impl Database { .await?; channel::Entity::delete_many() - .filter(channel::Column::Id.is_in(channels_to_remove.clone())) + .filter(channel::Column::Id.is_in(channels_to_remove.iter().copied())) .exec(&*tx) .await?; @@ -484,7 +484,7 @@ impl Database { tx: &DatabaseTransaction, ) -> Result { let mut edges = self - .get_channel_descendants_2(channel_memberships.iter().map(|m| m.channel_id), &*tx) + .get_channel_descendants(channel_memberships.iter().map(|m| m.channel_id), &*tx) .await?; let mut role_for_channel: HashMap = HashMap::default(); @@ -515,7 +515,7 @@ impl Database { let mut channels_to_remove: HashSet = HashSet::default(); let mut rows = channel::Entity::find() - .filter(channel::Column::Id.is_in(role_for_channel.keys().cloned())) + .filter(channel::Column::Id.is_in(role_for_channel.keys().copied())) .stream(&*tx) .await?; @@ -877,7 +877,7 @@ impl Database { let channel_ids = self.get_channel_ancestors(channel_id, tx).await?; let rows = channel::Entity::find() - .filter(channel::Column::Id.is_in(channel_ids.clone())) + .filter(channel::Column::Id.is_in(channel_ids.iter().copied())) .filter(channel::Column::Visibility.eq(ChannelVisibility::Public)) .all(&*tx) .await?; @@ -928,40 +928,39 @@ impl Database { .stream(&*tx) .await?; - let mut is_admin = false; - let mut is_member = false; + let mut user_role: Option = None; + let max_role = |role| { + user_role + .map(|user_role| user_role.max(role)) + .get_or_insert(role); + }; + let mut is_participant = false; - let mut is_banned = false; let mut current_channel_visibility = None; // note these channels are not iterated in any particular order, // our current logic takes the highest permission available. while let Some(row) = rows.next().await { - let (ch_id, role, visibility): (ChannelId, ChannelRole, ChannelVisibility) = row?; + let (membership_channel, role, visibility): ( + ChannelId, + ChannelRole, + ChannelVisibility, + ) = row?; match role { - ChannelRole::Admin => is_admin = true, - ChannelRole::Member => is_member = true, - ChannelRole::Guest => { - if visibility == ChannelVisibility::Public { - is_participant = true - } + ChannelRole::Admin | ChannelRole::Member | ChannelRole::Banned => max_role(role), + ChannelRole::Guest if visibility == ChannelVisibility::Public => { + is_participant = true } - ChannelRole::Banned => is_banned = true, + ChannelRole::Guest => {} } - if channel_id == ch_id { + if channel_id == membership_channel { current_channel_visibility = Some(visibility); } } // free up database connection drop(rows); - Ok(if is_admin { - Some(ChannelRole::Admin) - } else if is_member { - Some(ChannelRole::Member) - } else if is_banned { - Some(ChannelRole::Banned) - } else if is_participant { + if is_participant && user_role.is_none() { if current_channel_visibility.is_none() { current_channel_visibility = channel::Entity::find() .filter(channel::Column::Id.eq(channel_id)) @@ -970,13 +969,11 @@ impl Database { .map(|channel| channel.visibility); } if current_channel_visibility == Some(ChannelVisibility::Public) { - Some(ChannelRole::Guest) - } else { - None + user_role = Some(ChannelRole::Guest); } - } else { - None - }) + } + + Ok(user_role) } /// Returns the channel ancestors in arbitrary order @@ -1007,7 +1004,7 @@ impl Database { // Returns the channel desendants as a sorted list of edges for further processing. // The edges are sorted such that you will see unknown channel ids as children // before you see them as parents. - async fn get_channel_descendants_2( + async fn get_channel_descendants( &self, channel_ids: impl IntoIterator, tx: &DatabaseTransaction, From 851701cb6f1e50d0ccd272b107bad525eddf99f2 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Oct 2023 09:41:34 -0600 Subject: [PATCH 161/180] Fix get_most_public_ancestor --- crates/channel/src/channel_store.rs | 25 ++++++ crates/collab/src/db/ids.rs | 9 +-- crates/collab/src/db/queries/channels.rs | 78 +++++++++---------- crates/collab/src/db/tests/channel_tests.rs | 48 ++++++++++++ .../src/collab_panel/channel_modal.rs | 14 +++- crates/command_palette/src/command_palette.rs | 2 +- 6 files changed, 126 insertions(+), 50 deletions(-) diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 3e8fbafb6ad0fce58d8232fedf0ac95b8cf1d625..5fb7ddc72ccded0bf326032c9393becb1f2ca672 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -82,6 +82,31 @@ pub struct ChannelMembership { pub kind: proto::channel_member::Kind, pub role: proto::ChannelRole, } +impl ChannelMembership { + pub fn sort_key(&self) -> MembershipSortKey { + MembershipSortKey { + role_order: match self.role { + proto::ChannelRole::Admin => 0, + proto::ChannelRole::Member => 1, + proto::ChannelRole::Banned => 2, + proto::ChannelRole::Guest => 3, + }, + kind_order: match self.kind { + proto::channel_member::Kind::Member => 0, + proto::channel_member::Kind::AncestorMember => 1, + proto::channel_member::Kind::Invitee => 2, + }, + username_order: self.user.github_login.as_str(), + } + } +} + +#[derive(PartialOrd, Ord, PartialEq, Eq)] +pub struct MembershipSortKey<'a> { + role_order: u8, + kind_order: u8, + username_order: &'a str, +} pub enum ChannelEvent { ChannelCreated(ChannelId), diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 38240fd4c418c63f25a3fe3253c5fdda42bb03e7..f0de4c255edc7b13eb27656ceaccefb3c5e26c02 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -108,11 +108,10 @@ impl ChannelRole { } pub fn max(&self, other: Self) -> Self { - match (self, other) { - (ChannelRole::Admin, _) | (_, ChannelRole::Admin) => ChannelRole::Admin, - (ChannelRole::Member, _) | (_, ChannelRole::Member) => ChannelRole::Member, - (ChannelRole::Banned, _) | (_, ChannelRole::Banned) => ChannelRole::Banned, - (ChannelRole::Guest, _) | (_, ChannelRole::Guest) => ChannelRole::Guest, + if self.should_override(other) { + *self + } else { + other } } } diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 0dc197aa0b6e5a36e40d2ee0d50a2bace91f95c3..a1a618c7334b985bf70382f59f8b15ae10e60415 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -1,5 +1,3 @@ -use std::cmp::Ordering; - use super::*; use rpc::proto::{channel_member::Kind, ChannelEdge}; @@ -544,6 +542,12 @@ impl Database { if !channels_to_remove.is_empty() { // Note: this code assumes each channel has one parent. + // If there are multiple valid public paths to a channel, + // e.g. + // If both of these paths are present (* indicating public): + // - zed* -> projects -> vim* + // - zed* -> conrad -> public-projects* -> vim* + // Users would only see one of them (based on edge sort order) let mut replacement_parent: HashMap = HashMap::default(); for ChannelEdge { parent_id, @@ -707,14 +711,14 @@ impl Database { } let mut user_details: HashMap = HashMap::default(); - while let Some(row) = stream.next().await { + while let Some(user_membership) = stream.next().await { let (user_id, channel_role, is_direct_member, is_invite_accepted, visibility): ( UserId, ChannelRole, bool, bool, ChannelVisibility, - ) = row?; + ) = user_membership?; let kind = match (is_direct_member, is_invite_accepted) { (true, true) => proto::channel_member::Kind::Member, (true, false) => proto::channel_member::Kind::Invitee, @@ -745,33 +749,7 @@ impl Database { } } - // sort by permissions descending, within each section, show members, then ancestor members, then invitees. - let mut results: Vec<(UserId, UserDetail)> = user_details.into_iter().collect(); - results.sort_by(|a, b| { - if a.1.channel_role.should_override(b.1.channel_role) { - return Ordering::Less; - } else if b.1.channel_role.should_override(a.1.channel_role) { - return Ordering::Greater; - } - - if a.1.kind == Kind::Member && b.1.kind != Kind::Member { - return Ordering::Less; - } else if b.1.kind == Kind::Member && a.1.kind != Kind::Member { - return Ordering::Greater; - } - - if a.1.kind == Kind::AncestorMember && b.1.kind != Kind::AncestorMember { - return Ordering::Less; - } else if b.1.kind == Kind::AncestorMember && a.1.kind != Kind::AncestorMember { - return Ordering::Greater; - } - - // would be nice to sort alphabetically instead of by user id. - // (or defer all sorting to the UI, but we need something to help the tests) - return a.0.cmp(&b.0); - }); - - Ok(results + Ok(user_details .into_iter() .map(|(user_id, details)| proto::ChannelMember { user_id: user_id.to_proto(), @@ -810,7 +788,7 @@ impl Database { user_id: UserId, tx: &DatabaseTransaction, ) -> Result<()> { - match self.channel_role_for_user(channel_id, user_id, tx).await? { + match dbg!(self.channel_role_for_user(channel_id, user_id, tx).await)? { Some(ChannelRole::Admin) => Ok(()), Some(ChannelRole::Member) | Some(ChannelRole::Banned) @@ -874,10 +852,26 @@ impl Database { channel_id: ChannelId, tx: &DatabaseTransaction, ) -> Result> { - let channel_ids = self.get_channel_ancestors(channel_id, tx).await?; + // Note: if there are many paths to a channel, this will return just one + let arbitary_path = channel_path::Entity::find() + .filter(channel_path::Column::ChannelId.eq(channel_id)) + .order_by(channel_path::Column::IdPath, sea_orm::Order::Desc) + .one(tx) + .await?; + + let Some(path) = arbitary_path else { + return Ok(None); + }; + + let ancestor_ids: Vec = path + .id_path + .trim_matches('/') + .split('/') + .map(|id| ChannelId::from_proto(id.parse().unwrap())) + .collect(); let rows = channel::Entity::find() - .filter(channel::Column::Id.is_in(channel_ids.iter().copied())) + .filter(channel::Column::Id.is_in(ancestor_ids.iter().copied())) .filter(channel::Column::Visibility.eq(ChannelVisibility::Public)) .all(&*tx) .await?; @@ -888,7 +882,7 @@ impl Database { visible_channels.insert(row.id); } - for ancestor in channel_ids.into_iter().rev() { + for ancestor in ancestor_ids { if visible_channels.contains(&ancestor) { return Ok(Some(ancestor)); } @@ -929,11 +923,6 @@ impl Database { .await?; let mut user_role: Option = None; - let max_role = |role| { - user_role - .map(|user_role| user_role.max(role)) - .get_or_insert(role); - }; let mut is_participant = false; let mut current_channel_visibility = None; @@ -946,8 +935,15 @@ impl Database { ChannelRole, ChannelVisibility, ) = row?; + match role { - ChannelRole::Admin | ChannelRole::Member | ChannelRole::Banned => max_role(role), + ChannelRole::Admin | ChannelRole::Member | ChannelRole::Banned => { + if let Some(users_role) = user_role { + user_role = Some(users_role.max(role)); + } else { + user_role = Some(role) + } + } ChannelRole::Guest if visibility == ChannelVisibility::Public => { is_participant = true } diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index f08b1554bc0062e080e1bd5fdd8eee146f090c9d..ac272726da463f4d961f19127cedb3d551642306 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -1028,6 +1028,54 @@ async fn test_user_is_channel_participant(db: &Arc) { ) } +test_both_dbs!( + test_user_joins_correct_channel, + test_user_joins_correct_channel_postgres, + test_user_joins_correct_channel_sqlite +); + +async fn test_user_joins_correct_channel(db: &Arc) { + let admin = new_test_user(db, "admin@example.com").await; + + let zed_channel = db.create_root_channel("zed", admin).await.unwrap(); + + let active_channel = db + .create_channel("active", Some(zed_channel), admin) + .await + .unwrap(); + + let vim_channel = db + .create_channel("vim", Some(active_channel), admin) + .await + .unwrap(); + + let vim2_channel = db + .create_channel("vim2", Some(vim_channel), admin) + .await + .unwrap(); + + db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin) + .await + .unwrap(); + + db.set_channel_visibility(vim_channel, crate::db::ChannelVisibility::Public, admin) + .await + .unwrap(); + + db.set_channel_visibility(vim2_channel, crate::db::ChannelVisibility::Public, admin) + .await + .unwrap(); + + let most_public = db + .transaction( + |tx| async move { db.most_public_ancestor_for_channel(vim_channel, &*tx).await }, + ) + .await + .unwrap(); + + assert_eq!(most_public, Some(zed_channel)) +} + #[track_caller] fn assert_dag(actual: ChannelGraph, expected: &[(ChannelId, Option)]) { let mut actual_map: HashMap> = HashMap::default(); diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index da6edbde696ae011895142b95641f969538da28e..0ccf0894b25fc1fc04ed884769c87b78bfaa72fc 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -100,11 +100,14 @@ impl ChannelModal { let channel_id = self.channel_id; cx.spawn(|this, mut cx| async move { if mode == Mode::ManageMembers { - let members = channel_store + let mut members = channel_store .update(&mut cx, |channel_store, cx| { channel_store.get_channel_member_details(channel_id, cx) }) .await?; + + members.sort_by(|a, b| a.sort_key().cmp(&b.sort_key())); + this.update(&mut cx, |this, cx| { this.picker .update(cx, |picker, _| picker.delegate_mut().members = members); @@ -675,11 +678,16 @@ impl ChannelModalDelegate { invite_member.await?; this.update(&mut cx, |this, cx| { - this.delegate_mut().members.push(ChannelMembership { + let new_member = ChannelMembership { user, kind: proto::channel_member::Kind::Invitee, role: ChannelRole::Member, - }); + }; + let members = &mut this.delegate_mut().members; + match members.binary_search_by_key(&new_member.sort_key(), |k| k.sort_key()) { + Ok(ix) | Err(ix) => members.insert(ix, new_member), + } + cx.notify(); }) }) diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 9b74c13a71e70ea66b292d2232f0e4f55dc6b451..ce762876a41238ab4e0f51ddcd29155b39274840 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -7,7 +7,7 @@ use gpui::{ use picker::{Picker, PickerDelegate, PickerEvent}; use std::cmp::{self, Reverse}; use util::{ - channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL, RELEASE_CHANNEL_NAME}, + channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, ResultExt, }; use workspace::Workspace; From 2456c077f698d72d411543f33fc1d3da3df14c95 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Oct 2023 10:01:31 -0600 Subject: [PATCH 162/180] Fix channel test ordering --- crates/collab/src/db/tests/channel_tests.rs | 31 +++++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index ac272726da463f4d961f19127cedb3d551642306..40842aff5c3bbd90fa7fc1e38297096e6c2e8bcc 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -281,10 +281,12 @@ async fn test_channel_invites(db: &Arc) { assert_eq!(user_3_invites, &[channel_1_1]); - let members = db + let mut members = db .get_channel_participant_details(channel_1_1, user_1) .await .unwrap(); + + members.sort_by_key(|member| member.user_id); assert_eq!( members, &[ @@ -294,14 +296,14 @@ async fn test_channel_invites(db: &Arc) { role: proto::ChannelRole::Admin.into(), }, proto::ChannelMember { - user_id: user_3.to_proto(), + user_id: user_2.to_proto(), kind: proto::channel_member::Kind::Invitee.into(), - role: proto::ChannelRole::Admin.into(), + role: proto::ChannelRole::Member.into(), }, proto::ChannelMember { - user_id: user_2.to_proto(), + user_id: user_3.to_proto(), kind: proto::channel_member::Kind::Invitee.into(), - role: proto::ChannelRole::Member.into(), + role: proto::ChannelRole::Admin.into(), }, ] ); @@ -857,10 +859,13 @@ async fn test_user_is_channel_participant(db: &Arc) { .await .unwrap(); - let members = db + let mut members = db .get_channel_participant_details(vim_channel, admin) .await .unwrap(); + + members.sort_by_key(|member| member.user_id); + assert_eq!( members, &[ @@ -912,11 +917,13 @@ async fn test_user_is_channel_participant(db: &Arc) { .await .is_err()); - let members = db + let mut members = db .get_channel_participant_details(vim_channel, admin) .await .unwrap(); + members.sort_by_key(|member| member.user_id); + assert_eq!( members, &[ @@ -951,10 +958,13 @@ async fn test_user_is_channel_participant(db: &Arc) { .unwrap(); // currently people invited to parent channels are not shown here - let members = db + let mut members = db .get_channel_participant_details(vim_channel, admin) .await .unwrap(); + + members.sort_by_key(|member| member.user_id); + assert_eq!( members, &[ @@ -996,10 +1006,13 @@ async fn test_user_is_channel_participant(db: &Arc) { .await .unwrap(); - let members = db + let mut members = db .get_channel_participant_details(vim_channel, admin) .await .unwrap(); + + members.sort_by_key(|member| member.user_id); + assert_eq!( members, &[ From 3412becfc53ad2551412f586ef58b2c589fe3810 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Oct 2023 10:15:20 -0600 Subject: [PATCH 163/180] Fix some tests --- crates/channel/src/channel_store_tests.rs | 16 ++++++++-------- crates/collab/src/db/queries/channels.rs | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/channel/src/channel_store_tests.rs b/crates/channel/src/channel_store_tests.rs index ea47c7c7b7fe0f5db2c8776cebc7791d9b306a91..23f2e11a03e3fdd577412c5382ff724212e09e0f 100644 --- a/crates/channel/src/channel_store_tests.rs +++ b/crates/channel/src/channel_store_tests.rs @@ -18,12 +18,12 @@ fn test_update_channels(cx: &mut AppContext) { proto::Channel { id: 1, name: "b".to_string(), - visibility: proto::ChannelVisibility::ChannelMembers as i32, + visibility: proto::ChannelVisibility::Members as i32, }, proto::Channel { id: 2, name: "a".to_string(), - visibility: proto::ChannelVisibility::ChannelMembers as i32, + visibility: proto::ChannelVisibility::Members as i32, }, ], channel_permissions: vec![proto::ChannelPermission { @@ -51,12 +51,12 @@ fn test_update_channels(cx: &mut AppContext) { proto::Channel { id: 3, name: "x".to_string(), - visibility: proto::ChannelVisibility::ChannelMembers as i32, + visibility: proto::ChannelVisibility::Members as i32, }, proto::Channel { id: 4, name: "y".to_string(), - visibility: proto::ChannelVisibility::ChannelMembers as i32, + visibility: proto::ChannelVisibility::Members as i32, }, ], insert_edge: vec![ @@ -96,17 +96,17 @@ fn test_dangling_channel_paths(cx: &mut AppContext) { proto::Channel { id: 0, name: "a".to_string(), - visibility: proto::ChannelVisibility::ChannelMembers as i32, + visibility: proto::ChannelVisibility::Members as i32, }, proto::Channel { id: 1, name: "b".to_string(), - visibility: proto::ChannelVisibility::ChannelMembers as i32, + visibility: proto::ChannelVisibility::Members as i32, }, proto::Channel { id: 2, name: "c".to_string(), - visibility: proto::ChannelVisibility::ChannelMembers as i32, + visibility: proto::ChannelVisibility::Members as i32, }, ], insert_edge: vec![ @@ -165,7 +165,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) { channels: vec![proto::Channel { id: channel_id, name: "the-channel".to_string(), - visibility: proto::ChannelVisibility::ChannelMembers as i32, + visibility: proto::ChannelVisibility::Members as i32, }], ..Default::default() }); diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index a1a618c7334b985bf70382f59f8b15ae10e60415..07fe219330dcc2d13fd97722bd7d6885a01e034d 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -143,7 +143,8 @@ impl Database { channel_id: ActiveValue::Set(channel_id_to_join), user_id: ActiveValue::Set(user_id), accepted: ActiveValue::Set(true), - role: ActiveValue::Set(ChannelRole::Guest), + // TODO: change this back to Guest. + role: ActiveValue::Set(ChannelRole::Member), }) .exec(&*tx) .await?; From 5b39fc81232f4c7ed9aa94649f0e951f292b5f6d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Oct 2023 10:24:47 -0600 Subject: [PATCH 164/180] Temporarily join public channels as a member --- crates/collab/src/db/queries/channels.rs | 5 +++-- crates/collab/src/rpc.rs | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 07fe219330dcc2d13fd97722bd7d6885a01e034d..ee989b2ea068ff362adc635e7616b1c25a2de573 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -135,7 +135,8 @@ impl Database { .most_public_ancestor_for_channel(channel_id, &*tx) .await? .unwrap_or(channel_id); - role = Some(ChannelRole::Guest); + // TODO: change this back to Guest. + role = Some(ChannelRole::Member); joined_channel_id = Some(channel_id_to_join); channel_member::Entity::insert(channel_member::ActiveModel { @@ -789,7 +790,7 @@ impl Database { user_id: UserId, tx: &DatabaseTransaction, ) -> Result<()> { - match dbg!(self.channel_role_for_user(channel_id, user_id, tx).await)? { + match self.channel_role_for_user(channel_id, user_id, tx).await? { Some(ChannelRole::Admin) => Ok(()), Some(ChannelRole::Member) | Some(ChannelRole::Banned) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 575c9d88717f79bb933c63f18b6605c152b96985..15ea3b24e133912d030ff15a850c5c3aae70bea8 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -2720,10 +2720,8 @@ async fn join_channel_internal( channel_id: joined_room.channel_id.map(|id| id.to_proto()), live_kit_connection_info, })?; - dbg!("Joined channel", &joined_channel); if let Some(joined_channel) = joined_channel { - dbg!("CMU"); channel_membership_updated(db, joined_channel, &session).await? } From 31241f48bef316eacd5c0e874a96357755f12948 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 17 Oct 2023 18:56:03 +0200 Subject: [PATCH 165/180] workspace: Do not scan for .gitignore files if a .git directory is encountered along the way (#3135) Partially fixes zed-industries/community#575 This PR will see one more fix to the case I've spotted while working on this: namely, if a project has several nested repositories, e.g for a structure: /a /a/.git/ /a/.gitignore /a/b/ /a/b/.git/ /a/b/.gitignore /b/ should not account for a's .gitignore at all - which is sort of similar to the fix in commit #c416fbb, but for the paths in the project. The release note is kinda bad, I'll try to reword it too. - [ ] Improve release note. - [x] Address the same bug for project files. Release Notes: - Fixed .gitignore files beyond the first .git directory being respected by the worktree (zed-industries/community#575). --- crates/project/src/worktree.rs | 39 ++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index a38e43cd87bd16ad257686d5fc0a06336fb6895d..f6fae0c98ba520a789fb9da156fd5a20cc392efc 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2027,11 +2027,16 @@ impl LocalSnapshot { fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc { let mut new_ignores = Vec::new(); - for ancestor in abs_path.ancestors().skip(1) { - if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) { - new_ignores.push((ancestor, Some(ignore.clone()))); - } else { - new_ignores.push((ancestor, None)); + for (index, ancestor) in abs_path.ancestors().enumerate() { + if index > 0 { + if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) { + new_ignores.push((ancestor, Some(ignore.clone()))); + } else { + new_ignores.push((ancestor, None)); + } + } + if ancestor.join(&*DOT_GIT).is_dir() { + break; } } @@ -2048,7 +2053,6 @@ impl LocalSnapshot { if ignore_stack.is_abs_path_ignored(abs_path, is_dir) { ignore_stack = IgnoreStack::all(); } - ignore_stack } @@ -3064,14 +3068,21 @@ impl BackgroundScanner { // Populate ignores above the root. let root_abs_path = self.state.lock().snapshot.abs_path.clone(); - for ancestor in root_abs_path.ancestors().skip(1) { - if let Ok(ignore) = build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await - { - self.state - .lock() - .snapshot - .ignores_by_parent_abs_path - .insert(ancestor.into(), (ignore.into(), false)); + for (index, ancestor) in root_abs_path.ancestors().enumerate() { + if index != 0 { + if let Ok(ignore) = + build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await + { + self.state + .lock() + .snapshot + .ignores_by_parent_abs_path + .insert(ancestor.into(), (ignore.into(), false)); + } + } + if ancestor.join(&*DOT_GIT).is_dir() { + // Reached root of git repository. + break; } } From 8db389313bf993d60ecf774122eea276ef0546d2 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 17 Oct 2023 13:34:51 -0400 Subject: [PATCH 166/180] Add link & public icons --- assets/icons/link.svg | 3 +++ assets/icons/public.svg | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 assets/icons/link.svg create mode 100644 assets/icons/public.svg diff --git a/assets/icons/link.svg b/assets/icons/link.svg new file mode 100644 index 0000000000000000000000000000000000000000..4925bd8e0008b3f39fdd9316e075ec03dcd29dd2 --- /dev/null +++ b/assets/icons/link.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/public.svg b/assets/icons/public.svg new file mode 100644 index 0000000000000000000000000000000000000000..55a79684856cfce950785c6fcb94f5a90648ef02 --- /dev/null +++ b/assets/icons/public.svg @@ -0,0 +1,3 @@ + + + From 33296802fb0424863ad3a956b7b70b76afaa23f3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 12:11:39 +0300 Subject: [PATCH 167/180] Add a rough prototype --- crates/language_tools/src/lsp_log.rs | 60 ++++++++++++++++------------ 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 383ca94851409cfd28530c62e3d786e1b1c72061..a796bc46c85ba5270477cc4db2fb347cf8adc904 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -36,7 +36,7 @@ struct ProjectState { } struct LanguageServerState { - log_buffer: ModelHandle, + log_storage: Vec, rpc_state: Option, _io_logs_subscription: Option, _lsp_logs_subscription: Option, @@ -168,15 +168,14 @@ impl LogStore { project: &ModelHandle, id: LanguageServerId, cx: &mut ModelContext, - ) -> Option> { + ) -> Option<&mut Vec> { let project_state = self.projects.get_mut(&project.downgrade())?; let server_state = project_state.servers.entry(id).or_insert_with(|| { cx.notify(); LanguageServerState { rpc_state: None, - log_buffer: cx - .add_model(|cx| Buffer::new(0, cx.model_id() as u64, "")) - .clone(), + // TODO kb move this to settings? + log_storage: Vec::with_capacity(10_000), _io_logs_subscription: None, _lsp_logs_subscription: None, } @@ -186,7 +185,7 @@ impl LogStore { if let Some(server) = server.as_deref() { if server.has_notification_handler::() { // Another event wants to re-add the server that was already added and subscribed to, avoid doing it again. - return Some(server_state.log_buffer.clone()); + return Some(&mut server_state.log_storage); } } @@ -215,7 +214,7 @@ impl LogStore { } }) }); - Some(server_state.log_buffer.clone()) + Some(&mut server_state.log_storage) } fn add_language_server_log( @@ -225,25 +224,23 @@ impl LogStore { message: &str, cx: &mut ModelContext, ) -> Option<()> { - let buffer = match self + let log_lines = match self .projects .get_mut(&project.downgrade())? .servers - .get(&id) - .map(|state| state.log_buffer.clone()) + .get_mut(&id) + .map(|state| &mut state.log_storage) { Some(existing_buffer) => existing_buffer, None => self.add_language_server(&project, id, cx)?, }; - buffer.update(cx, |buffer, cx| { - let len = buffer.len(); - let has_newline = message.ends_with("\n"); - buffer.edit([(len..len, message)], None, cx); - if !has_newline { - let len = buffer.len(); - buffer.edit([(len..len, "\n")], None, cx); - } - }); + + // TODO kb something better VecDequeue? + if log_lines.capacity() == log_lines.len() { + log_lines.drain(..log_lines.len() / 2); + } + log_lines.push(message.trim().to_string()); + cx.notify(); Some(()) } @@ -260,15 +257,15 @@ impl LogStore { Some(()) } - pub fn log_buffer_for_server( + fn server_logs( &self, project: &ModelHandle, server_id: LanguageServerId, - ) -> Option> { + ) -> Option<&[String]> { let weak_project = project.downgrade(); let project_state = self.projects.get(&weak_project)?; let server_state = project_state.servers.get(&server_id)?; - Some(server_state.log_buffer.clone()) + Some(&server_state.log_storage) } fn enable_rpc_trace_for_language_server( @@ -487,14 +484,24 @@ impl LspLogView { } fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext) { - let buffer = self + let log_contents = self .log_store .read(cx) - .log_buffer_for_server(&self.project, server_id); - if let Some(buffer) = buffer { + .server_logs(&self.project, server_id) + .map(|lines| lines.join("\n")); + if let Some(log_contents) = log_contents { self.current_server_id = Some(server_id); self.is_showing_rpc_trace = false; - self.editor = Self::editor_for_buffer(self.project.clone(), buffer, cx); + let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, log_contents)); + let editor = cx.add_view(|cx| { + let mut editor = Editor::for_buffer(buffer, Some(self.project.clone()), cx); + editor.set_read_only(true); + editor.move_to_end(&Default::default(), cx); + editor + }); + cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())) + .detach(); + self.editor = editor; cx.notify(); } } @@ -505,6 +512,7 @@ impl LspLogView { cx: &mut ViewContext, ) { let buffer = self.log_store.update(cx, |log_set, cx| { + // TODO kb save this buffer from overflows too log_set.enable_rpc_trace_for_language_server(&self.project, server_id, cx) }); if let Some(buffer) = buffer { From 5a4161d29385ca6fd454ca788cab3204c5f1e820 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 15:41:27 +0300 Subject: [PATCH 168/180] Do not detach subscriptions --- crates/language_tools/src/lsp_log.rs | 63 +++++++++++++++++----------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index a796bc46c85ba5270477cc4db2fb347cf8adc904..dcdaf1df6b4683175d0c6f1428de41c9811784e7 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -1,4 +1,4 @@ -use collections::HashMap; +use collections::{HashMap, VecDeque}; use editor::Editor; use futures::{channel::mpsc, StreamExt}; use gpui::{ @@ -36,7 +36,7 @@ struct ProjectState { } struct LanguageServerState { - log_storage: Vec, + log_storage: VecDeque, rpc_state: Option, _io_logs_subscription: Option, _lsp_logs_subscription: Option, @@ -49,6 +49,7 @@ struct LanguageServerRpcState { pub struct LspLogView { pub(crate) editor: ViewHandle, + _editor_subscription: Subscription, log_store: ModelHandle, current_server_id: Option, is_showing_rpc_trace: bool, @@ -168,14 +169,14 @@ impl LogStore { project: &ModelHandle, id: LanguageServerId, cx: &mut ModelContext, - ) -> Option<&mut Vec> { + ) -> Option<&mut LanguageServerState> { let project_state = self.projects.get_mut(&project.downgrade())?; let server_state = project_state.servers.entry(id).or_insert_with(|| { cx.notify(); LanguageServerState { rpc_state: None, // TODO kb move this to settings? - log_storage: Vec::with_capacity(10_000), + log_storage: VecDeque::with_capacity(10_000), _io_logs_subscription: None, _lsp_logs_subscription: None, } @@ -185,7 +186,7 @@ impl LogStore { if let Some(server) = server.as_deref() { if server.has_notification_handler::() { // Another event wants to re-add the server that was already added and subscribed to, avoid doing it again. - return Some(&mut server_state.log_storage); + return Some(server_state); } } @@ -214,7 +215,7 @@ impl LogStore { } }) }); - Some(&mut server_state.log_storage) + Some(server_state) } fn add_language_server_log( @@ -224,22 +225,24 @@ impl LogStore { message: &str, cx: &mut ModelContext, ) -> Option<()> { - let log_lines = match self + let language_server_state = match self .projects .get_mut(&project.downgrade())? .servers .get_mut(&id) - .map(|state| &mut state.log_storage) { - Some(existing_buffer) => existing_buffer, + Some(existing_state) => existing_state, None => self.add_language_server(&project, id, cx)?, }; - // TODO kb something better VecDequeue? + let log_lines = &mut language_server_state.log_storage; if log_lines.capacity() == log_lines.len() { - log_lines.drain(..log_lines.len() / 2); + log_lines.pop_front(); } - log_lines.push(message.trim().to_string()); + log_lines.push_back(message.trim().to_string()); + + //// TODO kb refresh editor too + //need LspLogView. cx.notify(); Some(()) @@ -261,7 +264,7 @@ impl LogStore { &self, project: &ModelHandle, server_id: LanguageServerId, - ) -> Option<&[String]> { + ) -> Option<&VecDeque> { let weak_project = project.downgrade(); let project_state = self.projects.get(&weak_project)?; let server_state = project_state.servers.get(&server_id)?; @@ -408,8 +411,10 @@ impl LspLogView { cx.notify(); }); + let (editor, _editor_subscription) = Self::editor_for_buffer(project.clone(), buffer, cx); let mut this = Self { - editor: Self::editor_for_buffer(project.clone(), buffer, cx), + editor, + _editor_subscription, project, log_store, current_server_id: None, @@ -426,16 +431,15 @@ impl LspLogView { project: ModelHandle, buffer: ModelHandle, cx: &mut ViewContext, - ) -> ViewHandle { + ) -> (ViewHandle, Subscription) { let editor = cx.add_view(|cx| { let mut editor = Editor::for_buffer(buffer, Some(project), cx); editor.set_read_only(true); editor.move_to_end(&Default::default(), cx); editor }); - cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())) - .detach(); - editor + let subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); + (editor, subscription) } pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option> { @@ -488,19 +492,27 @@ impl LspLogView { .log_store .read(cx) .server_logs(&self.project, server_id) - .map(|lines| lines.join("\n")); + .map(|lines| { + let (a, b) = lines.as_slices(); + let log_contents = a.join("\n"); + if b.is_empty() { + log_contents + } else { + log_contents + "\n" + &b.join("\n") + } + }); if let Some(log_contents) = log_contents { self.current_server_id = Some(server_id); self.is_showing_rpc_trace = false; - let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, log_contents)); let editor = cx.add_view(|cx| { - let mut editor = Editor::for_buffer(buffer, Some(self.project.clone()), cx); + let mut editor = Editor::multi_line(None, cx); editor.set_read_only(true); editor.move_to_end(&Default::default(), cx); + editor.set_text(log_contents, cx); editor }); - cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())) - .detach(); + self._editor_subscription = + cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); self.editor = editor; cx.notify(); } @@ -518,7 +530,10 @@ impl LspLogView { if let Some(buffer) = buffer { self.current_server_id = Some(server_id); self.is_showing_rpc_trace = true; - self.editor = Self::editor_for_buffer(self.project.clone(), buffer, cx); + let (editor, _editor_subscription) = + Self::editor_for_buffer(self.project.clone(), buffer, cx); + self.editor = editor; + self._editor_subscription = _editor_subscription; cx.notify(); } } From ba5c188630d86373b119213ffdab21449eccccc6 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 16:53:44 +0300 Subject: [PATCH 169/180] Update editor with current buffer logs --- crates/language_tools/src/lsp_log.rs | 41 ++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index dcdaf1df6b4683175d0c6f1428de41c9811784e7..bf75d35bb7c6ac515ba476cd944620c0f7fbe595 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -24,6 +24,7 @@ use workspace::{ const SEND_LINE: &str = "// Send:\n"; const RECEIVE_LINE: &str = "// Receive:\n"; +const MAX_STORED_LOG_ENTRIES: usize = 5000; pub struct LogStore { projects: HashMap, ProjectState>, @@ -54,7 +55,7 @@ pub struct LspLogView { current_server_id: Option, is_showing_rpc_trace: bool, project: ModelHandle, - _log_store_subscription: Subscription, + _log_store_subscriptions: Vec, } pub struct LspLogToolbarItemView { @@ -175,8 +176,7 @@ impl LogStore { cx.notify(); LanguageServerState { rpc_state: None, - // TODO kb move this to settings? - log_storage: VecDeque::with_capacity(10_000), + log_storage: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), _io_logs_subscription: None, _lsp_logs_subscription: None, } @@ -236,14 +236,16 @@ impl LogStore { }; let log_lines = &mut language_server_state.log_storage; - if log_lines.capacity() == log_lines.len() { + if log_lines.len() == MAX_STORED_LOG_ENTRIES { log_lines.pop_front(); } - log_lines.push_back(message.trim().to_string()); - - //// TODO kb refresh editor too - //need LspLogView. + let message = message.trim(); + log_lines.push_back(message.to_string()); + cx.emit(Event::NewServerLogEntry { + id, + entry: message.to_string(), + }); cx.notify(); Some(()) } @@ -375,7 +377,7 @@ impl LspLogView { .get(&project.downgrade()) .and_then(|project| project.servers.keys().copied().next()); let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "")); - let _log_store_subscription = cx.observe(&log_store, |this, store, cx| { + let model_changes_subscription = cx.observe(&log_store, |this, store, cx| { (|| -> Option<()> { let project_state = store.read(cx).projects.get(&this.project.downgrade())?; if let Some(current_lsp) = this.current_server_id { @@ -411,6 +413,18 @@ impl LspLogView { cx.notify(); }); + let events_subscriptions = cx.subscribe(&log_store, |log_view, _, e, cx| match e { + Event::NewServerLogEntry { id, entry } => { + if log_view.current_server_id == Some(*id) { + log_view.editor.update(cx, |editor, cx| { + editor.set_read_only(false); + editor.handle_input(entry, cx); + editor.handle_input("\n", cx); + editor.set_read_only(true); + }) + } + } + }); let (editor, _editor_subscription) = Self::editor_for_buffer(project.clone(), buffer, cx); let mut this = Self { editor, @@ -419,7 +433,7 @@ impl LspLogView { log_store, current_server_id: None, is_showing_rpc_trace: false, - _log_store_subscription, + _log_store_subscriptions: vec![model_changes_subscription, events_subscriptions], }; if let Some(server_id) = server_id { this.show_logs_for_server(server_id, cx); @@ -524,7 +538,6 @@ impl LspLogView { cx: &mut ViewContext, ) { let buffer = self.log_store.update(cx, |log_set, cx| { - // TODO kb save this buffer from overflows too log_set.enable_rpc_trace_for_language_server(&self.project, server_id, cx) }); if let Some(buffer) = buffer { @@ -972,8 +985,12 @@ impl LspLogToolbarItemView { } } +pub enum Event { + NewServerLogEntry { id: LanguageServerId, entry: String }, +} + impl Entity for LogStore { - type Event = (); + type Event = Event; } impl Entity for LspLogView { From c872c86c4a5df3200cc1b2f621aedc5a4c8651e3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 20:53:39 +0300 Subject: [PATCH 170/180] Remove another needless log buffer --- crates/language_tools/src/lsp_log.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index bf75d35bb7c6ac515ba476cd944620c0f7fbe595..58f7d68235babf37f07b81864a966e109ae3975b 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -376,7 +376,6 @@ impl LspLogView { .projects .get(&project.downgrade()) .and_then(|project| project.servers.keys().copied().next()); - let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "")); let model_changes_subscription = cx.observe(&log_store, |this, store, cx| { (|| -> Option<()> { let project_state = store.read(cx).projects.get(&this.project.downgrade())?; @@ -425,7 +424,14 @@ impl LspLogView { } } }); - let (editor, _editor_subscription) = Self::editor_for_buffer(project.clone(), buffer, cx); + // TODO kb deduplicate + let editor = cx.add_view(|cx| { + let mut editor = Editor::multi_line(None, cx); + editor.set_read_only(true); + editor.move_to_end(&Default::default(), cx); + editor + }); + let _editor_subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); let mut this = Self { editor, _editor_subscription, From 08af830fd7b6b70ea29ba55b3088acec967829cd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 21:40:25 +0300 Subject: [PATCH 171/180] Do not create buffers for rpc logs --- crates/language_tools/src/lsp_log.rs | 218 +++++++++++++++------------ 1 file changed, 118 insertions(+), 100 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 58f7d68235babf37f07b81864a966e109ae3975b..ae63f84b64deff1910328823e6fcb285db8ed685 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -1,5 +1,5 @@ use collections::{HashMap, VecDeque}; -use editor::Editor; +use editor::{Editor, MoveToEnd}; use futures::{channel::mpsc, StreamExt}; use gpui::{ actions, @@ -11,7 +11,7 @@ use gpui::{ AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, Subscription, View, ViewContext, ViewHandle, WeakModelHandle, }; -use language::{Buffer, LanguageServerId, LanguageServerName}; +use language::{LanguageServerId, LanguageServerName}; use lsp::IoKind; use project::{search::SearchQuery, Project}; use std::{borrow::Cow, sync::Arc}; @@ -22,8 +22,8 @@ use workspace::{ ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceCreated, }; -const SEND_LINE: &str = "// Send:\n"; -const RECEIVE_LINE: &str = "// Receive:\n"; +const SEND_LINE: &str = "// Send:"; +const RECEIVE_LINE: &str = "// Receive:"; const MAX_STORED_LOG_ENTRIES: usize = 5000; pub struct LogStore { @@ -37,20 +37,20 @@ struct ProjectState { } struct LanguageServerState { - log_storage: VecDeque, + log_messages: VecDeque, rpc_state: Option, _io_logs_subscription: Option, _lsp_logs_subscription: Option, } struct LanguageServerRpcState { - buffer: ModelHandle, + rpc_messages: VecDeque, last_message_kind: Option, } pub struct LspLogView { pub(crate) editor: ViewHandle, - _editor_subscription: Subscription, + editor_subscription: Subscription, log_store: ModelHandle, current_server_id: Option, is_showing_rpc_trace: bool, @@ -124,10 +124,9 @@ impl LogStore { io_tx, }; cx.spawn_weak(|this, mut cx| async move { - while let Some((project, server_id, io_kind, mut message)) = io_rx.next().await { + while let Some((project, server_id, io_kind, message)) = io_rx.next().await { if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { - message.push('\n'); this.on_io(project, server_id, io_kind, &message, cx); }); } @@ -176,7 +175,7 @@ impl LogStore { cx.notify(); LanguageServerState { rpc_state: None, - log_storage: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), + log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), _io_logs_subscription: None, _lsp_logs_subscription: None, } @@ -235,16 +234,16 @@ impl LogStore { None => self.add_language_server(&project, id, cx)?, }; - let log_lines = &mut language_server_state.log_storage; + let log_lines = &mut language_server_state.log_messages; if log_lines.len() == MAX_STORED_LOG_ENTRIES { log_lines.pop_front(); } - let message = message.trim(); log_lines.push_back(message.to_string()); cx.emit(Event::NewServerLogEntry { id, entry: message.to_string(), + is_rpc: false, }); cx.notify(); Some(()) @@ -270,38 +269,24 @@ impl LogStore { let weak_project = project.downgrade(); let project_state = self.projects.get(&weak_project)?; let server_state = project_state.servers.get(&server_id)?; - Some(&server_state.log_storage) + Some(&server_state.log_messages) } fn enable_rpc_trace_for_language_server( &mut self, project: &ModelHandle, server_id: LanguageServerId, - cx: &mut ModelContext, - ) -> Option> { + ) -> Option<&mut LanguageServerRpcState> { let weak_project = project.downgrade(); let project_state = self.projects.get_mut(&weak_project)?; let server_state = project_state.servers.get_mut(&server_id)?; - let rpc_state = server_state.rpc_state.get_or_insert_with(|| { - let language = project.read(cx).languages().language_for_name("JSON"); - let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "")); - cx.spawn_weak({ - let buffer = buffer.clone(); - |_, mut cx| async move { - let language = language.await.ok(); - buffer.update(&mut cx, |buffer, cx| { - buffer.set_language(language, cx); - }); - } - }) - .detach(); - - LanguageServerRpcState { - buffer, + let rpc_state = server_state + .rpc_state + .get_or_insert_with(|| LanguageServerRpcState { + rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), last_message_kind: None, - } - }); - Some(rpc_state.buffer.clone()) + }); + Some(rpc_state) } pub fn disable_rpc_trace_for_language_server( @@ -330,7 +315,7 @@ impl LogStore { IoKind::StdIn => false, IoKind::StdErr => { let project = project.upgrade(cx)?; - let message = format!("stderr: {}\n", message.trim()); + let message = format!("stderr: {}", message.trim()); self.add_language_server_log(&project, language_server_id, &message, cx); return Some(()); } @@ -343,24 +328,40 @@ impl LogStore { .get_mut(&language_server_id)? .rpc_state .as_mut()?; - state.buffer.update(cx, |buffer, cx| { - let kind = if is_received { - MessageKind::Receive - } else { - MessageKind::Send + let kind = if is_received { + MessageKind::Receive + } else { + MessageKind::Send + }; + + let rpc_log_lines = &mut state.rpc_messages; + if rpc_log_lines.len() == MAX_STORED_LOG_ENTRIES { + rpc_log_lines.pop_front(); + } + if state.last_message_kind != Some(kind) { + let line_before_message = match kind { + MessageKind::Send => SEND_LINE, + MessageKind::Receive => RECEIVE_LINE, }; - if state.last_message_kind != Some(kind) { - let len = buffer.len(); - let line = match kind { - MessageKind::Send => SEND_LINE, - MessageKind::Receive => RECEIVE_LINE, - }; - buffer.edit([(len..len, line)], None, cx); - state.last_message_kind = Some(kind); - } - let len = buffer.len(); - buffer.edit([(len..len, message)], None, cx); + rpc_log_lines.push_back(line_before_message.to_string()); + cx.emit(Event::NewServerLogEntry { + id: language_server_id, + entry: line_before_message.to_string(), + is_rpc: true, + }); + } + + if rpc_log_lines.len() == MAX_STORED_LOG_ENTRIES { + rpc_log_lines.pop_front(); + } + let message = message.trim(); + rpc_log_lines.push_back(message.to_string()); + cx.emit(Event::NewServerLogEntry { + id: language_server_id, + entry: message.to_string(), + is_rpc: true, }); + cx.notify(); Some(()) } } @@ -413,28 +414,25 @@ impl LspLogView { cx.notify(); }); let events_subscriptions = cx.subscribe(&log_store, |log_view, _, e, cx| match e { - Event::NewServerLogEntry { id, entry } => { + Event::NewServerLogEntry { id, entry, is_rpc } => { if log_view.current_server_id == Some(*id) { - log_view.editor.update(cx, |editor, cx| { - editor.set_read_only(false); - editor.handle_input(entry, cx); - editor.handle_input("\n", cx); - editor.set_read_only(true); - }) + if (*is_rpc && log_view.is_showing_rpc_trace) + || (!*is_rpc && !log_view.is_showing_rpc_trace) + { + log_view.editor.update(cx, |editor, cx| { + editor.set_read_only(false); + editor.handle_input(entry.trim(), cx); + editor.handle_input("\n", cx); + editor.set_read_only(true); + }); + } } } }); - // TODO kb deduplicate - let editor = cx.add_view(|cx| { - let mut editor = Editor::multi_line(None, cx); - editor.set_read_only(true); - editor.move_to_end(&Default::default(), cx); - editor - }); - let _editor_subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); + let (editor, editor_subscription) = Self::editor_for_logs(String::new(), cx); let mut this = Self { editor, - _editor_subscription, + editor_subscription, project, log_store, current_server_id: None, @@ -447,19 +445,19 @@ impl LspLogView { this } - fn editor_for_buffer( - project: ModelHandle, - buffer: ModelHandle, + fn editor_for_logs( + log_contents: String, cx: &mut ViewContext, ) -> (ViewHandle, Subscription) { let editor = cx.add_view(|cx| { - let mut editor = Editor::for_buffer(buffer, Some(project), cx); + let mut editor = Editor::multi_line(None, cx); + editor.set_text(log_contents, cx); + editor.move_to_end(&MoveToEnd, cx); editor.set_read_only(true); - editor.move_to_end(&Default::default(), cx); editor }); - let subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); - (editor, subscription) + let editor_subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); + (editor, editor_subscription) } pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option> { @@ -512,28 +510,13 @@ impl LspLogView { .log_store .read(cx) .server_logs(&self.project, server_id) - .map(|lines| { - let (a, b) = lines.as_slices(); - let log_contents = a.join("\n"); - if b.is_empty() { - log_contents - } else { - log_contents + "\n" + &b.join("\n") - } - }); + .map(log_contents); if let Some(log_contents) = log_contents { self.current_server_id = Some(server_id); self.is_showing_rpc_trace = false; - let editor = cx.add_view(|cx| { - let mut editor = Editor::multi_line(None, cx); - editor.set_read_only(true); - editor.move_to_end(&Default::default(), cx); - editor.set_text(log_contents, cx); - editor - }); - self._editor_subscription = - cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); + let (editor, editor_subscription) = Self::editor_for_logs(log_contents, cx); self.editor = editor; + self.editor_subscription = editor_subscription; cx.notify(); } } @@ -543,16 +526,37 @@ impl LspLogView { server_id: LanguageServerId, cx: &mut ViewContext, ) { - let buffer = self.log_store.update(cx, |log_set, cx| { - log_set.enable_rpc_trace_for_language_server(&self.project, server_id, cx) + let rpc_log = self.log_store.update(cx, |log_store, _| { + log_store + .enable_rpc_trace_for_language_server(&self.project, server_id) + .map(|state| log_contents(&state.rpc_messages)) }); - if let Some(buffer) = buffer { + if let Some(rpc_log) = rpc_log { self.current_server_id = Some(server_id); self.is_showing_rpc_trace = true; - let (editor, _editor_subscription) = - Self::editor_for_buffer(self.project.clone(), buffer, cx); + let (editor, editor_subscription) = Self::editor_for_logs(rpc_log, cx); + let language = self.project.read(cx).languages().language_for_name("JSON"); + editor + .read(cx) + .buffer() + .read(cx) + .as_singleton() + .expect("log buffer should be a singleton") + .update(cx, |_, cx| { + cx.spawn_weak({ + let buffer = cx.handle(); + |_, mut cx| async move { + let language = language.await.ok(); + buffer.update(&mut cx, |buffer, cx| { + buffer.set_language(language, cx); + }); + } + }) + .detach(); + }); + self.editor = editor; - self._editor_subscription = _editor_subscription; + self.editor_subscription = editor_subscription; cx.notify(); } } @@ -565,7 +569,7 @@ impl LspLogView { ) { self.log_store.update(cx, |log_store, cx| { if enabled { - log_store.enable_rpc_trace_for_language_server(&self.project, server_id, cx); + log_store.enable_rpc_trace_for_language_server(&self.project, server_id); } else { log_store.disable_rpc_trace_for_language_server(&self.project, server_id, cx); } @@ -577,6 +581,16 @@ impl LspLogView { } } +fn log_contents(lines: &VecDeque) -> String { + let (a, b) = lines.as_slices(); + let log_contents = a.join("\n"); + if b.is_empty() { + log_contents + } else { + log_contents + "\n" + &b.join("\n") + } +} + impl View for LspLogView { fn ui_name() -> &'static str { "LspLogView" @@ -992,7 +1006,11 @@ impl LspLogToolbarItemView { } pub enum Event { - NewServerLogEntry { id: LanguageServerId, entry: String }, + NewServerLogEntry { + id: LanguageServerId, + entry: String, + is_rpc: bool, + }, } impl Entity for LogStore { From a95cce9a60716e77ecbfaa85eaa9ffaa039cfc60 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 21:47:21 +0300 Subject: [PATCH 172/180] Reduce max log lines, clean log buffers better --- crates/language_tools/src/lsp_log.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index ae63f84b64deff1910328823e6fcb285db8ed685..c75fea256db388166840adf9d55bf18ca3eaabdb 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -24,7 +24,7 @@ use workspace::{ const SEND_LINE: &str = "// Send:"; const RECEIVE_LINE: &str = "// Receive:"; -const MAX_STORED_LOG_ENTRIES: usize = 5000; +const MAX_STORED_LOG_ENTRIES: usize = 2000; pub struct LogStore { projects: HashMap, ProjectState>, @@ -235,7 +235,7 @@ impl LogStore { }; let log_lines = &mut language_server_state.log_messages; - if log_lines.len() == MAX_STORED_LOG_ENTRIES { + while log_lines.len() >= MAX_STORED_LOG_ENTRIES { log_lines.pop_front(); } let message = message.trim(); @@ -335,9 +335,6 @@ impl LogStore { }; let rpc_log_lines = &mut state.rpc_messages; - if rpc_log_lines.len() == MAX_STORED_LOG_ENTRIES { - rpc_log_lines.pop_front(); - } if state.last_message_kind != Some(kind) { let line_before_message = match kind { MessageKind::Send => SEND_LINE, @@ -351,7 +348,7 @@ impl LogStore { }); } - if rpc_log_lines.len() == MAX_STORED_LOG_ENTRIES { + while rpc_log_lines.len() >= MAX_STORED_LOG_ENTRIES { rpc_log_lines.pop_front(); } let message = message.trim(); From 1c5e07f4a21f0ca4f0b80881798605c51a94a6ec Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Oct 2023 13:19:22 -0600 Subject: [PATCH 173/180] update sidebar for public channels --- assets/icons/public.svg | 2 +- .../20221109000000_test_schema.sql | 2 +- ...rojects_room_id_fkey_on_delete_cascade.sql | 8 + crates/collab_ui/src/collab_panel.rs | 246 ++++++++++++++---- 4 files changed, 208 insertions(+), 50 deletions(-) create mode 100644 crates/collab/migrations/20231017185833_projects_room_id_fkey_on_delete_cascade.sql diff --git a/assets/icons/public.svg b/assets/icons/public.svg index 55a79684856cfce950785c6fcb94f5a90648ef02..38278cdabaf203ef47fea13a88a60ed6f61d18ac 100644 --- a/assets/icons/public.svg +++ b/assets/icons/public.svg @@ -1,3 +1,3 @@ - + diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index dcb793aa513d0bde1a5e6f5787dc0d7f95fead99..8eb6b52fd8b4f0ece5c64f4c45c48da4ee97fe18 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -44,7 +44,7 @@ CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); CREATE TABLE "projects" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, - "room_id" INTEGER REFERENCES rooms (id) NOT NULL, + "room_id" INTEGER REFERENCES rooms (id) ON DELETE CASCADE NOT NULL, "host_user_id" INTEGER REFERENCES users (id) NOT NULL, "host_connection_id" INTEGER, "host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE, diff --git a/crates/collab/migrations/20231017185833_projects_room_id_fkey_on_delete_cascade.sql b/crates/collab/migrations/20231017185833_projects_room_id_fkey_on_delete_cascade.sql new file mode 100644 index 0000000000000000000000000000000000000000..be535ff7fa6e707182b8698647ecc92d9f976183 --- /dev/null +++ b/crates/collab/migrations/20231017185833_projects_room_id_fkey_on_delete_cascade.sql @@ -0,0 +1,8 @@ +-- Add migration script here + +ALTER TABLE projects + DROP CONSTRAINT projects_room_id_fkey, + ADD CONSTRAINT projects_room_id_fkey + FOREIGN KEY (room_id) + REFERENCES rooms (id) + ON DELETE CASCADE; diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 30505b0876c054d8f1b05fcec531d6d04bde5fa1..2e68a1c939c8ba589f1c54a1c6b2abbca3ec8b49 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -11,7 +11,10 @@ use anyhow::Result; use call::ActiveCall; use channel::{Channel, ChannelData, ChannelEvent, ChannelId, ChannelPath, ChannelStore}; use channel_modal::ChannelModal; -use client::{proto::PeerId, Client, Contact, User, UserStore}; +use client::{ + proto::{self, PeerId}, + Client, Contact, User, UserStore, +}; use contact_finder::ContactFinder; use context_menu::{ContextMenu, ContextMenuItem}; use db::kvp::KEY_VALUE_STORE; @@ -428,7 +431,7 @@ enum ListEntry { is_last: bool, }, ParticipantScreen { - peer_id: PeerId, + peer_id: Option, is_last: bool, }, IncomingRequest(Arc), @@ -442,6 +445,9 @@ enum ListEntry { ChannelNotes { channel_id: ChannelId, }, + ChannelChat { + channel_id: ChannelId, + }, ChannelEditor { depth: usize, }, @@ -602,6 +608,13 @@ impl CollabPanel { ix, cx, ), + ListEntry::ChannelChat { channel_id } => this.render_channel_chat( + *channel_id, + &theme.collab_panel, + is_selected, + ix, + cx, + ), ListEntry::ChannelInvite(channel) => Self::render_channel_invite( channel.clone(), this.channel_store.clone(), @@ -804,7 +817,8 @@ impl CollabPanel { let room = room.read(cx); if let Some(channel_id) = room.channel_id() { - self.entries.push(ListEntry::ChannelNotes { channel_id }) + self.entries.push(ListEntry::ChannelNotes { channel_id }); + self.entries.push(ListEntry::ChannelChat { channel_id }) } // Populate the active user. @@ -836,7 +850,13 @@ impl CollabPanel { project_id: project.id, worktree_root_names: project.worktree_root_names.clone(), host_user_id: user_id, - is_last: projects.peek().is_none(), + is_last: projects.peek().is_none() && !room.is_screen_sharing(), + }); + } + if room.is_screen_sharing() { + self.entries.push(ListEntry::ParticipantScreen { + peer_id: None, + is_last: true, }); } } @@ -880,7 +900,7 @@ impl CollabPanel { } if !participant.video_tracks.is_empty() { self.entries.push(ListEntry::ParticipantScreen { - peer_id: participant.peer_id, + peer_id: Some(participant.peer_id), is_last: true, }); } @@ -1225,14 +1245,18 @@ impl CollabPanel { ) -> AnyElement { enum CallParticipant {} enum CallParticipantTooltip {} + enum LeaveCallButton {} + enum LeaveCallTooltip {} let collab_theme = &theme.collab_panel; let is_current_user = user_store.read(cx).current_user().map(|user| user.id) == Some(user.id); - let content = - MouseEventHandler::new::(user.id as usize, cx, |mouse_state, _| { + let content = MouseEventHandler::new::( + user.id as usize, + cx, + |mouse_state, cx| { let style = if is_current_user { *collab_theme .contact_row @@ -1268,14 +1292,32 @@ impl CollabPanel { Label::new("Calling", collab_theme.calling_indicator.text.clone()) .contained() .with_style(collab_theme.calling_indicator.container) - .aligned(), + .aligned() + .into_any(), ) } else if is_current_user { Some( - Label::new("You", collab_theme.calling_indicator.text.clone()) - .contained() - .with_style(collab_theme.calling_indicator.container) - .aligned(), + MouseEventHandler::new::(0, cx, |state, _| { + render_icon_button( + theme + .collab_panel + .leave_call_button + .style_for(is_selected, state), + "icons/exit.svg", + ) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, _, cx| { + Self::leave_call(cx); + }) + .with_tooltip::( + 0, + "Leave call", + None, + theme.tooltip.clone(), + cx, + ) + .into_any(), ) } else { None @@ -1284,7 +1326,8 @@ impl CollabPanel { .with_height(collab_theme.row_height) .contained() .with_style(style) - }); + }, + ); if is_current_user || is_pending || peer_id.is_none() { return content.into_any(); @@ -1406,7 +1449,7 @@ impl CollabPanel { } fn render_participant_screen( - peer_id: PeerId, + peer_id: Option, is_last: bool, is_selected: bool, theme: &theme::CollabPanel, @@ -1421,8 +1464,8 @@ impl CollabPanel { .unwrap_or(0.); let tree_branch = theme.tree_branch; - MouseEventHandler::new::( - peer_id.as_u64() as usize, + let handler = MouseEventHandler::new::( + peer_id.map(|id| id.as_u64()).unwrap_or(0) as usize, cx, |mouse_state, cx| { let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state); @@ -1460,16 +1503,20 @@ impl CollabPanel { .contained() .with_style(row.container) }, - ) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - workspace.open_shared_screen(peer_id, cx) - }); - } - }) - .into_any() + ); + if peer_id.is_none() { + return handler.into_any(); + } + handler + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, this, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + workspace.update(cx, |workspace, cx| { + workspace.open_shared_screen(peer_id.unwrap(), cx) + }); + } + }) + .into_any() } fn take_editing_state(&mut self, cx: &mut ViewContext) -> bool { @@ -1496,23 +1543,32 @@ impl CollabPanel { enum AddChannel {} let tooltip_style = &theme.tooltip; + let mut channel_link = None; + let mut channel_tooltip_text = None; + let mut channel_icon = None; + let text = match section { Section::ActiveCall => { let channel_name = iife!({ let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?; - let name = self - .channel_store - .read(cx) - .channel_for_id(channel_id)? - .name - .as_str(); + let channel = self.channel_store.read(cx).channel_for_id(channel_id)?; + + channel_link = Some(channel.link()); + (channel_icon, channel_tooltip_text) = match channel.visibility { + proto::ChannelVisibility::Public => { + (Some("icons/public.svg"), Some("Copy public channel link.")) + } + proto::ChannelVisibility::Members => { + (Some("icons/hash.svg"), Some("Copy private channel link.")) + } + }; - Some(name) + Some(channel.name.as_str()) }); if let Some(name) = channel_name { - Cow::Owned(format!("#{}", name)) + Cow::Owned(format!("{}", name)) } else { Cow::Borrowed("Current Call") } @@ -1527,28 +1583,30 @@ impl CollabPanel { enum AddContact {} let button = match section { - Section::ActiveCall => Some( + Section::ActiveCall => channel_link.map(|channel_link| { + let channel_link_copy = channel_link.clone(); MouseEventHandler::new::(0, cx, |state, _| { render_icon_button( theme .collab_panel .leave_call_button .style_for(is_selected, state), - "icons/exit.svg", + "icons/link.svg", ) }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, _, cx| { - Self::leave_call(cx); + .on_click(MouseButton::Left, move |_, _, cx| { + let item = ClipboardItem::new(channel_link_copy.clone()); + cx.write_to_clipboard(item) }) .with_tooltip::( 0, - "Leave call", + channel_tooltip_text.unwrap(), None, tooltip_style.clone(), cx, - ), - ), + ) + }), Section::Contacts => Some( MouseEventHandler::new::(0, cx, |state, _| { render_icon_button( @@ -1633,6 +1691,21 @@ impl CollabPanel { theme.collab_panel.contact_username.container.margin.left, ), ) + } else if let Some(channel_icon) = channel_icon { + Some( + Svg::new(channel_icon) + .with_color(header_style.text.color) + .constrained() + .with_max_width(icon_size) + .with_max_height(icon_size) + .aligned() + .constrained() + .with_width(icon_size) + .contained() + .with_margin_right( + theme.collab_panel.contact_username.container.margin.left, + ), + ) } else { None }) @@ -1908,6 +1981,12 @@ impl CollabPanel { let channel_id = channel.id; let collab_theme = &theme.collab_panel; let has_children = self.channel_store.read(cx).has_children(channel_id); + let is_public = self + .channel_store + .read(cx) + .channel_for_id(channel_id) + .map(|channel| channel.visibility) + == Some(proto::ChannelVisibility::Public); let other_selected = self.selected_channel().map(|channel| channel.0.id) == Some(channel.id); let disclosed = has_children.then(|| !self.collapsed_channels.binary_search(&path).is_ok()); @@ -1965,12 +2044,16 @@ impl CollabPanel { Flex::::row() .with_child( - Svg::new("icons/hash.svg") - .with_color(collab_theme.channel_hash.color) - .constrained() - .with_width(collab_theme.channel_hash.width) - .aligned() - .left(), + Svg::new(if is_public { + "icons/public.svg" + } else { + "icons/hash.svg" + }) + .with_color(collab_theme.channel_hash.color) + .constrained() + .with_width(collab_theme.channel_hash.width) + .aligned() + .left(), ) .with_child({ let style = collab_theme.channel_name.inactive_state(); @@ -2275,7 +2358,7 @@ impl CollabPanel { .with_child(render_tree_branch( tree_branch, &row.name.text, - true, + false, vec2f(host_avatar_width, theme.row_height), cx.font_cache(), )) @@ -2308,6 +2391,62 @@ impl CollabPanel { .into_any() } + fn render_channel_chat( + &self, + channel_id: ChannelId, + theme: &theme::CollabPanel, + is_selected: bool, + ix: usize, + cx: &mut ViewContext, + ) -> AnyElement { + enum ChannelChat {} + let host_avatar_width = theme + .contact_avatar + .width + .or(theme.contact_avatar.height) + .unwrap_or(0.); + + MouseEventHandler::new::(ix as usize, cx, |state, cx| { + let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state); + let row = theme.project_row.in_state(is_selected).style_for(state); + + Flex::::row() + .with_child(render_tree_branch( + tree_branch, + &row.name.text, + true, + vec2f(host_avatar_width, theme.row_height), + cx.font_cache(), + )) + .with_child( + Svg::new("icons/conversations.svg") + .with_color(theme.channel_hash.color) + .constrained() + .with_width(theme.channel_hash.width) + .aligned() + .left(), + ) + .with_child( + Label::new("chat", theme.channel_name.text.clone()) + .contained() + .with_style(theme.channel_name.container) + .aligned() + .left() + .flex(1., true), + ) + .constrained() + .with_height(theme.row_height) + .contained() + .with_style(*theme.channel_row.style_for(is_selected, state)) + .with_padding_left(theme.channel_row.default_style().padding.left) + }) + .on_click(MouseButton::Left, move |_, this, cx| { + this.join_channel_chat(&JoinChannelChat { channel_id }, cx); + }) + .with_cursor_style(CursorStyle::PointingHand) + .into_any() + } + fn render_channel_invite( channel: Arc, channel_store: ModelHandle, @@ -2771,6 +2910,9 @@ impl CollabPanel { } } ListEntry::ParticipantScreen { peer_id, .. } => { + let Some(peer_id) = peer_id else { + return; + }; if let Some(workspace) = self.workspace.upgrade(cx) { workspace.update(cx, |workspace, cx| { workspace.open_shared_screen(*peer_id, cx) @@ -3498,6 +3640,14 @@ impl PartialEq for ListEntry { return channel_id == other_id; } } + ListEntry::ChannelChat { channel_id } => { + if let ListEntry::ChannelChat { + channel_id: other_id, + } = other + { + return channel_id == other_id; + } + } ListEntry::ChannelInvite(channel_1) => { if let ListEntry::ChannelInvite(channel_2) = other { return channel_1.id == channel_2.id; From 04a28fe831d2d044eff3405f4b034d767e07be0a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Oct 2023 13:32:08 -0600 Subject: [PATCH 174/180] Fix lint errors --- styles/src/style_tree/collab_modals.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/styles/src/style_tree/collab_modals.ts b/styles/src/style_tree/collab_modals.ts index 586e7be3f0bb3c68a14e198ee93f1c89b35cf6fb..6132ce5ff4dd1d831ea976d5723a9d9e69550b02 100644 --- a/styles/src/style_tree/collab_modals.ts +++ b/styles/src/style_tree/collab_modals.ts @@ -1,4 +1,4 @@ -import { StyleSet, StyleSets, Styles, useTheme } from "../theme" +import { StyleSets, useTheme } from "../theme" import { background, border, foreground, text } from "./components" import picker from "./picker" import { input } from "../component/input" @@ -44,7 +44,7 @@ export default function channel_modal(): any { ...text(theme.middle, "sans", styleset, "active"), } } - }); + }) const member_icon_style = icon_button({ variant: "ghost", From 13c7bbbac622cf44fcaa2ee7340de016385c00d3 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 17 Oct 2023 15:47:17 -0400 Subject: [PATCH 175/180] Shorten GitHub release message --- .github/workflows/release_actions.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index c1df24a8e5cb98bdbea594fecc0fd4090e222877..550eda882beb27099ea221a7eaf87569f6e7391d 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -20,9 +20,7 @@ jobs: id: get-content with: stringToTruncate: | - 📣 Zed ${{ github.event.release.tag_name }} was just released! - - Restart your Zed or head to ${{ steps.get-release-url.outputs.URL }} to grab it. + 📣 Zed [${{ github.event.release.tag_name }}](${{ steps.get-release-url.outputs.URL }}) was just released! ${{ github.event.release.body }} maxLength: 2000 From ed8a2c8793cb3f14bed3f32fb791f620b0667739 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Wed, 18 Oct 2023 10:35:11 -0400 Subject: [PATCH 176/180] revert change to return only the text and inside return all text inside markdown blocks --- crates/assistant/src/prompts.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 7aafe75920b351e7244f14858036a4aa9af64f6f..18e9e18f7d5f673b608130f970697b6c9b3eb5c8 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -243,7 +243,7 @@ pub fn generate_content_prompt( } prompts.push("Never make remarks about the output.".to_string()); prompts.push("Do not return any text, except the generated code.".to_string()); - prompts.push("Do not wrap your text in a Markdown block".to_string()); + prompts.push("Always wrap your code in a Markdown block".to_string()); let current_messages = [ChatCompletionRequestMessage { role: "user".to_string(), @@ -256,7 +256,11 @@ pub fn generate_content_prompt( tiktoken_rs::num_tokens_from_messages(model, ¤t_messages) { let max_token_count = tiktoken_rs::model::get_context_size(model); - let intermediate_token_count = max_token_count - current_token_count; + let intermediate_token_count = if max_token_count > current_token_count { + max_token_count - current_token_count + } else { + 0 + }; if intermediate_token_count < RESERVED_TOKENS_FOR_GENERATION { 0 From 99121ad5cd5cb2debded3a9029383688d270d04c Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 18 Oct 2023 18:05:13 +0200 Subject: [PATCH 177/180] buffer_search: Discard empty search suggestions. (#3136) Now when buffer_search::Deploy action is triggered (with cmd-f), we'll keep the previous query in query_editor (if there was one) instead of replacing it with empty query. This addresses this bit of feedback from Jose: > If no text is selected, `cmd + f` should not delete the text in the search bar when refocusing Release Notes: - Improved buffer search by not clearing out query editor when no text is selected and "buffer search: deploy" (default keybind: cmd-f) is triggered. --- crates/search/src/buffer_search.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 07c1eef3ff4257487b518e2e850fe80a143565eb..ef8c56f2a7b4ed17305ae01a1cd638980079ea0a 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -537,6 +537,7 @@ impl BufferSearchBar { self.active_searchable_item .as_ref() .map(|searchable_item| searchable_item.query_suggestion(cx)) + .filter(|suggestion| !suggestion.is_empty()) } pub fn set_replacement(&mut self, replacement: Option<&str>, cx: &mut ViewContext) { From cf429ba284e041dfdd73a1bcb6de95afc312e626 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 18 Oct 2023 12:31:12 -0400 Subject: [PATCH 178/180] v0.110.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 ecbe076711ccbf9c0657e828930d8e57442efc3c..833e2349564baf09477485beb0d4a6f4143191aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10041,7 +10041,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.109.0" +version = "0.110.0" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index aeabd4b45315b238be8e2fade5d0f13396324f1b..4e2b97f4a1fc11f422481f5d72b368e7a936538d 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.109.0" +version = "0.110.0" publish = false [lib] From 4e68b588be4bf2107d03749bbf60b0af9ca80da2 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 18 Oct 2023 13:17:17 -0400 Subject: [PATCH 179/180] collab 0.25.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 833e2349564baf09477485beb0d4a6f4143191aa..6b92170b88ed0417c5a256bd32691d1dba1cacfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1467,7 +1467,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.24.0" +version = "0.25.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index b91f0e1a5ff7d3b5d1c7061fc65738dc4a5a6321..bc6e09f3bd2fdefb2a042e1087352c4f3924df8d 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.24.0" +version = "0.25.0" publish = false [[bin]] From 655c9ece2decdb550192959175c9cd3cd88f26d1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 18 Oct 2023 11:16:14 -0700 Subject: [PATCH 180/180] Fix possibility of infinite loop in selections_with_autoclose_regions Previously, that method could loop forever if the editor's autoclose regions had unexpected selection ids. Co-authored-by: Piotr --- crates/editor/src/editor.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7aca4ab98fff05a73dcacdb55bf97913c164146f..d7557230858ab12aa262156c0cfbaad9b00383ca 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3286,8 +3286,10 @@ impl Editor { i = 0; } else if pair_state.range.start.to_offset(buffer) > range.end { break; - } else if pair_state.selection_id == selection.id { - enclosing = Some(pair_state); + } else { + if pair_state.selection_id == selection.id { + enclosing = Some(pair_state); + } i += 1; } }