From e802c072f7e3e32503355e44cbaa32b5e382e14d Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 5 Sep 2023 22:16:12 -0400 Subject: [PATCH 01/92] Start hacking in autocomplete docs --- crates/editor/src/editor.rs | 91 ++++--- crates/editor/src/hover_popover.rs | 392 ++++++++++++++++++++++++----- crates/theme/src/theme.rs | 8 +- styles/src/style_tree/editor.ts | 8 +- 4 files changed, 382 insertions(+), 117 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 24ffa64a6a..b1c5e35703 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -859,7 +859,6 @@ struct CompletionsMenu { id: CompletionId, initial_position: Anchor, buffer: ModelHandle, - project: Option>, completions: Arc<[Completion]>, match_candidates: Vec, matches: Arc<[StringMatch]>, @@ -903,42 +902,17 @@ impl CompletionsMenu { fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { enum CompletionTag {} - let language_servers = self.project.as_ref().map(|project| { - project - .read(cx) - .language_servers_for_buffer(self.buffer.read(cx), cx) - .filter(|(_, server)| server.capabilities().completion_provider.is_some()) - .map(|(adapter, server)| (server.server_id(), adapter.short_name)) - .collect::>() - }); - let needs_server_name = language_servers - .as_ref() - .map_or(false, |servers| servers.len() > 1); - - let get_server_name = - move |lookup_server_id: lsp::LanguageServerId| -> Option<&'static str> { - language_servers - .iter() - .flatten() - .find_map(|(server_id, server_name)| { - if *server_id == lookup_server_id { - Some(*server_name) - } else { - None - } - }) - }; - let widest_completion_ix = self .matches .iter() .enumerate() .max_by_key(|(_, mat)| { let completion = &self.completions[mat.candidate_id]; - let mut len = completion.label.text.chars().count(); + let documentation = &completion.lsp_completion.documentation; - if let Some(server_name) = get_server_name(completion.server_id) { - len += server_name.chars().count(); + let mut len = completion.label.text.chars().count(); + if let Some(lsp::Documentation::String(text)) = documentation { + len += text.chars().count(); } len @@ -948,8 +922,16 @@ impl CompletionsMenu { let completions = self.completions.clone(); let matches = self.matches.clone(); let selected_item = self.selected_item; - let container_style = style.autocomplete.container; - UniformList::new( + + let alongside_docs_text_style = TextStyle { + soft_wrap: true, + ..style.text.clone() + }; + let alongside_docs_width = style.autocomplete.alongside_docs_width; + let alongside_docs_container_style = style.autocomplete.alongside_docs_container; + let outer_container_style = style.autocomplete.container; + + let list = UniformList::new( self.list.clone(), matches.len(), cx, @@ -957,7 +939,9 @@ impl CompletionsMenu { let start_ix = range.start; for (ix, mat) in matches[range].iter().enumerate() { let completion = &completions[mat.candidate_id]; + let documentation = &completion.lsp_completion.documentation; let item_ix = start_ix + ix; + items.push( MouseEventHandler::new::( mat.candidate_id, @@ -986,22 +970,18 @@ impl CompletionsMenu { ), ); - if let Some(server_name) = get_server_name(completion.server_id) { + if let Some(lsp::Documentation::String(text)) = documentation { Flex::row() .with_child(completion_label) .with_children((|| { - if !needs_server_name { - return None; - } - let text_style = TextStyle { - color: style.autocomplete.server_name_color, + color: style.autocomplete.inline_docs_color, font_size: style.text.font_size - * style.autocomplete.server_name_size_percent, + * style.autocomplete.inline_docs_size_percent, ..style.text.clone() }; - let label = Text::new(server_name, text_style) + let label = Text::new(text.clone(), text_style) .aligned() .constrained() .dynamically(move |constraint, _, _| { @@ -1021,7 +1001,7 @@ impl CompletionsMenu { .with_style( style .autocomplete - .server_name_container, + .inline_docs_container, ) .into_any(), ) @@ -1065,10 +1045,29 @@ impl CompletionsMenu { } }, ) - .with_width_from_item(widest_completion_ix) - .contained() - .with_style(container_style) - .into_any() + .with_width_from_item(widest_completion_ix); + + Flex::row() + .with_child(list) + .with_children({ + let completion = &self.completions[selected_item]; + let documentation = &completion.lsp_completion.documentation; + + if let Some(lsp::Documentation::MarkupContent(content)) = documentation { + Some( + Text::new(content.value.clone(), alongside_docs_text_style) + .constrained() + .with_width(alongside_docs_width) + .contained() + .with_style(alongside_docs_container_style), + ) + } else { + None + } + }) + .contained() + .with_style(outer_container_style) + .into_any() } pub async fn filter(&mut self, query: Option<&str>, executor: Arc) { @@ -3150,7 +3149,6 @@ impl Editor { }); let id = post_inc(&mut self.next_completion_id); - let project = self.project.clone(); let task = cx.spawn(|this, mut cx| { async move { let menu = if let Some(completions) = completions.await.log_err() { @@ -3169,7 +3167,6 @@ impl Editor { }) .collect(), buffer, - project, completions: completions.into(), matches: Vec::new().into(), selected_item: 0, diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 553cb321c3..69b5562c34 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,6 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, - link_go_to_definition::{InlayHighlight, RangeInEditor}, + link_go_to_definition::{DocumentRange, InlayRange}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -8,12 +8,12 @@ use futures::FutureExt; use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, + fonts::{HighlightStyle, Underline, Weight}, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, + AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry}; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; -use rich_text::{new_paragraph, render_code, render_markdown_mut, RichText}; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; @@ -50,18 +50,19 @@ pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewC pub struct InlayHover { pub excerpt: ExcerptId, - pub range: InlayHighlight, + pub triggered_from: InlayOffset, + pub range: InlayRange, pub tooltip: HoverBlock, } pub fn find_hovered_hint_part( label_parts: Vec, - hint_start: InlayOffset, + hint_range: Range, hovered_offset: InlayOffset, ) -> Option<(InlayHintLabelPart, Range)> { - if hovered_offset >= hint_start { - let mut hovered_character = (hovered_offset - hint_start).0; - let mut part_start = hint_start; + if hovered_offset >= hint_range.start && hovered_offset <= hint_range.end { + let mut hovered_character = (hovered_offset - hint_range.start).0; + let mut part_start = hint_range.start; for part in label_parts { let part_len = part.value.chars().count(); if hovered_character > part_len { @@ -87,8 +88,10 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie }; if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { - if let RangeInEditor::Inlay(range) = symbol_range { - if range == &inlay_hover.range { + if let DocumentRange::Inlay(range) = symbol_range { + if (range.highlight_start..range.highlight_end) + .contains(&inlay_hover.triggered_from) + { // Hover triggered from same location as last time. Don't show again. return; } @@ -96,6 +99,18 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie hide_hover(editor, cx); } + let snapshot = editor.snapshot(cx); + // Don't request again if the location is the same as the previous request + if let Some(triggered_from) = editor.hover_state.triggered_from { + if inlay_hover.triggered_from + == snapshot + .display_snapshot + .anchor_to_inlay_offset(triggered_from) + { + return; + } + } + let task = cx.spawn(|this, mut cx| { async move { cx.background() @@ -107,7 +122,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie let hover_popover = InfoPopover { project: project.clone(), - symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), + symbol_range: DocumentRange::Inlay(inlay_hover.range), blocks: vec![inlay_hover.tooltip], language: None, rendered_content: None, @@ -311,7 +326,7 @@ fn show_hover( Some(InfoPopover { project: project.clone(), - symbol_range: RangeInEditor::Text(range), + symbol_range: DocumentRange::Text(range), blocks: hover_result.contents, language: hover_result.language, rendered_content: None, @@ -346,43 +361,237 @@ fn show_hover( } fn render_blocks( + theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, language: Option<&Arc>, -) -> RichText { - let mut data = RichText { - text: Default::default(), - highlights: Default::default(), - region_ranges: Default::default(), - regions: Default::default(), - }; + style: &EditorStyle, +) -> RenderedInfo { + let mut text = String::new(); + let mut highlights = Vec::new(); + let mut region_ranges = Vec::new(); + let mut regions = Vec::new(); for block in blocks { match &block.kind { HoverBlockKind::PlainText => { - new_paragraph(&mut data.text, &mut Vec::new()); - data.text.push_str(&block.text); + new_paragraph(&mut text, &mut Vec::new()); + text.push_str(&block.text); } + HoverBlockKind::Markdown => { - render_markdown_mut(&block.text, language_registry, language, &mut data) + use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; + + let mut bold_depth = 0; + let mut italic_depth = 0; + let mut link_url = None; + let mut current_language = None; + let mut list_stack = Vec::new(); + + for event in Parser::new_ext(&block.text, Options::all()) { + let prev_len = text.len(); + match event { + Event::Text(t) => { + if let Some(language) = ¤t_language { + render_code( + &mut text, + &mut highlights, + t.as_ref(), + language, + style, + ); + } else { + text.push_str(t.as_ref()); + + let mut style = HighlightStyle::default(); + if bold_depth > 0 { + style.weight = Some(Weight::BOLD); + } + if italic_depth > 0 { + style.italic = Some(true); + } + if let Some(link_url) = link_url.clone() { + region_ranges.push(prev_len..text.len()); + regions.push(RenderedRegion { + link_url: Some(link_url), + code: false, + }); + style.underline = Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style != HighlightStyle::default() { + let mut new_highlight = true; + if let Some((last_range, last_style)) = highlights.last_mut() { + if last_range.end == prev_len && last_style == &style { + last_range.end = text.len(); + new_highlight = false; + } + } + if new_highlight { + highlights.push((prev_len..text.len(), style)); + } + } + } + } + + Event::Code(t) => { + text.push_str(t.as_ref()); + region_ranges.push(prev_len..text.len()); + if link_url.is_some() { + highlights.push(( + prev_len..text.len(), + HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + }, + )); + } + regions.push(RenderedRegion { + code: true, + link_url: link_url.clone(), + }); + } + + Event::Start(tag) => match tag { + Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), + + Tag::Heading(_, _, _) => { + new_paragraph(&mut text, &mut list_stack); + bold_depth += 1; + } + + Tag::CodeBlock(kind) => { + new_paragraph(&mut text, &mut list_stack); + current_language = if let CodeBlockKind::Fenced(language) = kind { + language_registry + .language_for_name(language.as_ref()) + .now_or_never() + .and_then(Result::ok) + } else { + language.cloned() + } + } + + Tag::Emphasis => italic_depth += 1, + + Tag::Strong => bold_depth += 1, + + Tag::Link(_, url, _) => link_url = Some(url.to_string()), + + Tag::List(number) => { + list_stack.push((number, false)); + } + + Tag::Item => { + let len = list_stack.len(); + if let Some((list_number, has_content)) = list_stack.last_mut() { + *has_content = false; + if !text.is_empty() && !text.ends_with('\n') { + text.push('\n'); + } + for _ in 0..len - 1 { + text.push_str(" "); + } + if let Some(number) = list_number { + text.push_str(&format!("{}. ", number)); + *number += 1; + *has_content = false; + } else { + text.push_str("- "); + } + } + } + + _ => {} + }, + + Event::End(tag) => match tag { + Tag::Heading(_, _, _) => bold_depth -= 1, + Tag::CodeBlock(_) => current_language = None, + Tag::Emphasis => italic_depth -= 1, + Tag::Strong => bold_depth -= 1, + Tag::Link(_, _, _) => link_url = None, + Tag::List(_) => drop(list_stack.pop()), + _ => {} + }, + + Event::HardBreak => text.push('\n'), + + Event::SoftBreak => text.push(' '), + + _ => {} + } + } } + HoverBlockKind::Code { language } => { if let Some(language) = language_registry .language_for_name(language) .now_or_never() .and_then(Result::ok) { - render_code(&mut data.text, &mut data.highlights, &block.text, &language); + render_code(&mut text, &mut highlights, &block.text, &language, style); } else { - data.text.push_str(&block.text); + text.push_str(&block.text); } } } } - data.text = data.text.trim().to_string(); + RenderedInfo { + theme_id, + text: text.trim().to_string(), + highlights, + region_ranges, + regions, + } +} - data +fn render_code( + text: &mut String, + highlights: &mut Vec<(Range, HighlightStyle)>, + content: &str, + language: &Arc, + style: &EditorStyle, +) { + let prev_len = text.len(); + text.push_str(content); + for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { + if let Some(style) = highlight_id.style(&style.syntax) { + highlights.push((prev_len + range.start..prev_len + range.end, style)); + } + } +} + +fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { + let mut is_subsequent_paragraph_of_list = false; + if let Some((_, has_content)) = list_stack.last_mut() { + if *has_content { + is_subsequent_paragraph_of_list = true; + } else { + *has_content = true; + return; + } + } + + if !text.is_empty() { + if !text.ends_with('\n') { + text.push('\n'); + } + text.push('\n'); + } + for _ in 0..list_stack.len().saturating_sub(1) { + text.push_str(" "); + } + if is_subsequent_paragraph_of_list { + text.push_str(" "); + } } #[derive(Default)] @@ -415,8 +624,8 @@ impl HoverState { self.info_popover .as_ref() .map(|info_popover| match &info_popover.symbol_range { - RangeInEditor::Text(range) => &range.start, - RangeInEditor::Inlay(range) => &range.inlay_position, + DocumentRange::Text(range) => &range.start, + DocumentRange::Inlay(range) => &range.inlay_position, }) })?; let point = anchor.to_display_point(&snapshot.display_snapshot); @@ -442,10 +651,25 @@ impl HoverState { #[derive(Debug, Clone)] pub struct InfoPopover { pub project: ModelHandle, - symbol_range: RangeInEditor, + symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - rendered_content: Option, + rendered_content: Option, +} + +#[derive(Debug, Clone)] +struct RenderedInfo { + theme_id: usize, + text: String, + highlights: Vec<(Range, HighlightStyle)>, + region_ranges: Vec>, + regions: Vec, +} + +#[derive(Debug, Clone)] +struct RenderedRegion { + code: bool, + link_url: Option, } impl InfoPopover { @@ -454,24 +678,63 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { + if let Some(rendered) = &self.rendered_content { + if rendered.theme_id != style.theme_id { + self.rendered_content = None; + } + } + let rendered_content = self.rendered_content.get_or_insert_with(|| { render_blocks( + style.theme_id, &self.blocks, self.project.read(cx).languages(), self.language.as_ref(), + style, ) }); - MouseEventHandler::new::(0, cx, move |_, cx| { + MouseEventHandler::new::(0, cx, |_, cx| { + let mut region_id = 0; + let view_id = cx.view_id(); + let code_span_background_color = style.document_highlight_read_background; + let regions = rendered_content.regions.clone(); Flex::column() .scrollable::(1, None, cx) - .with_child(rendered_content.element( - style.syntax.clone(), - style.text.clone(), - code_span_background_color, - cx, - )) + .with_child( + Text::new(rendered_content.text.clone(), style.text.clone()) + .with_highlights(rendered_content.highlights.clone()) + .with_custom_runs( + rendered_content.region_ranges.clone(), + move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::( + MouseButton::Left, + move |_, _, cx| cx.platform().open_url(&url), + ), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }, + ) + .with_soft_wrap(true), + ) .contained() .with_style(style.hover_popover.container) }) @@ -564,15 +827,13 @@ mod tests { inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, link_go_to_definition::update_inlay_link_and_hover_points, test::editor_lsp_test_context::EditorLspTestContext, - InlayId, }; use collections::BTreeSet; - use gpui::fonts::{HighlightStyle, Underline, Weight}; + use gpui::fonts::Weight; use indoc::indoc; use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; use lsp::LanguageServerId; use project::{HoverBlock, HoverBlockKind}; - use rich_text::Highlight; use smol::stream::StreamExt; use unindent::Unindent; use util::test::marked_text_ranges; @@ -783,7 +1044,7 @@ mod tests { .await; cx.condition(|editor, _| editor.hover_state.visible()).await; - cx.editor(|editor, _| { + cx.editor(|editor, cx| { let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; assert_eq!( blocks, @@ -793,7 +1054,8 @@ mod tests { }], ); - let rendered = render_blocks(&blocks, &Default::default(), None); + let style = editor.style(cx); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); assert_eq!( rendered.text, code_str.trim(), @@ -985,7 +1247,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(&blocks, &Default::default(), None); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges @@ -996,21 +1258,8 @@ mod tests { rendered.text, expected_text, "wrong text for input {blocks:?}" ); - - let rendered_highlights: Vec<_> = rendered - .highlights - .iter() - .filter_map(|(range, highlight)| { - let style = match highlight { - Highlight::Id(id) => id.style(&style.syntax)?, - Highlight::Highlight(style) => style.clone(), - }; - Some((range.clone(), style)) - }) - .collect(); - assert_eq!( - rendered_highlights, expected_highlights, + rendered.highlights, expected_highlights, "wrong highlights for input {blocks:?}" ); } @@ -1244,16 +1493,25 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); + let entire_inlay_start = snapshot.display_point_to_inlay_offset( + inlay_range.start.to_display_point(&snapshot), + Bias::Left, + ); + + let expected_new_type_label_start = InlayOffset(entire_inlay_start.0 + ": ".len()); assert_eq!( popover.symbol_range, - RangeInEditor::Inlay(InlayHighlight { - inlay: InlayId::Hint(0), + DocumentRange::Inlay(InlayRange { inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - range: ": ".len()..": ".len() + new_type_label.len(), + highlight_start: expected_new_type_label_start, + highlight_end: InlayOffset( + expected_new_type_label_start.0 + new_type_label.len() + ), }), "Popover range should match the new type label part" ); @@ -1301,17 +1559,23 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); + let entire_inlay_start = snapshot.display_point_to_inlay_offset( + inlay_range.start.to_display_point(&snapshot), + Bias::Left, + ); + let expected_struct_label_start = + InlayOffset(entire_inlay_start.0 + ": ".len() + new_type_label.len() + "<".len()); assert_eq!( popover.symbol_range, - RangeInEditor::Inlay(InlayHighlight { - inlay: InlayId::Hint(0), + DocumentRange::Inlay(InlayRange { inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - range: ": ".len() + new_type_label.len() + "<".len() - ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(), + highlight_start: expected_struct_label_start, + highlight_end: InlayOffset(expected_struct_label_start.0 + struct_label.len()), }), "Popover range should match the struct label part" ); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index e534ba4260..9f7530ec18 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -867,9 +867,11 @@ pub struct AutocompleteStyle { pub selected_item: ContainerStyle, pub hovered_item: ContainerStyle, pub match_highlight: HighlightStyle, - pub server_name_container: ContainerStyle, - pub server_name_color: Color, - pub server_name_size_percent: f32, + pub inline_docs_container: ContainerStyle, + pub inline_docs_color: Color, + pub inline_docs_size_percent: f32, + pub alongside_docs_width: f32, + pub alongside_docs_container: ContainerStyle, } #[derive(Clone, Copy, Default, Deserialize, JsonSchema)] diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index e55a73c365..37d6c4ea1e 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -206,9 +206,11 @@ export default function editor(): any { match_highlight: foreground(theme.middle, "accent", "active"), background: background(theme.middle, "active"), }, - server_name_container: { padding: { left: 40 } }, - server_name_color: text(theme.middle, "sans", "disabled", {}).color, - server_name_size_percent: 0.75, + inline_docs_container: { padding: { left: 40 } }, + inline_docs_color: text(theme.middle, "sans", "disabled", {}).color, + inline_docs_size_percent: 0.75, + alongside_docs_width: 400, + alongside_docs_container: { padding: autocomplete_item.padding } }, diagnostic_header: { background: background(theme.middle), From 1584dae9c211c65120825314e4a6b5fbda39cd1d Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 5 Sep 2023 22:23:16 -0400 Subject: [PATCH 02/92] Actually display the correct completion's doc --- crates/editor/src/editor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b1c5e35703..ba2b50c1c1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1050,7 +1050,8 @@ impl CompletionsMenu { Flex::row() .with_child(list) .with_children({ - let completion = &self.completions[selected_item]; + let mat = &self.matches[selected_item]; + let completion = &self.completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; if let Some(lsp::Documentation::MarkupContent(content)) = documentation { From 370a3cafd0381b088ca1ac2122ecea968c7f9839 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 14 Sep 2023 15:24:46 -0400 Subject: [PATCH 03/92] Add markdown rendering to alongside completion docs --- crates/editor/src/editor.rs | 51 ++++--- crates/editor/src/markdown.rs | 246 ++++++++++++++++++++++++++++++++ styles/src/style_tree/editor.ts | 2 +- 3 files changed, 280 insertions(+), 19 deletions(-) create mode 100644 crates/editor/src/markdown.rs diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ba2b50c1c1..2035bd35f0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9,6 +9,7 @@ mod highlight_matching_bracket; mod hover_popover; pub mod items; mod link_go_to_definition; +mod markdown; mod mouse_context_menu; pub mod movement; pub mod multi_buffer; @@ -845,11 +846,12 @@ impl ContextMenu { fn render( &self, cursor_position: DisplayPoint, + editor: &Editor, style: EditorStyle, cx: &mut ViewContext, ) -> (DisplayPoint, AnyElement) { match self { - ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), + ContextMenu::Completions(menu) => (cursor_position, menu.render(editor, style, cx)), ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), } } @@ -899,7 +901,12 @@ impl CompletionsMenu { !self.matches.is_empty() } - fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { + fn render( + &self, + editor: &Editor, + style: EditorStyle, + cx: &mut ViewContext, + ) -> AnyElement { enum CompletionTag {} let widest_completion_ix = self @@ -923,18 +930,12 @@ impl CompletionsMenu { let matches = self.matches.clone(); let selected_item = self.selected_item; - let alongside_docs_text_style = TextStyle { - soft_wrap: true, - ..style.text.clone() - }; let alongside_docs_width = style.autocomplete.alongside_docs_width; let alongside_docs_container_style = style.autocomplete.alongside_docs_container; let outer_container_style = style.autocomplete.container; - let list = UniformList::new( - self.list.clone(), - matches.len(), - cx, + let list = UniformList::new(self.list.clone(), matches.len(), cx, { + let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; for (ix, mat) in matches[range].iter().enumerate() { @@ -1043,8 +1044,8 @@ impl CompletionsMenu { .into_any(), ); } - }, - ) + } + }) .with_width_from_item(widest_completion_ix); Flex::row() @@ -1055,12 +1056,26 @@ impl CompletionsMenu { let documentation = &completion.lsp_completion.documentation; if let Some(lsp::Documentation::MarkupContent(content)) = documentation { + let registry = editor + .project + .as_ref() + .unwrap() + .read(cx) + .languages() + .clone(); + let language = self.buffer.read(cx).language().map(Arc::clone); Some( - Text::new(content.value.clone(), alongside_docs_text_style) - .constrained() - .with_width(alongside_docs_width) - .contained() - .with_style(alongside_docs_container_style), + crate::markdown::render_markdown( + &content.value, + ®istry, + &language, + &style, + cx, + ) + .constrained() + .with_width(alongside_docs_width) + .contained() + .with_style(alongside_docs_container_style), ) } else { None @@ -3985,7 +4000,7 @@ impl Editor { ) -> Option<(DisplayPoint, AnyElement)> { self.context_menu .as_ref() - .map(|menu| menu.render(cursor_position, style, cx)) + .map(|menu| menu.render(cursor_position, self, style, cx)) } fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { diff --git a/crates/editor/src/markdown.rs b/crates/editor/src/markdown.rs new file mode 100644 index 0000000000..3ea8db34b3 --- /dev/null +++ b/crates/editor/src/markdown.rs @@ -0,0 +1,246 @@ +use std::ops::Range; +use std::sync::Arc; + +use futures::FutureExt; +use gpui::{ + elements::Text, + fonts::{HighlightStyle, Underline, Weight}, + platform::{CursorStyle, MouseButton}, + CursorRegion, MouseRegion, ViewContext, +}; +use language::{Language, LanguageRegistry}; +use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; + +use crate::{Editor, EditorStyle}; + +#[derive(Debug, Clone)] +struct RenderedRegion { + code: bool, + link_url: Option, +} + +pub fn render_markdown( + markdown: &str, + language_registry: &Arc, + language: &Option>, + style: &EditorStyle, + cx: &mut ViewContext, +) -> Text { + let mut text = String::new(); + let mut highlights = Vec::new(); + let mut region_ranges = Vec::new(); + let mut regions = Vec::new(); + + let mut bold_depth = 0; + let mut italic_depth = 0; + let mut link_url = None; + let mut current_language = None; + let mut list_stack = Vec::new(); + + for event in Parser::new_ext(&markdown, Options::all()) { + let prev_len = text.len(); + match event { + Event::Text(t) => { + if let Some(language) = ¤t_language { + render_code(&mut text, &mut highlights, t.as_ref(), language, style); + } else { + text.push_str(t.as_ref()); + + let mut style = HighlightStyle::default(); + if bold_depth > 0 { + style.weight = Some(Weight::BOLD); + } + if italic_depth > 0 { + style.italic = Some(true); + } + if let Some(link_url) = link_url.clone() { + region_ranges.push(prev_len..text.len()); + regions.push(RenderedRegion { + link_url: Some(link_url), + code: false, + }); + style.underline = Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style != HighlightStyle::default() { + let mut new_highlight = true; + if let Some((last_range, last_style)) = highlights.last_mut() { + if last_range.end == prev_len && last_style == &style { + last_range.end = text.len(); + new_highlight = false; + } + } + if new_highlight { + highlights.push((prev_len..text.len(), style)); + } + } + } + } + + Event::Code(t) => { + text.push_str(t.as_ref()); + region_ranges.push(prev_len..text.len()); + if link_url.is_some() { + highlights.push(( + prev_len..text.len(), + HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + }, + )); + } + regions.push(RenderedRegion { + code: true, + link_url: link_url.clone(), + }); + } + + Event::Start(tag) => match tag { + Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), + + Tag::Heading(_, _, _) => { + new_paragraph(&mut text, &mut list_stack); + bold_depth += 1; + } + + Tag::CodeBlock(kind) => { + new_paragraph(&mut text, &mut list_stack); + current_language = if let CodeBlockKind::Fenced(language) = kind { + language_registry + .language_for_name(language.as_ref()) + .now_or_never() + .and_then(Result::ok) + } else { + language.clone() + } + } + + Tag::Emphasis => italic_depth += 1, + + Tag::Strong => bold_depth += 1, + + Tag::Link(_, url, _) => link_url = Some(url.to_string()), + + Tag::List(number) => { + list_stack.push((number, false)); + } + + Tag::Item => { + let len = list_stack.len(); + if let Some((list_number, has_content)) = list_stack.last_mut() { + *has_content = false; + if !text.is_empty() && !text.ends_with('\n') { + text.push('\n'); + } + for _ in 0..len - 1 { + text.push_str(" "); + } + if let Some(number) = list_number { + text.push_str(&format!("{}. ", number)); + *number += 1; + *has_content = false; + } else { + text.push_str("- "); + } + } + } + + _ => {} + }, + + Event::End(tag) => match tag { + Tag::Heading(_, _, _) => bold_depth -= 1, + Tag::CodeBlock(_) => current_language = None, + Tag::Emphasis => italic_depth -= 1, + Tag::Strong => bold_depth -= 1, + Tag::Link(_, _, _) => link_url = None, + Tag::List(_) => drop(list_stack.pop()), + _ => {} + }, + + Event::HardBreak => text.push('\n'), + + Event::SoftBreak => text.push(' '), + + _ => {} + } + } + + let code_span_background_color = style.document_highlight_read_background; + let view_id = cx.view_id(); + let mut region_id = 0; + Text::new(text, style.text.clone()) + .with_highlights(highlights) + .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) +} + +fn render_code( + text: &mut String, + highlights: &mut Vec<(Range, HighlightStyle)>, + content: &str, + language: &Arc, + style: &EditorStyle, +) { + let prev_len = text.len(); + text.push_str(content); + for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { + if let Some(style) = highlight_id.style(&style.syntax) { + highlights.push((prev_len + range.start..prev_len + range.end, style)); + } + } +} + +fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { + let mut is_subsequent_paragraph_of_list = false; + if let Some((_, has_content)) = list_stack.last_mut() { + if *has_content { + is_subsequent_paragraph_of_list = true; + } else { + *has_content = true; + return; + } + } + + if !text.is_empty() { + if !text.ends_with('\n') { + text.push('\n'); + } + text.push('\n'); + } + for _ in 0..list_stack.len().saturating_sub(1) { + text.push_str(" "); + } + if is_subsequent_paragraph_of_list { + text.push_str(" "); + } +} diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index 37d6c4ea1e..e7717583a8 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -209,7 +209,7 @@ export default function editor(): any { inline_docs_container: { padding: { left: 40 } }, inline_docs_color: text(theme.middle, "sans", "disabled", {}).color, inline_docs_size_percent: 0.75, - alongside_docs_width: 400, + alongside_docs_width: 700, alongside_docs_container: { padding: autocomplete_item.padding } }, diagnostic_header: { From e8be14e5d64d6b574ea626ff50e59e84875ebf39 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 15 Sep 2023 11:51:57 -0400 Subject: [PATCH 04/92] Merge info popover's and autocomplete docs' markdown rendering --- crates/editor/src/hover_popover.rs | 230 ++++------------------------- crates/editor/src/markdown.rs | 102 ++++++++----- 2 files changed, 87 insertions(+), 245 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 69b5562c34..16ecb2dc01 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,7 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, link_go_to_definition::{DocumentRange, InlayRange}, + markdown::{self, RenderedRegion}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -8,7 +9,7 @@ use futures::FutureExt; use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, - fonts::{HighlightStyle, Underline, Weight}, + fonts::HighlightStyle, platform::{CursorStyle, MouseButton}, AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; @@ -364,7 +365,7 @@ fn render_blocks( theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, - language: Option<&Arc>, + language: &Option>, style: &EditorStyle, ) -> RenderedInfo { let mut text = String::new(); @@ -375,160 +376,20 @@ fn render_blocks( for block in blocks { match &block.kind { HoverBlockKind::PlainText => { - new_paragraph(&mut text, &mut Vec::new()); + markdown::new_paragraph(&mut text, &mut Vec::new()); text.push_str(&block.text); } - HoverBlockKind::Markdown => { - use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; - - let mut bold_depth = 0; - let mut italic_depth = 0; - let mut link_url = None; - let mut current_language = None; - let mut list_stack = Vec::new(); - - for event in Parser::new_ext(&block.text, Options::all()) { - let prev_len = text.len(); - match event { - Event::Text(t) => { - if let Some(language) = ¤t_language { - render_code( - &mut text, - &mut highlights, - t.as_ref(), - language, - style, - ); - } else { - text.push_str(t.as_ref()); - - let mut style = HighlightStyle::default(); - if bold_depth > 0 { - style.weight = Some(Weight::BOLD); - } - if italic_depth > 0 { - style.italic = Some(true); - } - if let Some(link_url) = link_url.clone() { - region_ranges.push(prev_len..text.len()); - regions.push(RenderedRegion { - link_url: Some(link_url), - code: false, - }); - style.underline = Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }); - } - - if style != HighlightStyle::default() { - let mut new_highlight = true; - if let Some((last_range, last_style)) = highlights.last_mut() { - if last_range.end == prev_len && last_style == &style { - last_range.end = text.len(); - new_highlight = false; - } - } - if new_highlight { - highlights.push((prev_len..text.len(), style)); - } - } - } - } - - Event::Code(t) => { - text.push_str(t.as_ref()); - region_ranges.push(prev_len..text.len()); - if link_url.is_some() { - highlights.push(( - prev_len..text.len(), - HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), - ..Default::default() - }, - )); - } - regions.push(RenderedRegion { - code: true, - link_url: link_url.clone(), - }); - } - - Event::Start(tag) => match tag { - Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), - - Tag::Heading(_, _, _) => { - new_paragraph(&mut text, &mut list_stack); - bold_depth += 1; - } - - Tag::CodeBlock(kind) => { - new_paragraph(&mut text, &mut list_stack); - current_language = if let CodeBlockKind::Fenced(language) = kind { - language_registry - .language_for_name(language.as_ref()) - .now_or_never() - .and_then(Result::ok) - } else { - language.cloned() - } - } - - Tag::Emphasis => italic_depth += 1, - - Tag::Strong => bold_depth += 1, - - Tag::Link(_, url, _) => link_url = Some(url.to_string()), - - Tag::List(number) => { - list_stack.push((number, false)); - } - - Tag::Item => { - let len = list_stack.len(); - if let Some((list_number, has_content)) = list_stack.last_mut() { - *has_content = false; - if !text.is_empty() && !text.ends_with('\n') { - text.push('\n'); - } - for _ in 0..len - 1 { - text.push_str(" "); - } - if let Some(number) = list_number { - text.push_str(&format!("{}. ", number)); - *number += 1; - *has_content = false; - } else { - text.push_str("- "); - } - } - } - - _ => {} - }, - - Event::End(tag) => match tag { - Tag::Heading(_, _, _) => bold_depth -= 1, - Tag::CodeBlock(_) => current_language = None, - Tag::Emphasis => italic_depth -= 1, - Tag::Strong => bold_depth -= 1, - Tag::Link(_, _, _) => link_url = None, - Tag::List(_) => drop(list_stack.pop()), - _ => {} - }, - - Event::HardBreak => text.push('\n'), - - Event::SoftBreak => text.push(' '), - - _ => {} - } - } - } + HoverBlockKind::Markdown => markdown::render_markdown_block( + &block.text, + language_registry, + language, + style, + &mut text, + &mut highlights, + &mut region_ranges, + &mut regions, + ), HoverBlockKind::Code { language } => { if let Some(language) = language_registry @@ -536,7 +397,13 @@ fn render_blocks( .now_or_never() .and_then(Result::ok) { - render_code(&mut text, &mut highlights, &block.text, &language, style); + markdown::render_code( + &mut text, + &mut highlights, + &block.text, + &language, + style, + ); } else { text.push_str(&block.text); } @@ -553,47 +420,6 @@ fn render_blocks( } } -fn render_code( - text: &mut String, - highlights: &mut Vec<(Range, HighlightStyle)>, - content: &str, - language: &Arc, - style: &EditorStyle, -) { - let prev_len = text.len(); - text.push_str(content); - for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { - if let Some(style) = highlight_id.style(&style.syntax) { - highlights.push((prev_len + range.start..prev_len + range.end, style)); - } - } -} - -fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { - let mut is_subsequent_paragraph_of_list = false; - if let Some((_, has_content)) = list_stack.last_mut() { - if *has_content { - is_subsequent_paragraph_of_list = true; - } else { - *has_content = true; - return; - } - } - - if !text.is_empty() { - if !text.ends_with('\n') { - text.push('\n'); - } - text.push('\n'); - } - for _ in 0..list_stack.len().saturating_sub(1) { - text.push_str(" "); - } - if is_subsequent_paragraph_of_list { - text.push_str(" "); - } -} - #[derive(Default)] pub struct HoverState { pub info_popover: Option, @@ -666,12 +492,6 @@ struct RenderedInfo { regions: Vec, } -#[derive(Debug, Clone)] -struct RenderedRegion { - code: bool, - link_url: Option, -} - impl InfoPopover { pub fn render( &mut self, @@ -689,7 +509,7 @@ impl InfoPopover { style.theme_id, &self.blocks, self.project.read(cx).languages(), - self.language.as_ref(), + &self.language, style, ) }); @@ -829,7 +649,7 @@ mod tests { test::editor_lsp_test_context::EditorLspTestContext, }; use collections::BTreeSet; - use gpui::fonts::Weight; + use gpui::fonts::{Underline, Weight}; use indoc::indoc; use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; use lsp::LanguageServerId; @@ -1055,7 +875,7 @@ mod tests { ); let style = editor.style(cx); - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); assert_eq!( rendered.text, code_str.trim(), @@ -1247,7 +1067,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges diff --git a/crates/editor/src/markdown.rs b/crates/editor/src/markdown.rs index 3ea8db34b3..df5041c0db 100644 --- a/crates/editor/src/markdown.rs +++ b/crates/editor/src/markdown.rs @@ -14,9 +14,9 @@ use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; use crate::{Editor, EditorStyle}; #[derive(Debug, Clone)] -struct RenderedRegion { - code: bool, - link_url: Option, +pub struct RenderedRegion { + pub code: bool, + pub link_url: Option, } pub fn render_markdown( @@ -31,6 +31,59 @@ pub fn render_markdown( let mut region_ranges = Vec::new(); let mut regions = Vec::new(); + render_markdown_block( + markdown, + language_registry, + language, + style, + &mut text, + &mut highlights, + &mut region_ranges, + &mut regions, + ); + + let code_span_background_color = style.document_highlight_read_background; + let view_id = cx.view_id(); + let mut region_id = 0; + Text::new(text, style.text.clone()) + .with_highlights(highlights) + .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) +} + +pub fn render_markdown_block( + markdown: &str, + language_registry: &Arc, + language: &Option>, + style: &EditorStyle, + text: &mut String, + highlights: &mut Vec<(Range, HighlightStyle)>, + region_ranges: &mut Vec>, + regions: &mut Vec, +) { let mut bold_depth = 0; let mut italic_depth = 0; let mut link_url = None; @@ -42,7 +95,7 @@ pub fn render_markdown( match event { Event::Text(t) => { if let Some(language) = ¤t_language { - render_code(&mut text, &mut highlights, t.as_ref(), language, style); + render_code(text, highlights, t.as_ref(), language, style); } else { text.push_str(t.as_ref()); @@ -102,15 +155,15 @@ pub fn render_markdown( } Event::Start(tag) => match tag { - Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), + Tag::Paragraph => new_paragraph(text, &mut list_stack), Tag::Heading(_, _, _) => { - new_paragraph(&mut text, &mut list_stack); + new_paragraph(text, &mut list_stack); bold_depth += 1; } Tag::CodeBlock(kind) => { - new_paragraph(&mut text, &mut list_stack); + new_paragraph(text, &mut list_stack); current_language = if let CodeBlockKind::Fenced(language) = kind { language_registry .language_for_name(language.as_ref()) @@ -171,40 +224,9 @@ pub fn render_markdown( _ => {} } } - - let code_span_background_color = style.document_highlight_read_background; - let view_id = cx.view_id(); - let mut region_id = 0; - Text::new(text, style.text.clone()) - .with_highlights(highlights) - .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) - }), - ); - } - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }) - .with_soft_wrap(true) } -fn render_code( +pub fn render_code( text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, content: &str, @@ -220,7 +242,7 @@ fn render_code( } } -fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { +pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { let mut is_subsequent_paragraph_of_list = false; if let Some((_, has_content)) = list_stack.last_mut() { if *has_content { From ca88717f0c3145b441f3efeb81d53d578b1a9c7e Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 15 Sep 2023 15:12:04 -0400 Subject: [PATCH 05/92] Make completion docs scrollable --- crates/editor/src/editor.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2035bd35f0..2f02ac59b0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1064,18 +1064,22 @@ impl CompletionsMenu { .languages() .clone(); let language = self.buffer.read(cx).language().map(Arc::clone); + + enum CompletionDocsMarkdown {} Some( - crate::markdown::render_markdown( - &content.value, - ®istry, - &language, - &style, - cx, - ) - .constrained() - .with_width(alongside_docs_width) - .contained() - .with_style(alongside_docs_container_style), + Flex::column() + .scrollable::(0, None, cx) + .with_child(crate::markdown::render_markdown( + &content.value, + ®istry, + &language, + &style, + cx, + )) + .constrained() + .with_width(alongside_docs_width) + .contained() + .with_style(alongside_docs_container_style), ) } else { None From 77ba25328cbeee3bfec5c3acef71fa8ff4394870 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 22 Sep 2023 15:10:48 -0400 Subject: [PATCH 06/92] Most of getting completion documentation resolved & cached MD parsing --- Cargo.lock | 2 +- crates/editor/Cargo.toml | 1 - crates/editor/src/editor.rs | 170 ++++++++++++++++---- crates/editor/src/hover_popover.rs | 6 +- crates/language/Cargo.toml | 1 + crates/language/src/buffer.rs | 2 + crates/language/src/language.rs | 1 + crates/{editor => language}/src/markdown.rs | 90 ++++++----- crates/language/src/proto.rs | 1 + crates/lsp/src/lsp.rs | 14 +- crates/project/src/lsp_command.rs | 1 + crates/zed/src/languages.rs | 4 +- 12 files changed, 217 insertions(+), 76 deletions(-) rename crates/{editor => language}/src/markdown.rs (79%) diff --git a/Cargo.lock b/Cargo.lock index c971846a5d..147760ab14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2404,7 +2404,6 @@ dependencies = [ "parking_lot 0.11.2", "postage", "project", - "pulldown-cmark", "rand 0.8.5", "rich_text", "rpc", @@ -3990,6 +3989,7 @@ dependencies = [ "lsp", "parking_lot 0.11.2", "postage", + "pulldown-cmark", "rand 0.8.5", "regex", "rpc", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 2c3d6227a9..d03e1c1106 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -57,7 +57,6 @@ log.workspace = true ordered-float.workspace = true parking_lot.workspace = true postage.workspace = true -pulldown-cmark = { version = "0.9.2", default-features = false } rand.workspace = true schemars.workspace = true serde.workspace = true diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2f02ac59b0..c0d2b4ee0b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9,7 +9,6 @@ mod highlight_matching_bracket; mod hover_popover; pub mod items; mod link_go_to_definition; -mod markdown; mod mouse_context_menu; pub mod movement; pub mod multi_buffer; @@ -78,6 +77,7 @@ pub use multi_buffer::{ ToPoint, }; use ordered_float::OrderedFloat; +use parking_lot::RwLock; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use rand::{seq::SliceRandom, thread_rng}; use rpc::proto::PeerId; @@ -788,10 +788,14 @@ enum ContextMenu { } impl ContextMenu { - fn select_first(&mut self, cx: &mut ViewContext) -> bool { + fn select_first( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_first(cx), + ContextMenu::Completions(menu) => menu.select_first(project, cx), ContextMenu::CodeActions(menu) => menu.select_first(cx), } true @@ -800,10 +804,14 @@ impl ContextMenu { } } - fn select_prev(&mut self, cx: &mut ViewContext) -> bool { + fn select_prev( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_prev(cx), + ContextMenu::Completions(menu) => menu.select_prev(project, cx), ContextMenu::CodeActions(menu) => menu.select_prev(cx), } true @@ -812,10 +820,14 @@ impl ContextMenu { } } - fn select_next(&mut self, cx: &mut ViewContext) -> bool { + fn select_next( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_next(cx), + ContextMenu::Completions(menu) => menu.select_next(project, cx), ContextMenu::CodeActions(menu) => menu.select_next(cx), } true @@ -824,10 +836,14 @@ impl ContextMenu { } } - fn select_last(&mut self, cx: &mut ViewContext) -> bool { + fn select_last( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_last(cx), + ContextMenu::Completions(menu) => menu.select_last(project, cx), ContextMenu::CodeActions(menu) => menu.select_last(cx), } true @@ -861,7 +877,7 @@ struct CompletionsMenu { id: CompletionId, initial_position: Anchor, buffer: ModelHandle, - completions: Arc<[Completion]>, + completions: Arc>>, match_candidates: Vec, matches: Arc<[StringMatch]>, selected_item: usize, @@ -869,34 +885,115 @@ struct CompletionsMenu { } impl CompletionsMenu { - fn select_first(&mut self, cx: &mut ViewContext) { + fn select_first( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } - fn select_prev(&mut self, cx: &mut ViewContext) { + fn select_prev( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { if self.selected_item > 0 { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } - fn select_next(&mut self, cx: &mut ViewContext) { + fn select_next( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { if self.selected_item + 1 < self.matches.len() { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } - fn select_last(&mut self, cx: &mut ViewContext) { + fn select_last( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } + fn attempt_resolve_selected_completion( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { + println!("attempt_resolve_selected_completion"); + let index = self.matches[dbg!(self.selected_item)].candidate_id; + dbg!(index); + let Some(project) = project else { + println!("no project"); + return; + }; + + let completions = self.completions.clone(); + let completions_guard = completions.read(); + let completion = &completions_guard[index]; + if completion.lsp_completion.documentation.is_some() { + println!("has existing documentation"); + return; + } + + let server_id = completion.server_id; + let completion = completion.lsp_completion.clone(); + drop(completions_guard); + + let Some(server) = project.read(cx).language_server_for_id(server_id) else { + println!("no server"); + return; + }; + + let can_resolve = server + .capabilities() + .completion_provider + .as_ref() + .and_then(|options| options.resolve_provider) + .unwrap_or(false); + if !dbg!(can_resolve) { + return; + } + + cx.spawn(|this, mut cx| async move { + println!("in spawn"); + let request = server.request::(completion); + let Some(completion_item) = request.await.log_err() else { + println!("errored"); + return; + }; + + if completion_item.documentation.is_some() { + println!("got new documentation"); + let mut completions = completions.write(); + completions[index].lsp_completion.documentation = completion_item.documentation; + println!("notifying"); + _ = this.update(&mut cx, |_, cx| cx.notify()); + } else { + println!("did not get anything"); + } + }) + .detach(); + } + fn visible(&self) -> bool { !self.matches.is_empty() } @@ -914,7 +1011,8 @@ impl CompletionsMenu { .iter() .enumerate() .max_by_key(|(_, mat)| { - let completion = &self.completions[mat.candidate_id]; + let completions = self.completions.read(); + let completion = &completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; let mut len = completion.label.text.chars().count(); @@ -938,6 +1036,7 @@ impl CompletionsMenu { let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; + let completions = completions.read(); for (ix, mat) in matches[range].iter().enumerate() { let completion = &completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; @@ -1052,7 +1151,8 @@ impl CompletionsMenu { .with_child(list) .with_children({ let mat = &self.matches[selected_item]; - let completion = &self.completions[mat.candidate_id]; + let completions = self.completions.read(); + let completion = &completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; if let Some(lsp::Documentation::MarkupContent(content)) = documentation { @@ -1069,13 +1169,12 @@ impl CompletionsMenu { Some( Flex::column() .scrollable::(0, None, cx) - .with_child(crate::markdown::render_markdown( - &content.value, - ®istry, - &language, - &style, - cx, - )) + // .with_child(language::markdown::render_markdown( + // &content.value, + // ®istry, + // &language, + // &style, + // )) .constrained() .with_width(alongside_docs_width) .contained() @@ -1130,17 +1229,20 @@ impl CompletionsMenu { } } + let completions = self.completions.read(); matches.sort_unstable_by_key(|mat| { - let completion = &self.completions[mat.candidate_id]; + let completion = &completions[mat.candidate_id]; ( completion.lsp_completion.sort_text.as_ref(), Reverse(OrderedFloat(mat.score)), completion.sort_key(), ) }); + drop(completions); for mat in &mut matches { - let filter_start = self.completions[mat.candidate_id].label.filter_range.start; + let completions = self.completions.read(); + let filter_start = completions[mat.candidate_id].label.filter_range.start; for position in &mut mat.positions { *position += filter_start; } @@ -3187,7 +3289,7 @@ impl Editor { }) .collect(), buffer, - completions: completions.into(), + completions: Arc::new(RwLock::new(completions.into())), matches: Vec::new().into(), selected_item: 0, list: Default::default(), @@ -3196,6 +3298,9 @@ impl Editor { if menu.matches.is_empty() { None } else { + _ = this.update(&mut cx, |editor, cx| { + menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); + }); Some(menu) } } else { @@ -3252,7 +3357,8 @@ impl Editor { .matches .get(action.item_ix.unwrap_or(completions_menu.selected_item))?; let buffer_handle = completions_menu.buffer; - let completion = completions_menu.completions.get(mat.candidate_id)?; + let completions = completions_menu.completions.read(); + let completion = completions.get(mat.candidate_id)?; let snippet; let text; @@ -5372,7 +5478,7 @@ impl Editor { if self .context_menu .as_mut() - .map(|menu| menu.select_last(cx)) + .map(|menu| menu.select_last(self.project.as_ref(), cx)) .unwrap_or(false) { return; @@ -5416,25 +5522,25 @@ impl Editor { pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_first(cx); + context_menu.select_first(self.project.as_ref(), cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_prev(cx); + context_menu.select_prev(self.project.as_ref(), cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_next(cx); + context_menu.select_next(self.project.as_ref(), cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_last(cx); + context_menu.select_last(self.project.as_ref(), cx); } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 16ecb2dc01..ea6eac3a66 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,7 +1,6 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, link_go_to_definition::{DocumentRange, InlayRange}, - markdown::{self, RenderedRegion}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -13,7 +12,10 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; -use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry}; +use language::{ + markdown::{self, RenderedRegion}, + Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, +}; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 4771fc7083..d5d5bdd1af 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -46,6 +46,7 @@ lazy_static.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true +pulldown-cmark = { version = "0.9.2", default-features = false } regex.workspace = true schemars.workspace = true serde.workspace = true diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 207c41e7cd..10585633ae 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,6 +1,7 @@ pub use crate::{ diagnostic_set::DiagnosticSet, highlight_map::{HighlightId, HighlightMap}, + markdown::RenderedMarkdown, proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT, }; use crate::{ @@ -148,6 +149,7 @@ pub struct Completion { pub old_range: Range, pub new_text: String, pub label: CodeLabel, + pub alongside_documentation: Option, pub server_id: LanguageServerId, pub lsp_completion: lsp::CompletionItem, } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 7d113a88af..12f76f1df3 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -2,6 +2,7 @@ mod buffer; mod diagnostic_set; mod highlight_map; pub mod language_settings; +pub mod markdown; mod outline; pub mod proto; mod syntax_map; diff --git a/crates/editor/src/markdown.rs b/crates/language/src/markdown.rs similarity index 79% rename from crates/editor/src/markdown.rs rename to crates/language/src/markdown.rs index df5041c0db..e033820a21 100644 --- a/crates/editor/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -1,6 +1,7 @@ use std::ops::Range; use std::sync::Arc; +use crate::{Language, LanguageRegistry}; use futures::FutureExt; use gpui::{ elements::Text, @@ -8,10 +9,50 @@ use gpui::{ platform::{CursorStyle, MouseButton}, CursorRegion, MouseRegion, ViewContext, }; -use language::{Language, LanguageRegistry}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; -use crate::{Editor, EditorStyle}; +#[derive(Debug, Clone)] +pub struct RenderedMarkdown { + text: String, + highlights: Vec<(Range, HighlightStyle)>, + region_ranges: Vec>, + regions: Vec, +} + +// impl RenderedMarkdown { +// pub fn render(&self, style: &theme::Editor, cx: &mut ViewContext) -> Text { +// let code_span_background_color = style.document_highlight_read_background; +// let view_id = cx.view_id(); +// let mut region_id = 0; +// Text::new(text, style.text.clone()) +// .with_highlights(highlights) +// .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { +// region_id += 1; +// let region = regions[ix].clone(); +// if let Some(url) = region.link_url { +// scene.push_cursor_region(CursorRegion { +// bounds, +// style: CursorStyle::PointingHand, +// }); +// scene.push_mouse_region( +// MouseRegion::new::(view_id, region_id, bounds) +// .on_click::(MouseButton::Left, move |_, _, cx| { +// cx.platform().open_url(&url) +// }), +// ); +// } +// if region.code { +// scene.push_quad(gpui::Quad { +// bounds, +// background: Some(code_span_background_color), +// border: Default::default(), +// corner_radii: (2.0).into(), +// }); +// } +// }) +// .with_soft_wrap(true) +// } +// } #[derive(Debug, Clone)] pub struct RenderedRegion { @@ -23,9 +64,8 @@ pub fn render_markdown( markdown: &str, language_registry: &Arc, language: &Option>, - style: &EditorStyle, - cx: &mut ViewContext, -) -> Text { + style: &theme::Editor, +) -> RenderedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); @@ -42,43 +82,19 @@ pub fn render_markdown( &mut regions, ); - let code_span_background_color = style.document_highlight_read_background; - let view_id = cx.view_id(); - let mut region_id = 0; - Text::new(text, style.text.clone()) - .with_highlights(highlights) - .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) - }), - ); - } - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }) - .with_soft_wrap(true) + RenderedMarkdown { + text, + highlights, + region_ranges, + regions, + } } pub fn render_markdown_block( markdown: &str, language_registry: &Arc, language: &Option>, - style: &EditorStyle, + style: &theme::Editor, text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, region_ranges: &mut Vec>, @@ -231,7 +247,7 @@ pub fn render_code( highlights: &mut Vec<(Range, HighlightStyle)>, content: &str, language: &Arc, - style: &EditorStyle, + style: &theme::Editor, ) { let prev_len = text.len(); text.push_str(content); diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index c4abe39d47..49b332b4fb 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -482,6 +482,7 @@ pub async fn deserialize_completion( lsp_completion.filter_text.as_deref(), ) }), + alongside_documentation: None, server_id: LanguageServerId(completion.server_id as usize), lsp_completion, }) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 33581721ae..b4099e2f6e 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -466,7 +466,10 @@ impl LanguageServer { completion_item: Some(CompletionItemCapability { snippet_support: Some(true), resolve_support: Some(CompletionItemCapabilityResolveSupport { - properties: vec!["additionalTextEdits".to_string()], + properties: vec![ + "documentation".to_string(), + "additionalTextEdits".to_string(), + ], }), ..Default::default() }), @@ -748,6 +751,15 @@ impl LanguageServer { ) } + // some child of string literal (be it "" or ``) which is the child of an attribute + + // + // + // + // + // const classes = "awesome "; + // + fn request_internal( next_id: &AtomicUsize, response_handlers: &Mutex>>, diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 8beaea5031..000fd3928c 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1462,6 +1462,7 @@ impl LspCommand for GetCompletions { lsp_completion.filter_text.as_deref(), ) }), + alongside_documentation: None, server_id, lsp_completion, } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 04e5292a7d..3b65255a3d 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -128,8 +128,8 @@ pub fn init( "tsx", tree_sitter_typescript::language_tsx(), vec![ - Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), - Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), + // Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + // Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), ], ); From fcaf48eb4965349bedc7daab6561060686c8c757 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 22 Sep 2023 17:03:40 -0400 Subject: [PATCH 07/92] Use completion item default `data` when provided --- crates/project/src/lsp_command.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 000fd3928c..16ebb7467b 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1358,7 +1358,7 @@ impl LspCommand for GetCompletions { } } } else { - Default::default() + Vec::new() }; let completions = buffer.read_with(&cx, |buffer, _| { @@ -1370,6 +1370,14 @@ impl LspCommand for GetCompletions { completions .into_iter() .filter_map(move |mut lsp_completion| { + if let Some(response_list) = &response_list { + if let Some(item_defaults) = &response_list.item_defaults { + if let Some(data) = &item_defaults.data { + lsp_completion.data = Some(data.clone()); + } + } + } + let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() { // If the language server provides a range to overwrite, then // check that the range is valid. From fe62423344fedb81c17a5f6aa81b56805fc8d2bc Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 28 Sep 2023 14:16:30 -0400 Subject: [PATCH 08/92] Asynchronously request completion documentation if not present --- crates/editor/src/editor.rs | 78 +++++++++++++++++++++++---------- crates/language/src/markdown.rs | 50 +++------------------ 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c0d2b4ee0b..d35a9dd30e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -48,9 +48,9 @@ use gpui::{ impl_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton}, - serde_json, AnyElement, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, - Entity, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, + serde_json, AnyElement, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, + CursorRegion, Element, Entity, ModelHandle, MouseRegion, Subscription, Task, View, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -119,6 +119,46 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); +pub fn render_rendered_markdown( + md: &language::RenderedMarkdown, + style: &EditorStyle, + cx: &mut ViewContext, +) -> Text { + enum RenderedRenderedMarkdown {} + + let md = md.clone(); + let code_span_background_color = style.document_highlight_read_background; + let view_id = cx.view_id(); + let mut region_id = 0; + Text::new(md.text, style.text.clone()) + .with_highlights(md.highlights) + .with_custom_runs(md.region_ranges, move |ix, bounds, scene, _| { + region_id += 1; + let region = md.regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) +} + #[derive(Clone, Deserialize, PartialEq, Default)] pub struct SelectNext { #[serde(default)] @@ -938,11 +978,8 @@ impl CompletionsMenu { project: Option<&ModelHandle>, cx: &mut ViewContext, ) { - println!("attempt_resolve_selected_completion"); - let index = self.matches[dbg!(self.selected_item)].candidate_id; - dbg!(index); + let index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { - println!("no project"); return; }; @@ -950,7 +987,6 @@ impl CompletionsMenu { let completions_guard = completions.read(); let completion = &completions_guard[index]; if completion.lsp_completion.documentation.is_some() { - println!("has existing documentation"); return; } @@ -959,7 +995,6 @@ impl CompletionsMenu { drop(completions_guard); let Some(server) = project.read(cx).language_server_for_id(server_id) else { - println!("no server"); return; }; @@ -969,26 +1004,21 @@ impl CompletionsMenu { .as_ref() .and_then(|options| options.resolve_provider) .unwrap_or(false); - if !dbg!(can_resolve) { + if !can_resolve { return; } cx.spawn(|this, mut cx| async move { - println!("in spawn"); let request = server.request::(completion); let Some(completion_item) = request.await.log_err() else { - println!("errored"); return; }; if completion_item.documentation.is_some() { - println!("got new documentation"); let mut completions = completions.write(); completions[index].lsp_completion.documentation = completion_item.documentation; - println!("notifying"); + drop(completions); _ = this.update(&mut cx, |_, cx| cx.notify()); - } else { - println!("did not get anything"); } }) .detach(); @@ -1169,12 +1199,16 @@ impl CompletionsMenu { Some( Flex::column() .scrollable::(0, None, cx) - // .with_child(language::markdown::render_markdown( - // &content.value, - // ®istry, - // &language, - // &style, - // )) + .with_child(render_rendered_markdown( + &language::markdown::render_markdown( + &content.value, + ®istry, + &language, + &style.theme, + ), + &style, + cx, + )) .constrained() .with_width(alongside_docs_width) .contained() diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index e033820a21..4ccd2955b6 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -3,57 +3,17 @@ use std::sync::Arc; use crate::{Language, LanguageRegistry}; use futures::FutureExt; -use gpui::{ - elements::Text, - fonts::{HighlightStyle, Underline, Weight}, - platform::{CursorStyle, MouseButton}, - CursorRegion, MouseRegion, ViewContext, -}; +use gpui::fonts::{HighlightStyle, Underline, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] pub struct RenderedMarkdown { - text: String, - highlights: Vec<(Range, HighlightStyle)>, - region_ranges: Vec>, - regions: Vec, + pub text: String, + pub highlights: Vec<(Range, HighlightStyle)>, + pub region_ranges: Vec>, + pub regions: Vec, } -// impl RenderedMarkdown { -// pub fn render(&self, style: &theme::Editor, cx: &mut ViewContext) -> Text { -// let code_span_background_color = style.document_highlight_read_background; -// let view_id = cx.view_id(); -// let mut region_id = 0; -// Text::new(text, style.text.clone()) -// .with_highlights(highlights) -// .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { -// region_id += 1; -// let region = regions[ix].clone(); -// if let Some(url) = region.link_url { -// scene.push_cursor_region(CursorRegion { -// bounds, -// style: CursorStyle::PointingHand, -// }); -// scene.push_mouse_region( -// MouseRegion::new::(view_id, region_id, bounds) -// .on_click::(MouseButton::Left, move |_, _, cx| { -// cx.platform().open_url(&url) -// }), -// ); -// } -// if region.code { -// scene.push_quad(gpui::Quad { -// bounds, -// background: Some(code_span_background_color), -// border: Default::default(), -// corner_radii: (2.0).into(), -// }); -// } -// }) -// .with_soft_wrap(true) -// } -// } - #[derive(Debug, Clone)] pub struct RenderedRegion { pub code: bool, From b8876f2b17307917bf24b20ffcdd090f9dc5126e Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 3 Oct 2023 10:58:08 -0400 Subject: [PATCH 09/92] Preparse documentation markdown when resolving completion --- crates/editor/src/editor.rs | 131 ++++++++++++++++------------- crates/editor/src/hover_popover.rs | 26 +++--- crates/language/src/buffer.rs | 44 +++++++++- crates/language/src/markdown.rs | 30 +++---- crates/language/src/proto.rs | 2 +- crates/project/src/lsp_command.rs | 2 +- 6 files changed, 144 insertions(+), 91 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d35a9dd30e..0f117cde1b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -119,12 +119,12 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); -pub fn render_rendered_markdown( - md: &language::RenderedMarkdown, +pub fn render_parsed_markdown( + md: &language::ParsedMarkdown, style: &EditorStyle, cx: &mut ViewContext, ) -> Text { - enum RenderedRenderedMarkdown {} + enum RenderedMarkdown {} let md = md.clone(); let code_span_background_color = style.document_highlight_read_background; @@ -141,7 +141,7 @@ pub fn render_rendered_markdown( style: CursorStyle::PointingHand, }); scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) + MouseRegion::new::(view_id, region_id, bounds) .on_click::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) }), @@ -831,11 +831,12 @@ impl ContextMenu { fn select_first( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_first(project, cx), + ContextMenu::Completions(menu) => menu.select_first(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_first(cx), } true @@ -847,11 +848,12 @@ impl ContextMenu { fn select_prev( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_prev(project, cx), + ContextMenu::Completions(menu) => menu.select_prev(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_prev(cx), } true @@ -863,11 +865,12 @@ impl ContextMenu { fn select_next( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_next(project, cx), + ContextMenu::Completions(menu) => menu.select_next(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_next(cx), } true @@ -879,11 +882,12 @@ impl ContextMenu { fn select_last( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_last(project, cx), + ContextMenu::Completions(menu) => menu.select_last(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_last(cx), } true @@ -928,60 +932,66 @@ impl CompletionsMenu { fn select_first( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn select_prev( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item > 0 { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn select_next( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item + 1 < self.matches.len() { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn select_last( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn attempt_resolve_selected_completion( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { let index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { return; }; + let language_registry = project.read(cx).languages().clone(); let completions = self.completions.clone(); let completions_guard = completions.read(); @@ -1008,16 +1018,27 @@ impl CompletionsMenu { return; } + // TODO: Do on background cx.spawn(|this, mut cx| async move { let request = server.request::(completion); let Some(completion_item) = request.await.log_err() else { return; }; - if completion_item.documentation.is_some() { + if let Some(lsp_documentation) = completion_item.documentation { + let documentation = language::prepare_completion_documentation( + &lsp_documentation, + &language_registry, + None, // TODO: Try to reasonably work out which language the completion is for + &style, + ); + let mut completions = completions.write(); - completions[index].lsp_completion.documentation = completion_item.documentation; + let completion = &mut completions[index]; + completion.documentation = documentation; + completion.lsp_completion.documentation = Some(lsp_documentation); drop(completions); + _ = this.update(&mut cx, |_, cx| cx.notify()); } }) @@ -1069,7 +1090,7 @@ impl CompletionsMenu { let completions = completions.read(); for (ix, mat) in matches[range].iter().enumerate() { let completion = &completions[mat.candidate_id]; - let documentation = &completion.lsp_completion.documentation; + let documentation = &completion.documentation; let item_ix = start_ix + ix; items.push( @@ -1100,7 +1121,9 @@ impl CompletionsMenu { ), ); - if let Some(lsp::Documentation::String(text)) = documentation { + if let Some(language::Documentation::SingleLine(text)) = + documentation + { Flex::row() .with_child(completion_label) .with_children((|| { @@ -1183,39 +1206,18 @@ impl CompletionsMenu { let mat = &self.matches[selected_item]; let completions = self.completions.read(); let completion = &completions[mat.candidate_id]; - let documentation = &completion.lsp_completion.documentation; + let documentation = &completion.documentation; - if let Some(lsp::Documentation::MarkupContent(content)) = documentation { - let registry = editor - .project - .as_ref() - .unwrap() - .read(cx) - .languages() - .clone(); - let language = self.buffer.read(cx).language().map(Arc::clone); + match documentation { + Some(language::Documentation::MultiLinePlainText(text)) => { + Some(Text::new(text.clone(), style.text.clone())) + } - enum CompletionDocsMarkdown {} - Some( - Flex::column() - .scrollable::(0, None, cx) - .with_child(render_rendered_markdown( - &language::markdown::render_markdown( - &content.value, - ®istry, - &language, - &style.theme, - ), - &style, - cx, - )) - .constrained() - .with_width(alongside_docs_width) - .contained() - .with_style(alongside_docs_container_style), - ) - } else { - None + Some(language::Documentation::MultiLineMarkdown(parsed)) => { + Some(render_parsed_markdown(parsed, &style, cx)) + } + + _ => None, } }) .contained() @@ -3333,7 +3335,11 @@ impl Editor { None } else { _ = this.update(&mut cx, |editor, cx| { - menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); + menu.attempt_resolve_selected_completion( + editor.project.as_ref(), + editor.style(cx).theme, + cx, + ); }); Some(menu) } @@ -5509,13 +5515,16 @@ impl Editor { return; } - if self - .context_menu - .as_mut() - .map(|menu| menu.select_last(self.project.as_ref(), cx)) - .unwrap_or(false) - { - return; + if self.context_menu.is_some() { + let style = self.style(cx).theme; + if self + .context_menu + .as_mut() + .map(|menu| menu.select_last(self.project.as_ref(), style, cx)) + .unwrap_or(false) + { + return; + } } if matches!(self.mode, EditorMode::SingleLine) { @@ -5555,26 +5564,30 @@ impl Editor { } pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_first(self.project.as_ref(), cx); + context_menu.select_first(self.project.as_ref(), style, cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_prev(self.project.as_ref(), cx); + context_menu.select_prev(self.project.as_ref(), style, cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_next(self.project.as_ref(), cx); + context_menu.select_next(self.project.as_ref(), style, cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_last(self.project.as_ref(), cx); + context_menu.select_last(self.project.as_ref(), style, cx); } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index ea6eac3a66..9341fa2da3 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -13,7 +13,7 @@ use gpui::{ AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; use language::{ - markdown::{self, RenderedRegion}, + markdown::{self, ParsedRegion}, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, }; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; @@ -367,9 +367,9 @@ fn render_blocks( theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, - language: &Option>, + language: Option>, style: &EditorStyle, -) -> RenderedInfo { +) -> ParsedInfo { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); @@ -382,10 +382,10 @@ fn render_blocks( text.push_str(&block.text); } - HoverBlockKind::Markdown => markdown::render_markdown_block( + HoverBlockKind::Markdown => markdown::parse_markdown_block( &block.text, language_registry, - language, + language.clone(), style, &mut text, &mut highlights, @@ -399,7 +399,7 @@ fn render_blocks( .now_or_never() .and_then(Result::ok) { - markdown::render_code( + markdown::highlight_code( &mut text, &mut highlights, &block.text, @@ -413,7 +413,7 @@ fn render_blocks( } } - RenderedInfo { + ParsedInfo { theme_id, text: text.trim().to_string(), highlights, @@ -482,16 +482,16 @@ pub struct InfoPopover { symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - rendered_content: Option, + rendered_content: Option, } #[derive(Debug, Clone)] -struct RenderedInfo { +struct ParsedInfo { theme_id: usize, text: String, highlights: Vec<(Range, HighlightStyle)>, region_ranges: Vec>, - regions: Vec, + regions: Vec, } impl InfoPopover { @@ -511,7 +511,7 @@ impl InfoPopover { style.theme_id, &self.blocks, self.project.read(cx).languages(), - &self.language, + self.language.clone(), style, ) }); @@ -877,7 +877,7 @@ mod tests { ); let style = editor.style(cx); - let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); assert_eq!( rendered.text, code_str.trim(), @@ -1069,7 +1069,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 10585633ae..344b470aa9 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,12 +1,13 @@ pub use crate::{ diagnostic_set::DiagnosticSet, highlight_map::{HighlightId, HighlightMap}, - markdown::RenderedMarkdown, + markdown::ParsedMarkdown, proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT, }; use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, language_settings::{language_settings, LanguageSettings}, + markdown, outline::OutlineItem, syntax_map::{ SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, @@ -144,12 +145,51 @@ pub struct Diagnostic { pub is_unnecessary: bool, } +pub fn prepare_completion_documentation( + documentation: &lsp::Documentation, + language_registry: &Arc, + language: Option>, + style: &theme::Editor, +) -> Option { + match documentation { + lsp::Documentation::String(text) => { + if text.lines().count() <= 1 { + Some(Documentation::SingleLine(text.clone())) + } else { + Some(Documentation::MultiLinePlainText(text.clone())) + } + } + + lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind { + lsp::MarkupKind::PlainText => { + if value.lines().count() <= 1 { + Some(Documentation::SingleLine(value.clone())) + } else { + Some(Documentation::MultiLinePlainText(value.clone())) + } + } + + lsp::MarkupKind::Markdown => { + let parsed = markdown::parse_markdown(value, language_registry, language, style); + Some(Documentation::MultiLineMarkdown(parsed)) + } + }, + } +} + +#[derive(Clone, Debug)] +pub enum Documentation { + SingleLine(String), + MultiLinePlainText(String), + MultiLineMarkdown(ParsedMarkdown), +} + #[derive(Clone, Debug)] pub struct Completion { pub old_range: Range, pub new_text: String, pub label: CodeLabel, - pub alongside_documentation: Option, + pub documentation: Option, pub server_id: LanguageServerId, pub lsp_completion: lsp::CompletionItem, } diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index 4ccd2955b6..c56a676378 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -7,31 +7,31 @@ use gpui::fonts::{HighlightStyle, Underline, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] -pub struct RenderedMarkdown { +pub struct ParsedMarkdown { pub text: String, pub highlights: Vec<(Range, HighlightStyle)>, pub region_ranges: Vec>, - pub regions: Vec, + pub regions: Vec, } #[derive(Debug, Clone)] -pub struct RenderedRegion { +pub struct ParsedRegion { pub code: bool, pub link_url: Option, } -pub fn render_markdown( +pub fn parse_markdown( markdown: &str, language_registry: &Arc, - language: &Option>, + language: Option>, style: &theme::Editor, -) -> RenderedMarkdown { +) -> ParsedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); let mut regions = Vec::new(); - render_markdown_block( + parse_markdown_block( markdown, language_registry, language, @@ -42,7 +42,7 @@ pub fn render_markdown( &mut regions, ); - RenderedMarkdown { + ParsedMarkdown { text, highlights, region_ranges, @@ -50,15 +50,15 @@ pub fn render_markdown( } } -pub fn render_markdown_block( +pub fn parse_markdown_block( markdown: &str, language_registry: &Arc, - language: &Option>, + language: Option>, style: &theme::Editor, text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, region_ranges: &mut Vec>, - regions: &mut Vec, + regions: &mut Vec, ) { let mut bold_depth = 0; let mut italic_depth = 0; @@ -71,7 +71,7 @@ pub fn render_markdown_block( match event { Event::Text(t) => { if let Some(language) = ¤t_language { - render_code(text, highlights, t.as_ref(), language, style); + highlight_code(text, highlights, t.as_ref(), language, style); } else { text.push_str(t.as_ref()); @@ -84,7 +84,7 @@ pub fn render_markdown_block( } if let Some(link_url) = link_url.clone() { region_ranges.push(prev_len..text.len()); - regions.push(RenderedRegion { + regions.push(ParsedRegion { link_url: Some(link_url), code: false, }); @@ -124,7 +124,7 @@ pub fn render_markdown_block( }, )); } - regions.push(RenderedRegion { + regions.push(ParsedRegion { code: true, link_url: link_url.clone(), }); @@ -202,7 +202,7 @@ pub fn render_markdown_block( } } -pub fn render_code( +pub fn highlight_code( text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, content: &str, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 49b332b4fb..957f4ee7fb 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -482,7 +482,7 @@ pub async fn deserialize_completion( lsp_completion.filter_text.as_deref(), ) }), - alongside_documentation: None, + documentation: None, server_id: LanguageServerId(completion.server_id as usize), lsp_completion, }) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 16ebb7467b..400dbe2abf 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1470,7 +1470,7 @@ impl LspCommand for GetCompletions { lsp_completion.filter_text.as_deref(), ) }), - alongside_documentation: None, + documentation: None, server_id, lsp_completion, } From ea6f366d2348135897fdb4a803097d4ffdfdab24 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 3 Oct 2023 11:58:49 -0400 Subject: [PATCH 10/92] If documentation exists and hasn't been parsed, do so at render and keep --- crates/editor/src/editor.rs | 46 ++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0f117cde1b..cabf73b581 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,10 +60,10 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, - point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, - CursorShape, Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, - LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, - TransactionId, + point_from_lsp, prepare_completion_documentation, AutoindentMode, BracketPair, Buffer, + CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, + File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, + Selection, SelectionGoal, TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -1075,21 +1075,37 @@ impl CompletionsMenu { }) .map(|(ix, _)| ix); + let project = editor.project.clone(); let completions = self.completions.clone(); let matches = self.matches.clone(); let selected_item = self.selected_item; - let alongside_docs_width = style.autocomplete.alongside_docs_width; - let alongside_docs_container_style = style.autocomplete.alongside_docs_container; - let outer_container_style = style.autocomplete.container; - let list = UniformList::new(self.list.clone(), matches.len(), cx, { let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; - let completions = completions.read(); + let mut completions = completions.write(); + for (ix, mat) in matches[range].iter().enumerate() { - let completion = &completions[mat.candidate_id]; + let completion = &mut completions[mat.candidate_id]; + + if completion.documentation.is_none() { + if let Some(lsp_docs) = &completion.lsp_completion.documentation { + let project = project + .as_ref() + .expect("It is impossible have LSP servers without a project"); + + let language_registry = project.read(cx).languages(); + + completion.documentation = prepare_completion_documentation( + lsp_docs, + language_registry, + None, + &style.theme, + ); + } + } + let documentation = &completion.documentation; let item_ix = start_ix + ix; @@ -1121,9 +1137,7 @@ impl CompletionsMenu { ), ); - if let Some(language::Documentation::SingleLine(text)) = - documentation - { + if let Some(Documentation::SingleLine(text)) = documentation { Flex::row() .with_child(completion_label) .with_children((|| { @@ -1209,11 +1223,11 @@ impl CompletionsMenu { let documentation = &completion.documentation; match documentation { - Some(language::Documentation::MultiLinePlainText(text)) => { + Some(Documentation::MultiLinePlainText(text)) => { Some(Text::new(text.clone(), style.text.clone())) } - Some(language::Documentation::MultiLineMarkdown(parsed)) => { + Some(Documentation::MultiLineMarkdown(parsed)) => { Some(render_parsed_markdown(parsed, &style, cx)) } @@ -1221,7 +1235,7 @@ impl CompletionsMenu { } }) .contained() - .with_style(outer_container_style) + .with_style(style.autocomplete.container) .into_any() } From a881b1f5fb4b2c28a48581a90e16dba1201afe7e Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 4 Oct 2023 17:36:51 -0400 Subject: [PATCH 11/92] Wait for language to load when parsing markdown --- crates/editor/src/editor.rs | 42 ++++--- crates/editor/src/hover_popover.rs | 169 +++++++++++++++++------------ crates/language/src/buffer.rs | 6 +- crates/language/src/markdown.rs | 12 +- 4 files changed, 137 insertions(+), 92 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cabf73b581..257abad41b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1018,7 +1018,6 @@ impl CompletionsMenu { return; } - // TODO: Do on background cx.spawn(|this, mut cx| async move { let request = server.request::(completion); let Some(completion_item) = request.await.log_err() else { @@ -1031,7 +1030,8 @@ impl CompletionsMenu { &language_registry, None, // TODO: Try to reasonably work out which language the completion is for &style, - ); + ) + .await; let mut completions = completions.write(); let completion = &mut completions[index]; @@ -1084,30 +1084,46 @@ impl CompletionsMenu { let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; - let mut completions = completions.write(); + let completions_guard = completions.read(); for (ix, mat) in matches[range].iter().enumerate() { - let completion = &mut completions[mat.candidate_id]; + let item_ix = start_ix + ix; + let candidate_id = mat.candidate_id; + let completion = &completions_guard[candidate_id]; - if completion.documentation.is_none() { + if item_ix == selected_item && completion.documentation.is_none() { if let Some(lsp_docs) = &completion.lsp_completion.documentation { let project = project .as_ref() .expect("It is impossible have LSP servers without a project"); - let language_registry = project.read(cx).languages(); + let lsp_docs = lsp_docs.clone(); + let lsp_docs = lsp_docs.clone(); + let language_registry = project.read(cx).languages().clone(); + let style = style.theme.clone(); + let completions = completions.clone(); - completion.documentation = prepare_completion_documentation( - lsp_docs, - language_registry, - None, - &style.theme, - ); + cx.spawn(|this, mut cx| async move { + let documentation = prepare_completion_documentation( + &lsp_docs, + &language_registry, + None, + &style, + ) + .await; + + this.update(&mut cx, |_, cx| { + let mut completions = completions.write(); + completions[candidate_id].documentation = documentation; + drop(completions); + cx.notify(); + }) + }) + .detach(); } } let documentation = &completion.documentation; - let item_ix = start_ix + ix; items.push( MouseEventHandler::new::( diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 9341fa2da3..585a335bd6 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -7,7 +7,7 @@ use crate::{ use futures::FutureExt; use gpui::{ actions, - elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, + elements::{Empty, Flex, MouseEventHandler, Padding, ParentElement, Text}, fonts::HighlightStyle, platform::{CursorStyle, MouseButton}, AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, @@ -128,7 +128,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie symbol_range: DocumentRange::Inlay(inlay_hover.range), blocks: vec![inlay_hover.tooltip], language: None, - rendered_content: None, + parsed_content: None, }; this.update(&mut cx, |this, cx| { @@ -332,7 +332,7 @@ fn show_hover( symbol_range: DocumentRange::Text(range), blocks: hover_result.contents, language: hover_result.language, - rendered_content: None, + parsed_content: None, }) }); @@ -363,12 +363,12 @@ fn show_hover( editor.hover_state.info_task = Some(task); } -fn render_blocks( +async fn render_blocks( theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, language: Option>, - style: &EditorStyle, + style: &theme::Editor, ) -> ParsedInfo { let mut text = String::new(); let mut highlights = Vec::new(); @@ -382,16 +382,19 @@ fn render_blocks( text.push_str(&block.text); } - HoverBlockKind::Markdown => markdown::parse_markdown_block( - &block.text, - language_registry, - language.clone(), - style, - &mut text, - &mut highlights, - &mut region_ranges, - &mut regions, - ), + HoverBlockKind::Markdown => { + markdown::parse_markdown_block( + &block.text, + language_registry, + language.clone(), + style, + &mut text, + &mut highlights, + &mut region_ranges, + &mut regions, + ) + .await + } HoverBlockKind::Code { language } => { if let Some(language) = language_registry @@ -482,7 +485,7 @@ pub struct InfoPopover { symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - rendered_content: Option, + parsed_content: Option, } #[derive(Debug, Clone)] @@ -500,63 +503,87 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { - if let Some(rendered) = &self.rendered_content { - if rendered.theme_id != style.theme_id { - self.rendered_content = None; + if let Some(parsed) = &self.parsed_content { + if parsed.theme_id != style.theme_id { + self.parsed_content = None; } } - let rendered_content = self.rendered_content.get_or_insert_with(|| { - render_blocks( - style.theme_id, - &self.blocks, - self.project.read(cx).languages(), - self.language.clone(), - style, - ) - }); + let rendered = if let Some(parsed) = &self.parsed_content { + let view_id = cx.view_id(); + let regions = parsed.regions.clone(); + let code_span_background_color = style.document_highlight_read_background; + + let mut region_id = 0; + + Text::new(parsed.text.clone(), style.text.clone()) + .with_highlights(parsed.highlights.clone()) + .with_custom_runs(parsed.region_ranges.clone(), move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) + .into_any() + } else { + let theme_id = style.theme_id; + let language_registry = self.project.read(cx).languages().clone(); + let blocks = self.blocks.clone(); + let language = self.language.clone(); + let style = style.theme.clone(); + cx.spawn(|this, mut cx| async move { + let blocks = + render_blocks(theme_id, &blocks, &language_registry, language, &style).await; + _ = this.update(&mut cx, |_, cx| cx.notify()); + blocks + }) + .detach(); + + Empty::new().into_any() + }; + + // let rendered_content = self.parsed_content.get_or_insert_with(|| { + // let language_registry = self.project.read(cx).languages().clone(); + // cx.spawn(|this, mut cx| async move { + // let blocks = render_blocks( + // style.theme_id, + // &self.blocks, + // &language_registry, + // self.language.clone(), + // style, + // ) + // .await; + // this.update(&mut cx, |_, cx| cx.notify()); + // blocks + // }) + // .shared() + // }); MouseEventHandler::new::(0, cx, |_, cx| { - let mut region_id = 0; - let view_id = cx.view_id(); - - let code_span_background_color = style.document_highlight_read_background; - let regions = rendered_content.regions.clone(); Flex::column() .scrollable::(1, None, cx) - .with_child( - Text::new(rendered_content.text.clone(), style.text.clone()) - .with_highlights(rendered_content.highlights.clone()) - .with_custom_runs( - rendered_content.region_ranges.clone(), - move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::( - MouseButton::Left, - move |_, _, cx| cx.platform().open_url(&url), - ), - ); - } - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }, - ) - .with_soft_wrap(true), - ) + .with_child(rendered) .contained() .with_style(style.hover_popover.container) }) @@ -877,7 +904,8 @@ mod tests { ); let style = editor.style(cx); - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = + smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); assert_eq!( rendered.text, code_str.trim(), @@ -1069,7 +1097,8 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = + smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges @@ -1339,7 +1368,7 @@ mod tests { ); assert_eq!( popover - .rendered_content + .parsed_content .as_ref() .expect("should have label text for new type hint") .text, @@ -1403,7 +1432,7 @@ mod tests { ); assert_eq!( popover - .rendered_content + .parsed_content .as_ref() .expect("should have label text for struct hint") .text, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 344b470aa9..971494ea4f 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -7,7 +7,7 @@ pub use crate::{ use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, language_settings::{language_settings, LanguageSettings}, - markdown, + markdown::parse_markdown, outline::OutlineItem, syntax_map::{ SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, @@ -145,7 +145,7 @@ pub struct Diagnostic { pub is_unnecessary: bool, } -pub fn prepare_completion_documentation( +pub async fn prepare_completion_documentation( documentation: &lsp::Documentation, language_registry: &Arc, language: Option>, @@ -170,7 +170,7 @@ pub fn prepare_completion_documentation( } lsp::MarkupKind::Markdown => { - let parsed = markdown::parse_markdown(value, language_registry, language, style); + let parsed = parse_markdown(value, language_registry, language, style).await; Some(Documentation::MultiLineMarkdown(parsed)) } }, diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index c56a676378..de5c7e8b09 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -2,7 +2,6 @@ use std::ops::Range; use std::sync::Arc; use crate::{Language, LanguageRegistry}; -use futures::FutureExt; use gpui::fonts::{HighlightStyle, Underline, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; @@ -20,7 +19,7 @@ pub struct ParsedRegion { pub link_url: Option, } -pub fn parse_markdown( +pub async fn parse_markdown( markdown: &str, language_registry: &Arc, language: Option>, @@ -40,7 +39,8 @@ pub fn parse_markdown( &mut highlights, &mut region_ranges, &mut regions, - ); + ) + .await; ParsedMarkdown { text, @@ -50,7 +50,7 @@ pub fn parse_markdown( } } -pub fn parse_markdown_block( +pub async fn parse_markdown_block( markdown: &str, language_registry: &Arc, language: Option>, @@ -143,8 +143,8 @@ pub fn parse_markdown_block( current_language = if let CodeBlockKind::Fenced(language) = kind { language_registry .language_for_name(language.as_ref()) - .now_or_never() - .and_then(Result::ok) + .await + .ok() } else { language.clone() } From 8dca4c3f9ac9b923fd9bef415fb0c6de19f5becb Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 5 Oct 2023 14:40:41 -0400 Subject: [PATCH 12/92] Don't need editor style to parse markdown --- crates/editor/src/editor.rs | 118 ++++++++++++---------- crates/editor/src/hover_popover.rs | 154 ++++++----------------------- crates/language/src/buffer.rs | 3 +- crates/language/src/markdown.rs | 63 ++++++------ 4 files changed, 133 insertions(+), 205 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 257abad41b..6b2be7c719 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,6 +60,7 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, + markdown::MarkdownHighlight, point_from_lsp, prepare_completion_documentation, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, @@ -120,21 +121,57 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); pub fn render_parsed_markdown( - md: &language::ParsedMarkdown, - style: &EditorStyle, + parsed: &language::ParsedMarkdown, + editor_style: &EditorStyle, cx: &mut ViewContext, ) -> Text { enum RenderedMarkdown {} - let md = md.clone(); - let code_span_background_color = style.document_highlight_read_background; + let parsed = parsed.clone(); let view_id = cx.view_id(); + let code_span_background_color = editor_style.document_highlight_read_background; + let mut region_id = 0; - Text::new(md.text, style.text.clone()) - .with_highlights(md.highlights) - .with_custom_runs(md.region_ranges, move |ix, bounds, scene, _| { + + Text::new(parsed.text, editor_style.text.clone()) + .with_highlights( + parsed + .highlights + .iter() + .filter_map(|(range, highlight)| { + let highlight = match highlight { + MarkdownHighlight::Style(style) => { + let mut highlight = HighlightStyle::default(); + + if style.italic { + highlight.italic = Some(true); + } + + if style.underline { + highlight.underline = Some(fonts::Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style.weight != fonts::Weight::default() { + highlight.weight = Some(style.weight); + } + + highlight + } + + MarkdownHighlight::Code(id) => id.style(&editor_style.syntax)?, + }; + + Some((range.clone(), highlight)) + }) + .collect::>(), + ) + .with_custom_runs(parsed.region_ranges, move |ix, bounds, scene, _| { region_id += 1; - let region = md.regions[ix].clone(); + let region = parsed.regions[ix].clone(); + if let Some(url) = region.link_url { scene.push_cursor_region(CursorRegion { bounds, @@ -147,6 +184,7 @@ pub fn render_parsed_markdown( }), ); } + if region.code { scene.push_quad(gpui::Quad { bounds, @@ -831,12 +869,11 @@ impl ContextMenu { fn select_first( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_first(project, style, cx), + ContextMenu::Completions(menu) => menu.select_first(project, cx), ContextMenu::CodeActions(menu) => menu.select_first(cx), } true @@ -848,12 +885,11 @@ impl ContextMenu { fn select_prev( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_prev(project, style, cx), + ContextMenu::Completions(menu) => menu.select_prev(project, cx), ContextMenu::CodeActions(menu) => menu.select_prev(cx), } true @@ -865,12 +901,11 @@ impl ContextMenu { fn select_next( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_next(project, style, cx), + ContextMenu::Completions(menu) => menu.select_next(project, cx), ContextMenu::CodeActions(menu) => menu.select_next(cx), } true @@ -882,12 +917,11 @@ impl ContextMenu { fn select_last( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_last(project, style, cx), + ContextMenu::Completions(menu) => menu.select_last(project, cx), ContextMenu::CodeActions(menu) => menu.select_last(cx), } true @@ -932,59 +966,54 @@ impl CompletionsMenu { fn select_first( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn select_prev( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item > 0 { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn select_next( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item + 1 < self.matches.len() { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn select_last( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn attempt_resolve_selected_completion( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { let index = self.matches[self.selected_item].candidate_id; @@ -1029,7 +1058,6 @@ impl CompletionsMenu { &lsp_documentation, &language_registry, None, // TODO: Try to reasonably work out which language the completion is for - &style, ) .await; @@ -1097,10 +1125,8 @@ impl CompletionsMenu { .as_ref() .expect("It is impossible have LSP servers without a project"); - let lsp_docs = lsp_docs.clone(); let lsp_docs = lsp_docs.clone(); let language_registry = project.read(cx).languages().clone(); - let style = style.theme.clone(); let completions = completions.clone(); cx.spawn(|this, mut cx| async move { @@ -1108,7 +1134,6 @@ impl CompletionsMenu { &lsp_docs, &language_registry, None, - &style, ) .await; @@ -3365,11 +3390,7 @@ impl Editor { None } else { _ = this.update(&mut cx, |editor, cx| { - menu.attempt_resolve_selected_completion( - editor.project.as_ref(), - editor.style(cx).theme, - cx, - ); + menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); }); Some(menu) } @@ -5545,16 +5566,13 @@ impl Editor { return; } - if self.context_menu.is_some() { - let style = self.style(cx).theme; - if self - .context_menu - .as_mut() - .map(|menu| menu.select_last(self.project.as_ref(), style, cx)) - .unwrap_or(false) - { - return; - } + if self + .context_menu + .as_mut() + .map(|menu| menu.select_last(self.project.as_ref(), cx)) + .unwrap_or(false) + { + return; } if matches!(self.mode, EditorMode::SingleLine) { @@ -5594,30 +5612,26 @@ impl Editor { } pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_first(self.project.as_ref(), style, cx); + context_menu.select_first(self.project.as_ref(), cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_prev(self.project.as_ref(), style, cx); + context_menu.select_prev(self.project.as_ref(), cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_next(self.project.as_ref(), style, cx); + context_menu.select_next(self.project.as_ref(), cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_last(self.project.as_ref(), style, cx); + context_menu.select_last(self.project.as_ref(), cx); } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 585a335bd6..51fe27e58a 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -8,13 +8,11 @@ use futures::FutureExt; use gpui::{ actions, elements::{Empty, Flex, MouseEventHandler, Padding, ParentElement, Text}, - fonts::HighlightStyle, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, + AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, }; use language::{ - markdown::{self, ParsedRegion}, - Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, + markdown, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, ParsedMarkdown, }; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use std::{ops::Range, sync::Arc, time::Duration}; @@ -363,13 +361,11 @@ fn show_hover( editor.hover_state.info_task = Some(task); } -async fn render_blocks( - theme_id: usize, +async fn parse_blocks( blocks: &[HoverBlock], language_registry: &Arc, language: Option>, - style: &theme::Editor, -) -> ParsedInfo { +) -> markdown::ParsedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); @@ -387,7 +383,6 @@ async fn render_blocks( &block.text, language_registry, language.clone(), - style, &mut text, &mut highlights, &mut region_ranges, @@ -402,13 +397,7 @@ async fn render_blocks( .now_or_never() .and_then(Result::ok) { - markdown::highlight_code( - &mut text, - &mut highlights, - &block.text, - &language, - style, - ); + markdown::highlight_code(&mut text, &mut highlights, &block.text, &language); } else { text.push_str(&block.text); } @@ -416,8 +405,7 @@ async fn render_blocks( } } - ParsedInfo { - theme_id, + ParsedMarkdown { text: text.trim().to_string(), highlights, region_ranges, @@ -485,16 +473,7 @@ pub struct InfoPopover { symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - parsed_content: Option, -} - -#[derive(Debug, Clone)] -struct ParsedInfo { - theme_id: usize, - text: String, - highlights: Vec<(Range, HighlightStyle)>, - region_ranges: Vec>, - regions: Vec, + parsed_content: Option, } impl InfoPopover { @@ -503,58 +482,14 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { - if let Some(parsed) = &self.parsed_content { - if parsed.theme_id != style.theme_id { - self.parsed_content = None; - } - } - let rendered = if let Some(parsed) = &self.parsed_content { - let view_id = cx.view_id(); - let regions = parsed.regions.clone(); - let code_span_background_color = style.document_highlight_read_background; - - let mut region_id = 0; - - Text::new(parsed.text.clone(), style.text.clone()) - .with_highlights(parsed.highlights.clone()) - .with_custom_runs(parsed.region_ranges.clone(), move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) - }), - ); - } - - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }) - .with_soft_wrap(true) - .into_any() + crate::render_parsed_markdown(parsed, style, cx).into_any() } else { - let theme_id = style.theme_id; let language_registry = self.project.read(cx).languages().clone(); let blocks = self.blocks.clone(); let language = self.language.clone(); - let style = style.theme.clone(); cx.spawn(|this, mut cx| async move { - let blocks = - render_blocks(theme_id, &blocks, &language_registry, language, &style).await; + let blocks = parse_blocks(&blocks, &language_registry, language).await; _ = this.update(&mut cx, |_, cx| cx.notify()); blocks }) @@ -563,23 +498,6 @@ impl InfoPopover { Empty::new().into_any() }; - // let rendered_content = self.parsed_content.get_or_insert_with(|| { - // let language_registry = self.project.read(cx).languages().clone(); - // cx.spawn(|this, mut cx| async move { - // let blocks = render_blocks( - // style.theme_id, - // &self.blocks, - // &language_registry, - // self.language.clone(), - // style, - // ) - // .await; - // this.update(&mut cx, |_, cx| cx.notify()); - // blocks - // }) - // .shared() - // }); - MouseEventHandler::new::(0, cx, |_, cx| { Flex::column() .scrollable::(1, None, cx) @@ -678,9 +596,12 @@ mod tests { test::editor_lsp_test_context::EditorLspTestContext, }; use collections::BTreeSet; - use gpui::fonts::{Underline, Weight}; + use gpui::fonts::Weight; use indoc::indoc; - use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; + use language::{ + language_settings::InlayHintSettings, markdown::MarkdownHighlightStyle, Diagnostic, + DiagnosticSet, + }; use lsp::LanguageServerId; use project::{HoverBlock, HoverBlockKind}; use smol::stream::StreamExt; @@ -893,7 +814,7 @@ mod tests { .await; cx.condition(|editor, _| editor.hover_state.visible()).await; - cx.editor(|editor, cx| { + cx.editor(|editor, _| { let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; assert_eq!( blocks, @@ -903,9 +824,7 @@ mod tests { }], ); - let style = editor.style(cx); - let rendered = - smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); + let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); assert_eq!( rendered.text, code_str.trim(), @@ -984,16 +903,17 @@ mod tests { #[gpui::test] fn test_render_blocks(cx: &mut gpui::TestAppContext) { + use markdown::MarkdownHighlight; + init_test(cx, |_| {}); cx.add_window(|cx| { let editor = Editor::single_line(None, cx); - let style = editor.style(cx); struct Row { blocks: Vec, expected_marked_text: String, - expected_styles: Vec, + expected_styles: Vec, } let rows = &[ @@ -1004,10 +924,10 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![HighlightStyle { - weight: Some(Weight::BOLD), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + weight: Weight::BOLD, ..Default::default() - }], + })], }, // Links Row { @@ -1016,13 +936,10 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }], + })], }, // Lists Row { @@ -1047,13 +964,10 @@ mod tests { - «c» - d" .unindent(), - expected_styles: vec![HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }], + })], }, // Multi-paragraph list items Row { @@ -1081,13 +995,10 @@ mod tests { - ten - six" .unindent(), - expected_styles: vec![HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }], + })], }, ]; @@ -1097,8 +1008,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = - smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); + let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 971494ea4f..d318a87b40 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -149,7 +149,6 @@ pub async fn prepare_completion_documentation( documentation: &lsp::Documentation, language_registry: &Arc, language: Option>, - style: &theme::Editor, ) -> Option { match documentation { lsp::Documentation::String(text) => { @@ -170,7 +169,7 @@ pub async fn prepare_completion_documentation( } lsp::MarkupKind::Markdown => { - let parsed = parse_markdown(value, language_registry, language, style).await; + let parsed = parse_markdown(value, language_registry, language).await; Some(Documentation::MultiLineMarkdown(parsed)) } }, diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index de5c7e8b09..9f29e7cb88 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -1,18 +1,31 @@ use std::ops::Range; use std::sync::Arc; -use crate::{Language, LanguageRegistry}; -use gpui::fonts::{HighlightStyle, Underline, Weight}; +use crate::{HighlightId, Language, LanguageRegistry}; +use gpui::fonts::Weight; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] pub struct ParsedMarkdown { pub text: String, - pub highlights: Vec<(Range, HighlightStyle)>, + pub highlights: Vec<(Range, MarkdownHighlight)>, pub region_ranges: Vec>, pub regions: Vec, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MarkdownHighlight { + Style(MarkdownHighlightStyle), + Code(HighlightId), +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct MarkdownHighlightStyle { + pub italic: bool, + pub underline: bool, + pub weight: Weight, +} + #[derive(Debug, Clone)] pub struct ParsedRegion { pub code: bool, @@ -23,7 +36,6 @@ pub async fn parse_markdown( markdown: &str, language_registry: &Arc, language: Option>, - style: &theme::Editor, ) -> ParsedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); @@ -34,7 +46,6 @@ pub async fn parse_markdown( markdown, language_registry, language, - style, &mut text, &mut highlights, &mut region_ranges, @@ -54,9 +65,8 @@ pub async fn parse_markdown_block( markdown: &str, language_registry: &Arc, language: Option>, - style: &theme::Editor, text: &mut String, - highlights: &mut Vec<(Range, HighlightStyle)>, + highlights: &mut Vec<(Range, MarkdownHighlight)>, region_ranges: &mut Vec>, regions: &mut Vec, ) { @@ -71,16 +81,16 @@ pub async fn parse_markdown_block( match event { Event::Text(t) => { if let Some(language) = ¤t_language { - highlight_code(text, highlights, t.as_ref(), language, style); + highlight_code(text, highlights, t.as_ref(), language); } else { text.push_str(t.as_ref()); - let mut style = HighlightStyle::default(); + let mut style = MarkdownHighlightStyle::default(); if bold_depth > 0 { - style.weight = Some(Weight::BOLD); + style.weight = Weight::BOLD; } if italic_depth > 0 { - style.italic = Some(true); + style.italic = true; } if let Some(link_url) = link_url.clone() { region_ranges.push(prev_len..text.len()); @@ -88,22 +98,22 @@ pub async fn parse_markdown_block( link_url: Some(link_url), code: false, }); - style.underline = Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }); + style.underline = true; } - if style != HighlightStyle::default() { + if style != MarkdownHighlightStyle::default() { let mut new_highlight = true; - if let Some((last_range, last_style)) = highlights.last_mut() { + if let Some((last_range, MarkdownHighlight::Style(last_style))) = + highlights.last_mut() + { if last_range.end == prev_len && last_style == &style { last_range.end = text.len(); new_highlight = false; } } if new_highlight { - highlights.push((prev_len..text.len(), style)); + let range = prev_len..text.len(); + highlights.push((range, MarkdownHighlight::Style(style))); } } } @@ -115,13 +125,10 @@ pub async fn parse_markdown_block( if link_url.is_some() { highlights.push(( prev_len..text.len(), - HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }, + }), )); } regions.push(ParsedRegion { @@ -204,17 +211,15 @@ pub async fn parse_markdown_block( pub fn highlight_code( text: &mut String, - highlights: &mut Vec<(Range, HighlightStyle)>, + highlights: &mut Vec<(Range, MarkdownHighlight)>, content: &str, language: &Arc, - style: &theme::Editor, ) { let prev_len = text.len(); text.push_str(content); for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { - if let Some(style) = highlight_id.style(&style.syntax) { - highlights.push((prev_len + range.start..prev_len + range.end, style)); - } + let highlight = MarkdownHighlight::Code(highlight_id); + highlights.push((prev_len + range.start..prev_len + range.end, highlight)); } } From 32a29cd4d32d447c00af9157c99f45d57d219cda Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 5 Oct 2023 23:57:01 -0400 Subject: [PATCH 13/92] Unbork info popover parsing/rendering and make better --- crates/editor/src/hover_popover.rs | 105 +++++++++++++---------------- 1 file changed, 45 insertions(+), 60 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 51fe27e58a..7917d57865 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -7,7 +7,7 @@ use crate::{ use futures::FutureExt; use gpui::{ actions, - elements::{Empty, Flex, MouseEventHandler, Padding, ParentElement, Text}, + elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, platform::{CursorStyle, MouseButton}, AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, }; @@ -121,12 +121,15 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie this.hover_state.diagnostic_popover = None; })?; + let language_registry = project.update(&mut cx, |p, _| p.languages().clone()); + let blocks = vec![inlay_hover.tooltip]; + let parsed_content = parse_blocks(&blocks, &language_registry, None).await; + let hover_popover = InfoPopover { project: project.clone(), symbol_range: DocumentRange::Inlay(inlay_hover.range), - blocks: vec![inlay_hover.tooltip], - language: None, - parsed_content: None, + blocks, + parsed_content, }; this.update(&mut cx, |this, cx| { @@ -304,35 +307,38 @@ fn show_hover( }); })?; - // Construct new hover popover from hover request - let hover_popover = hover_request.await.ok().flatten().and_then(|hover_result| { - if hover_result.is_empty() { - return None; + let hover_result = hover_request.await.ok().flatten(); + let hover_popover = match hover_result { + Some(hover_result) if !hover_result.is_empty() => { + // Create symbol range of anchors for highlighting and filtering of future requests. + let range = if let Some(range) = hover_result.range { + let start = snapshot + .buffer_snapshot + .anchor_in_excerpt(excerpt_id.clone(), range.start); + let end = snapshot + .buffer_snapshot + .anchor_in_excerpt(excerpt_id.clone(), range.end); + + start..end + } else { + anchor..anchor + }; + + let language_registry = project.update(&mut cx, |p, _| p.languages().clone()); + let blocks = hover_result.contents; + let language = hover_result.language; + let parsed_content = parse_blocks(&blocks, &language_registry, language).await; + + Some(InfoPopover { + project: project.clone(), + symbol_range: DocumentRange::Text(range), + blocks, + parsed_content, + }) } - // Create symbol range of anchors for highlighting and filtering - // of future requests. - let range = if let Some(range) = hover_result.range { - let start = snapshot - .buffer_snapshot - .anchor_in_excerpt(excerpt_id.clone(), range.start); - let end = snapshot - .buffer_snapshot - .anchor_in_excerpt(excerpt_id.clone(), range.end); - - start..end - } else { - anchor..anchor - }; - - Some(InfoPopover { - project: project.clone(), - symbol_range: DocumentRange::Text(range), - blocks: hover_result.contents, - language: hover_result.language, - parsed_content: None, - }) - }); + _ => None, + }; this.update(&mut cx, |this, cx| { if let Some(symbol_range) = hover_popover @@ -472,8 +478,7 @@ pub struct InfoPopover { pub project: ModelHandle, symbol_range: DocumentRange, pub blocks: Vec, - language: Option>, - parsed_content: Option, + parsed_content: ParsedMarkdown, } impl InfoPopover { @@ -482,26 +487,14 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { - let rendered = if let Some(parsed) = &self.parsed_content { - crate::render_parsed_markdown(parsed, style, cx).into_any() - } else { - let language_registry = self.project.read(cx).languages().clone(); - let blocks = self.blocks.clone(); - let language = self.language.clone(); - cx.spawn(|this, mut cx| async move { - let blocks = parse_blocks(&blocks, &language_registry, language).await; - _ = this.update(&mut cx, |_, cx| cx.notify()); - blocks - }) - .detach(); - - Empty::new().into_any() - }; - MouseEventHandler::new::(0, cx, |_, cx| { Flex::column() .scrollable::(1, None, cx) - .with_child(rendered) + .with_child(crate::render_parsed_markdown( + &self.parsed_content, + style, + cx, + )) .contained() .with_style(style.hover_popover.container) }) @@ -1277,11 +1270,7 @@ mod tests { "Popover range should match the new type label part" ); assert_eq!( - popover - .parsed_content - .as_ref() - .expect("should have label text for new type hint") - .text, + popover.parsed_content.text, format!("A tooltip for `{new_type_label}`"), "Rendered text should not anyhow alter backticks" ); @@ -1341,11 +1330,7 @@ mod tests { "Popover range should match the struct label part" ); assert_eq!( - popover - .parsed_content - .as_ref() - .expect("should have label text for struct hint") - .text, + popover.parsed_content.text, format!("A tooltip for {struct_label}"), "Rendered markdown element should remove backticks from text" ); From 9d8cff1275e8bfa6a6f08d48c286cdc9a8a5ab6e Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 6 Oct 2023 00:17:36 -0400 Subject: [PATCH 14/92] If documentation included in original completion then parse up front --- crates/editor/src/editor.rs | 51 +++++-------------------------- crates/project/src/lsp_command.rs | 27 ++++++++++++---- 2 files changed, 28 insertions(+), 50 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6b2be7c719..6585611040 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -61,10 +61,10 @@ pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, markdown::MarkdownHighlight, - point_from_lsp, prepare_completion_documentation, AutoindentMode, BracketPair, Buffer, - CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, - File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, - Selection, SelectionGoal, TransactionId, + point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, + CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, + Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, + TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -940,12 +940,11 @@ impl ContextMenu { fn render( &self, cursor_position: DisplayPoint, - editor: &Editor, style: EditorStyle, cx: &mut ViewContext, ) -> (DisplayPoint, AnyElement) { match self { - ContextMenu::Completions(menu) => (cursor_position, menu.render(editor, style, cx)), + ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), } } @@ -1077,12 +1076,7 @@ impl CompletionsMenu { !self.matches.is_empty() } - fn render( - &self, - editor: &Editor, - style: EditorStyle, - cx: &mut ViewContext, - ) -> AnyElement { + fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { enum CompletionTag {} let widest_completion_ix = self @@ -1103,7 +1097,6 @@ impl CompletionsMenu { }) .map(|(ix, _)| ix); - let project = editor.project.clone(); let completions = self.completions.clone(); let matches = self.matches.clone(); let selected_item = self.selected_item; @@ -1118,36 +1111,6 @@ impl CompletionsMenu { let item_ix = start_ix + ix; let candidate_id = mat.candidate_id; let completion = &completions_guard[candidate_id]; - - if item_ix == selected_item && completion.documentation.is_none() { - if let Some(lsp_docs) = &completion.lsp_completion.documentation { - let project = project - .as_ref() - .expect("It is impossible have LSP servers without a project"); - - let lsp_docs = lsp_docs.clone(); - let language_registry = project.read(cx).languages().clone(); - let completions = completions.clone(); - - cx.spawn(|this, mut cx| async move { - let documentation = prepare_completion_documentation( - &lsp_docs, - &language_registry, - None, - ) - .await; - - this.update(&mut cx, |_, cx| { - let mut completions = completions.write(); - completions[candidate_id].documentation = documentation; - drop(completions); - cx.notify(); - }) - }) - .detach(); - } - } - let documentation = &completion.documentation; items.push( @@ -4201,7 +4164,7 @@ impl Editor { ) -> Option<(DisplayPoint, AnyElement)> { self.context_menu .as_ref() - .map(|menu| menu.render(cursor_position, self, style, cx)) + .map(|menu| menu.render(cursor_position, style, cx)) } fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 400dbe2abf..c71b378da6 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -10,7 +10,7 @@ use futures::future; use gpui::{AppContext, AsyncAppContext, ModelHandle}; use language::{ language_settings::{language_settings, InlayHintKind}, - point_from_lsp, point_to_lsp, + point_from_lsp, point_to_lsp, prepare_completion_documentation, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, @@ -1341,7 +1341,7 @@ impl LspCommand for GetCompletions { async fn response_from_lsp( self, completions: Option, - _: ModelHandle, + project: ModelHandle, buffer: ModelHandle, server_id: LanguageServerId, cx: AsyncAppContext, @@ -1361,7 +1361,8 @@ impl LspCommand for GetCompletions { Vec::new() }; - let completions = buffer.read_with(&cx, |buffer, _| { + let completions = buffer.read_with(&cx, |buffer, cx| { + let language_registry = project.read(cx).languages().clone(); let language = buffer.language().cloned(); let snapshot = buffer.snapshot(); let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left); @@ -1453,14 +1454,28 @@ impl LspCommand for GetCompletions { } }; - let language = language.clone(); LineEnding::normalize(&mut new_text); + let language_registry = language_registry.clone(); + let language = language.clone(); + Some(async move { let mut label = None; - if let Some(language) = language { + if let Some(language) = language.as_ref() { language.process_completion(&mut lsp_completion).await; label = language.label_for_completion(&lsp_completion).await; } + + let documentation = if let Some(lsp_docs) = &lsp_completion.documentation { + prepare_completion_documentation( + lsp_docs, + &language_registry, + language.clone(), + ) + .await + } else { + None + }; + Completion { old_range, new_text, @@ -1470,7 +1485,7 @@ impl LspCommand for GetCompletions { lsp_completion.filter_text.as_deref(), ) }), - documentation: None, + documentation, server_id, lsp_completion, } From f18f870206bf274a0e9a9dfd92efd8d4ed4b5c82 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 6 Oct 2023 11:56:55 -0400 Subject: [PATCH 15/92] Re-enable language servers --- crates/zed/src/languages.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 3b65255a3d..04e5292a7d 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -128,8 +128,8 @@ pub fn init( "tsx", tree_sitter_typescript::language_tsx(), vec![ - // Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), - // Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), + Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), ], ); From 7020050b069474ab9145cef6578a95d68f217f43 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 9 Oct 2023 14:28:53 -0400 Subject: [PATCH 16/92] Fix `hover_popover.rs` after bad rebase --- crates/editor/src/editor.rs | 35 ++------ crates/editor/src/hover_popover.rs | 128 +++++++++++++---------------- crates/language/src/markdown.rs | 31 ++++++- 3 files changed, 92 insertions(+), 102 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6585611040..b8c9690b90 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,7 +60,6 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, - markdown::MarkdownHighlight, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, @@ -139,45 +138,21 @@ pub fn render_parsed_markdown( .highlights .iter() .filter_map(|(range, highlight)| { - let highlight = match highlight { - MarkdownHighlight::Style(style) => { - let mut highlight = HighlightStyle::default(); - - if style.italic { - highlight.italic = Some(true); - } - - if style.underline { - highlight.underline = Some(fonts::Underline { - thickness: 1.0.into(), - ..Default::default() - }); - } - - if style.weight != fonts::Weight::default() { - highlight.weight = Some(style.weight); - } - - highlight - } - - MarkdownHighlight::Code(id) => id.style(&editor_style.syntax)?, - }; - + let highlight = highlight.to_highlight_style(&editor_style.syntax)?; Some((range.clone(), highlight)) }) .collect::>(), ) - .with_custom_runs(parsed.region_ranges, move |ix, bounds, scene, _| { + .with_custom_runs(parsed.region_ranges, move |ix, bounds, cx| { region_id += 1; let region = parsed.regions[ix].clone(); if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { + cx.scene().push_cursor_region(CursorRegion { bounds, style: CursorStyle::PointingHand, }); - scene.push_mouse_region( + cx.scene().push_mouse_region( MouseRegion::new::(view_id, region_id, bounds) .on_click::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) @@ -186,7 +161,7 @@ pub fn render_parsed_markdown( } if region.code { - scene.push_quad(gpui::Quad { + cx.scene().push_quad(gpui::Quad { bounds, background: Some(code_span_background_color), border: Default::default(), diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 7917d57865..d5ccb481b2 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,6 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, - link_go_to_definition::{DocumentRange, InlayRange}, + link_go_to_definition::{InlayHighlight, RangeInEditor}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -51,19 +51,18 @@ pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewC pub struct InlayHover { pub excerpt: ExcerptId, - pub triggered_from: InlayOffset, - pub range: InlayRange, + pub range: InlayHighlight, pub tooltip: HoverBlock, } pub fn find_hovered_hint_part( label_parts: Vec, - hint_range: Range, + hint_start: InlayOffset, hovered_offset: InlayOffset, ) -> Option<(InlayHintLabelPart, Range)> { - if hovered_offset >= hint_range.start && hovered_offset <= hint_range.end { - let mut hovered_character = (hovered_offset - hint_range.start).0; - let mut part_start = hint_range.start; + if hovered_offset >= hint_start { + let mut hovered_character = (hovered_offset - hint_start).0; + let mut part_start = hint_start; for part in label_parts { let part_len = part.value.chars().count(); if hovered_character > part_len { @@ -89,10 +88,8 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie }; if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { - if let DocumentRange::Inlay(range) = symbol_range { - if (range.highlight_start..range.highlight_end) - .contains(&inlay_hover.triggered_from) - { + if let RangeInEditor::Inlay(range) = symbol_range { + if range == &inlay_hover.range { // Hover triggered from same location as last time. Don't show again. return; } @@ -100,18 +97,6 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie hide_hover(editor, cx); } - let snapshot = editor.snapshot(cx); - // Don't request again if the location is the same as the previous request - if let Some(triggered_from) = editor.hover_state.triggered_from { - if inlay_hover.triggered_from - == snapshot - .display_snapshot - .anchor_to_inlay_offset(triggered_from) - { - return; - } - } - let task = cx.spawn(|this, mut cx| { async move { cx.background() @@ -127,7 +112,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie let hover_popover = InfoPopover { project: project.clone(), - symbol_range: DocumentRange::Inlay(inlay_hover.range), + symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), blocks, parsed_content, }; @@ -331,7 +316,7 @@ fn show_hover( Some(InfoPopover { project: project.clone(), - symbol_range: DocumentRange::Text(range), + symbol_range: RangeInEditor::Text(range), blocks, parsed_content, }) @@ -449,8 +434,8 @@ impl HoverState { self.info_popover .as_ref() .map(|info_popover| match &info_popover.symbol_range { - DocumentRange::Text(range) => &range.start, - DocumentRange::Inlay(range) => &range.inlay_position, + RangeInEditor::Text(range) => &range.start, + RangeInEditor::Inlay(range) => &range.inlay_position, }) })?; let point = anchor.to_display_point(&snapshot.display_snapshot); @@ -476,7 +461,7 @@ impl HoverState { #[derive(Debug, Clone)] pub struct InfoPopover { pub project: ModelHandle, - symbol_range: DocumentRange, + symbol_range: RangeInEditor, pub blocks: Vec, parsed_content: ParsedMarkdown, } @@ -587,14 +572,12 @@ mod tests { inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, link_go_to_definition::update_inlay_link_and_hover_points, test::editor_lsp_test_context::EditorLspTestContext, + InlayId, }; use collections::BTreeSet; - use gpui::fonts::Weight; + use gpui::fonts::{HighlightStyle, Underline, Weight}; use indoc::indoc; - use language::{ - language_settings::InlayHintSettings, markdown::MarkdownHighlightStyle, Diagnostic, - DiagnosticSet, - }; + use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; use lsp::LanguageServerId; use project::{HoverBlock, HoverBlockKind}; use smol::stream::StreamExt; @@ -896,17 +879,16 @@ mod tests { #[gpui::test] fn test_render_blocks(cx: &mut gpui::TestAppContext) { - use markdown::MarkdownHighlight; - init_test(cx, |_| {}); cx.add_window(|cx| { let editor = Editor::single_line(None, cx); + let style = editor.style(cx); struct Row { blocks: Vec, expected_marked_text: String, - expected_styles: Vec, + expected_styles: Vec, } let rows = &[ @@ -917,10 +899,10 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - weight: Weight::BOLD, + expected_styles: vec![HighlightStyle { + weight: Some(Weight::BOLD), ..Default::default() - })], + }], }, // Links Row { @@ -929,10 +911,13 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - underline: true, + expected_styles: vec![HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), ..Default::default() - })], + }], }, // Lists Row { @@ -957,10 +942,13 @@ mod tests { - «c» - d" .unindent(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - underline: true, + expected_styles: vec![HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), ..Default::default() - })], + }], }, // Multi-paragraph list items Row { @@ -988,10 +976,13 @@ mod tests { - ten - six" .unindent(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - underline: true, + expected_styles: vec![HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), ..Default::default() - })], + }], }, ]; @@ -1012,8 +1003,18 @@ mod tests { rendered.text, expected_text, "wrong text for input {blocks:?}" ); + + let rendered_highlights: Vec<_> = rendered + .highlights + .iter() + .filter_map(|(range, highlight)| { + let highlight = highlight.to_highlight_style(&style.syntax)?; + Some((range.clone(), highlight)) + }) + .collect(); + assert_eq!( - rendered.highlights, expected_highlights, + rendered_highlights, expected_highlights, "wrong highlights for input {blocks:?}" ); } @@ -1247,25 +1248,16 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); - let entire_inlay_start = snapshot.display_point_to_inlay_offset( - inlay_range.start.to_display_point(&snapshot), - Bias::Left, - ); - - let expected_new_type_label_start = InlayOffset(entire_inlay_start.0 + ": ".len()); assert_eq!( popover.symbol_range, - DocumentRange::Inlay(InlayRange { + RangeInEditor::Inlay(InlayHighlight { + inlay: InlayId::Hint(0), inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - highlight_start: expected_new_type_label_start, - highlight_end: InlayOffset( - expected_new_type_label_start.0 + new_type_label.len() - ), + range: ": ".len()..": ".len() + new_type_label.len(), }), "Popover range should match the new type label part" ); @@ -1309,23 +1301,17 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); - let entire_inlay_start = snapshot.display_point_to_inlay_offset( - inlay_range.start.to_display_point(&snapshot), - Bias::Left, - ); - let expected_struct_label_start = - InlayOffset(entire_inlay_start.0 + ": ".len() + new_type_label.len() + "<".len()); assert_eq!( popover.symbol_range, - DocumentRange::Inlay(InlayRange { + RangeInEditor::Inlay(InlayHighlight { + inlay: InlayId::Hint(0), inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - highlight_start: expected_struct_label_start, - highlight_end: InlayOffset(expected_struct_label_start.0 + struct_label.len()), + range: ": ".len() + new_type_label.len() + "<".len() + ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(), }), "Popover range should match the struct label part" ); diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index 9f29e7cb88..8be15e81f6 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -2,7 +2,7 @@ use std::ops::Range; use std::sync::Arc; use crate::{HighlightId, Language, LanguageRegistry}; -use gpui::fonts::Weight; +use gpui::fonts::{self, HighlightStyle, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] @@ -19,6 +19,35 @@ pub enum MarkdownHighlight { Code(HighlightId), } +impl MarkdownHighlight { + pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option { + match self { + MarkdownHighlight::Style(style) => { + let mut highlight = HighlightStyle::default(); + + if style.italic { + highlight.italic = Some(true); + } + + if style.underline { + highlight.underline = Some(fonts::Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style.weight != fonts::Weight::default() { + highlight.weight = Some(style.weight); + } + + Some(highlight) + } + + MarkdownHighlight::Code(id) => id.style(theme), + } + } +} + #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct MarkdownHighlightStyle { pub italic: bool, From 354882f2c00d877defaf050ae8a34451ef5fa851 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 10 Oct 2023 00:16:15 -0400 Subject: [PATCH 17/92] Enable completion menu to resolve documentation when guest --- crates/collab/src/rpc.rs | 1 + crates/editor/src/editor.rs | 74 ++++++++++++-- crates/language/src/buffer.rs | 13 +-- crates/project/src/lsp_command.rs | 12 ++- crates/project/src/project.rs | 35 +++++++ crates/rpc/proto/zed.proto | 157 ++++++++++++++++-------------- crates/rpc/src/proto.rs | 7 ++ crates/rpc/src/rpc.rs | 2 +- 8 files changed, 207 insertions(+), 94 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5eb434e167..ed09cde061 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -224,6 +224,7 @@ impl Server { .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) + .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b8c9690b90..3143b19629 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,10 +60,10 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, - point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, - CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, - Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, - TransactionId, + markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, + Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, + IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, + SelectionGoal, TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -80,7 +80,7 @@ use ordered_float::OrderedFloat; use parking_lot::RwLock; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use rand::{seq::SliceRandom, thread_rng}; -use rpc::proto::PeerId; +use rpc::proto::{self, PeerId}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -999,7 +999,7 @@ impl CompletionsMenu { let completions = self.completions.clone(); let completions_guard = completions.read(); let completion = &completions_guard[index]; - if completion.lsp_completion.documentation.is_some() { + if completion.documentation.is_some() { return; } @@ -1007,6 +1007,57 @@ impl CompletionsMenu { let completion = completion.lsp_completion.clone(); drop(completions_guard); + if project.read(cx).is_remote() { + let Some(project_id) = project.read(cx).remote_id() else { + log::error!("Remote project without remote_id"); + return; + }; + + let client = project.read(cx).client(); + let request = proto::ResolveCompletionDocumentation { + project_id, + language_server_id: server_id.0 as u64, + lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), + }; + + cx.spawn(|this, mut cx| async move { + let Some(response) = client + .request(request) + .await + .context("completion documentation resolve proto request") + .log_err() + else { + return; + }; + + if response.text.is_empty() { + let mut completions = completions.write(); + let completion = &mut completions[index]; + completion.documentation = Some(Documentation::Undocumented); + } + + let documentation = if response.is_markdown { + Documentation::MultiLineMarkdown( + markdown::parse_markdown(&response.text, &language_registry, None).await, + ) + } else if response.text.lines().count() <= 1 { + Documentation::SingleLine(response.text) + } else { + Documentation::MultiLinePlainText(response.text) + }; + + let mut completions = completions.write(); + let completion = &mut completions[index]; + completion.documentation = Some(documentation); + drop(completions); + + _ = this.update(&mut cx, |_, cx| cx.notify()); + }) + .detach(); + + return; + } + let Some(server) = project.read(cx).language_server_for_id(server_id) else { return; }; @@ -1037,11 +1088,14 @@ impl CompletionsMenu { let mut completions = completions.write(); let completion = &mut completions[index]; - completion.documentation = documentation; - completion.lsp_completion.documentation = Some(lsp_documentation); + completion.documentation = Some(documentation); drop(completions); _ = this.update(&mut cx, |_, cx| cx.notify()); + } else { + let mut completions = completions.write(); + let completion = &mut completions[index]; + completion.documentation = Some(Documentation::Undocumented); } }) .detach(); @@ -1061,10 +1115,10 @@ impl CompletionsMenu { .max_by_key(|(_, mat)| { let completions = self.completions.read(); let completion = &completions[mat.candidate_id]; - let documentation = &completion.lsp_completion.documentation; + let documentation = &completion.documentation; let mut len = completion.label.text.chars().count(); - if let Some(lsp::Documentation::String(text)) = documentation { + if let Some(Documentation::SingleLine(text)) = documentation { len += text.chars().count(); } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d318a87b40..d8ebc1d445 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -149,28 +149,28 @@ pub async fn prepare_completion_documentation( documentation: &lsp::Documentation, language_registry: &Arc, language: Option>, -) -> Option { +) -> Documentation { match documentation { lsp::Documentation::String(text) => { if text.lines().count() <= 1 { - Some(Documentation::SingleLine(text.clone())) + Documentation::SingleLine(text.clone()) } else { - Some(Documentation::MultiLinePlainText(text.clone())) + Documentation::MultiLinePlainText(text.clone()) } } lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind { lsp::MarkupKind::PlainText => { if value.lines().count() <= 1 { - Some(Documentation::SingleLine(value.clone())) + Documentation::SingleLine(value.clone()) } else { - Some(Documentation::MultiLinePlainText(value.clone())) + Documentation::MultiLinePlainText(value.clone()) } } lsp::MarkupKind::Markdown => { let parsed = parse_markdown(value, language_registry, language).await; - Some(Documentation::MultiLineMarkdown(parsed)) + Documentation::MultiLineMarkdown(parsed) } }, } @@ -178,6 +178,7 @@ pub async fn prepare_completion_documentation( #[derive(Clone, Debug)] pub enum Documentation { + Undocumented, SingleLine(String), MultiLinePlainText(String), MultiLineMarkdown(ParsedMarkdown), diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index c71b378da6..72d79ca979 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1466,12 +1466,14 @@ impl LspCommand for GetCompletions { } let documentation = if let Some(lsp_docs) = &lsp_completion.documentation { - prepare_completion_documentation( - lsp_docs, - &language_registry, - language.clone(), + Some( + prepare_completion_documentation( + lsp_docs, + &language_registry, + language.clone(), + ) + .await, ) - .await } else { None }; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a50e02a631..f25309b9c6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -580,6 +580,7 @@ impl Project { client.add_model_request_handler(Self::handle_apply_code_action); client.add_model_request_handler(Self::handle_on_type_formatting); client.add_model_request_handler(Self::handle_inlay_hints); + client.add_model_request_handler(Self::handle_resolve_completion_documentation); client.add_model_request_handler(Self::handle_resolve_inlay_hint); client.add_model_request_handler(Self::handle_refresh_inlay_hints); client.add_model_request_handler(Self::handle_reload_buffers); @@ -7155,6 +7156,40 @@ impl Project { }) } + async fn handle_resolve_completion_documentation( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + let lsp_completion = serde_json::from_slice(&envelope.payload.lsp_completion)?; + + let completion = this + .read_with(&mut cx, |this, _| { + let id = LanguageServerId(envelope.payload.language_server_id as usize); + let Some(server) = this.language_server_for_id(id) else { + return Err(anyhow!("No language server {id}")); + }; + + Ok(server.request::(lsp_completion)) + })? + .await?; + + let mut is_markdown = false; + let text = match completion.documentation { + Some(lsp::Documentation::String(text)) => text, + + Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value })) => { + is_markdown = kind == lsp::MarkupKind::Markdown; + value + } + + _ => String::new(), + }; + + Ok(proto::ResolveCompletionDocumentationResponse { text, is_markdown }) + } + async fn handle_apply_code_action( this: ModelHandle, envelope: TypedEnvelope, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 3501e70e6a..e97ede3fee 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -89,88 +89,90 @@ message Envelope { FormatBuffersResponse format_buffers_response = 70; GetCompletions get_completions = 71; GetCompletionsResponse get_completions_response = 72; - ApplyCompletionAdditionalEdits apply_completion_additional_edits = 73; - ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 74; - GetCodeActions get_code_actions = 75; - GetCodeActionsResponse get_code_actions_response = 76; - GetHover get_hover = 77; - GetHoverResponse get_hover_response = 78; - ApplyCodeAction apply_code_action = 79; - ApplyCodeActionResponse apply_code_action_response = 80; - PrepareRename prepare_rename = 81; - PrepareRenameResponse prepare_rename_response = 82; - PerformRename perform_rename = 83; - PerformRenameResponse perform_rename_response = 84; - SearchProject search_project = 85; - SearchProjectResponse search_project_response = 86; + ResolveCompletionDocumentation resolve_completion_documentation = 73; + ResolveCompletionDocumentationResponse resolve_completion_documentation_response = 74; + ApplyCompletionAdditionalEdits apply_completion_additional_edits = 75; + ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 76; + GetCodeActions get_code_actions = 77; + GetCodeActionsResponse get_code_actions_response = 78; + GetHover get_hover = 79; + GetHoverResponse get_hover_response = 80; + ApplyCodeAction apply_code_action = 81; + ApplyCodeActionResponse apply_code_action_response = 82; + PrepareRename prepare_rename = 83; + PrepareRenameResponse prepare_rename_response = 84; + PerformRename perform_rename = 85; + PerformRenameResponse perform_rename_response = 86; + SearchProject search_project = 87; + SearchProjectResponse search_project_response = 88; - UpdateContacts update_contacts = 87; - UpdateInviteInfo update_invite_info = 88; - ShowContacts show_contacts = 89; + UpdateContacts update_contacts = 89; + UpdateInviteInfo update_invite_info = 90; + ShowContacts show_contacts = 91; - GetUsers get_users = 90; - FuzzySearchUsers fuzzy_search_users = 91; - UsersResponse users_response = 92; - RequestContact request_contact = 93; - RespondToContactRequest respond_to_contact_request = 94; - RemoveContact remove_contact = 95; + GetUsers get_users = 92; + FuzzySearchUsers fuzzy_search_users = 93; + UsersResponse users_response = 94; + RequestContact request_contact = 95; + RespondToContactRequest respond_to_contact_request = 96; + RemoveContact remove_contact = 97; - Follow follow = 96; - FollowResponse follow_response = 97; - UpdateFollowers update_followers = 98; - Unfollow unfollow = 99; - GetPrivateUserInfo get_private_user_info = 100; - GetPrivateUserInfoResponse get_private_user_info_response = 101; - UpdateDiffBase update_diff_base = 102; + Follow follow = 98; + FollowResponse follow_response = 99; + UpdateFollowers update_followers = 100; + Unfollow unfollow = 101; + GetPrivateUserInfo get_private_user_info = 102; + GetPrivateUserInfoResponse get_private_user_info_response = 103; + UpdateDiffBase update_diff_base = 104; - OnTypeFormatting on_type_formatting = 103; - OnTypeFormattingResponse on_type_formatting_response = 104; + OnTypeFormatting on_type_formatting = 105; + OnTypeFormattingResponse on_type_formatting_response = 106; - UpdateWorktreeSettings update_worktree_settings = 105; + UpdateWorktreeSettings update_worktree_settings = 107; - InlayHints inlay_hints = 106; - InlayHintsResponse inlay_hints_response = 107; - ResolveInlayHint resolve_inlay_hint = 108; - ResolveInlayHintResponse resolve_inlay_hint_response = 109; - RefreshInlayHints refresh_inlay_hints = 110; + InlayHints inlay_hints = 108; + InlayHintsResponse inlay_hints_response = 109; + ResolveInlayHint resolve_inlay_hint = 110; + ResolveInlayHintResponse resolve_inlay_hint_response = 111; + RefreshInlayHints refresh_inlay_hints = 112; - CreateChannel create_channel = 111; - CreateChannelResponse create_channel_response = 112; - InviteChannelMember invite_channel_member = 113; - RemoveChannelMember remove_channel_member = 114; - RespondToChannelInvite respond_to_channel_invite = 115; - UpdateChannels update_channels = 116; - JoinChannel join_channel = 117; - DeleteChannel delete_channel = 118; - GetChannelMembers get_channel_members = 119; - GetChannelMembersResponse get_channel_members_response = 120; - SetChannelMemberAdmin set_channel_member_admin = 121; - RenameChannel rename_channel = 122; - RenameChannelResponse rename_channel_response = 123; + CreateChannel create_channel = 113; + CreateChannelResponse create_channel_response = 114; + InviteChannelMember invite_channel_member = 115; + RemoveChannelMember remove_channel_member = 116; + RespondToChannelInvite respond_to_channel_invite = 117; + UpdateChannels update_channels = 118; + JoinChannel join_channel = 119; + DeleteChannel delete_channel = 120; + GetChannelMembers get_channel_members = 121; + GetChannelMembersResponse get_channel_members_response = 122; + SetChannelMemberAdmin set_channel_member_admin = 123; + RenameChannel rename_channel = 124; + RenameChannelResponse rename_channel_response = 125; - JoinChannelBuffer join_channel_buffer = 124; - JoinChannelBufferResponse join_channel_buffer_response = 125; - UpdateChannelBuffer update_channel_buffer = 126; - LeaveChannelBuffer leave_channel_buffer = 127; - UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 128; - RejoinChannelBuffers rejoin_channel_buffers = 129; - RejoinChannelBuffersResponse rejoin_channel_buffers_response = 130; - AckBufferOperation ack_buffer_operation = 143; + JoinChannelBuffer join_channel_buffer = 126; + JoinChannelBufferResponse join_channel_buffer_response = 127; + UpdateChannelBuffer update_channel_buffer = 128; + LeaveChannelBuffer leave_channel_buffer = 129; + UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 130; + RejoinChannelBuffers rejoin_channel_buffers = 131; + RejoinChannelBuffersResponse rejoin_channel_buffers_response = 132; + AckBufferOperation ack_buffer_operation = 145; - JoinChannelChat join_channel_chat = 131; - JoinChannelChatResponse join_channel_chat_response = 132; - LeaveChannelChat leave_channel_chat = 133; - SendChannelMessage send_channel_message = 134; - SendChannelMessageResponse send_channel_message_response = 135; - ChannelMessageSent channel_message_sent = 136; - GetChannelMessages get_channel_messages = 137; - GetChannelMessagesResponse get_channel_messages_response = 138; - RemoveChannelMessage remove_channel_message = 139; - AckChannelMessage ack_channel_message = 144; + JoinChannelChat join_channel_chat = 133; + JoinChannelChatResponse join_channel_chat_response = 134; + LeaveChannelChat leave_channel_chat = 135; + SendChannelMessage send_channel_message = 136; + SendChannelMessageResponse send_channel_message_response = 137; + ChannelMessageSent channel_message_sent = 138; + GetChannelMessages get_channel_messages = 139; + GetChannelMessagesResponse get_channel_messages_response = 140; + RemoveChannelMessage remove_channel_message = 141; + AckChannelMessage ack_channel_message = 146; - LinkChannel link_channel = 140; - UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; // current max: 144 + LinkChannel link_channel = 142; + UnlinkChannel unlink_channel = 143; + MoveChannel move_channel = 144; // current max: 146 } } @@ -832,6 +834,17 @@ message ResolveState { } } +message ResolveCompletionDocumentation { + uint64 project_id = 1; + uint64 language_server_id = 2; + bytes lsp_completion = 3; +} + +message ResolveCompletionDocumentationResponse { + string text = 1; + bool is_markdown = 2; +} + message ResolveInlayHint { uint64 project_id = 1; uint64 buffer_id = 2; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index f0d7937f6f..abadada328 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -205,6 +205,8 @@ messages!( (OnTypeFormattingResponse, Background), (InlayHints, Background), (InlayHintsResponse, Background), + (ResolveCompletionDocumentation, Background), + (ResolveCompletionDocumentationResponse, Background), (ResolveInlayHint, Background), (ResolveInlayHintResponse, Background), (RefreshInlayHints, Foreground), @@ -318,6 +320,10 @@ request_messages!( (PrepareRename, PrepareRenameResponse), (OnTypeFormatting, OnTypeFormattingResponse), (InlayHints, InlayHintsResponse), + ( + ResolveCompletionDocumentation, + ResolveCompletionDocumentationResponse + ), (ResolveInlayHint, ResolveInlayHintResponse), (RefreshInlayHints, Ack), (ReloadBuffers, ReloadBuffersResponse), @@ -381,6 +387,7 @@ entity_messages!( PerformRename, OnTypeFormatting, InlayHints, + ResolveCompletionDocumentation, ResolveInlayHint, RefreshInlayHints, PrepareRename, diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 942672b94b..5ba531a50e 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 64; +pub const PROTOCOL_VERSION: u32 = 65; From f5af5f7334a7b523090c0741029dde913cd00c2c Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 10 Oct 2023 09:27:18 -0400 Subject: [PATCH 18/92] Avoid leaving selected item index past end of matches list Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3143b19629..9b276f002c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1332,6 +1332,7 @@ impl CompletionsMenu { } self.matches = matches.into(); + self.selected_item = 0; } } From 801af95a13e0332af3f9a4d1a1d62c47bea07529 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 10 Oct 2023 10:08:29 -0400 Subject: [PATCH 19/92] Make completion documentation scroll & fix accompanying panic from tag Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 26 +++++++---- crates/editor/src/hover_popover.rs | 4 +- crates/gpui/src/elements/flex.rs | 75 ++++++++++++++++++------------ 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9b276f002c..06482dbbc6 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -119,7 +119,7 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); -pub fn render_parsed_markdown( +pub fn render_parsed_markdown( parsed: &language::ParsedMarkdown, editor_style: &EditorStyle, cx: &mut ViewContext, @@ -153,7 +153,7 @@ pub fn render_parsed_markdown( style: CursorStyle::PointingHand, }); cx.scene().push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) + MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds) .on_click::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) }), @@ -1247,6 +1247,8 @@ impl CompletionsMenu { }) .with_width_from_item(widest_completion_ix); + enum MultiLineDocumentation {} + Flex::row() .with_child(list) .with_children({ @@ -1256,13 +1258,21 @@ impl CompletionsMenu { let documentation = &completion.documentation; match documentation { - Some(Documentation::MultiLinePlainText(text)) => { - Some(Text::new(text.clone(), style.text.clone())) - } + Some(Documentation::MultiLinePlainText(text)) => Some( + Flex::column() + .scrollable::(0, None, cx) + .with_child( + Text::new(text.clone(), style.text.clone()).with_soft_wrap(true), + ), + ), - Some(Documentation::MultiLineMarkdown(parsed)) => { - Some(render_parsed_markdown(parsed, &style, cx)) - } + Some(Documentation::MultiLineMarkdown(parsed)) => Some( + Flex::column() + .scrollable::(0, None, cx) + .with_child(render_parsed_markdown::( + parsed, &style, cx, + )), + ), _ => None, } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index d5ccb481b2..e8901ad6c1 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -474,8 +474,8 @@ impl InfoPopover { ) -> AnyElement { MouseEventHandler::new::(0, cx, |_, cx| { Flex::column() - .scrollable::(1, None, cx) - .with_child(crate::render_parsed_markdown( + .scrollable::(0, None, cx) + .with_child(crate::render_parsed_markdown::( &self.parsed_content, style, cx, diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index cdce0423fd..ba387c5e48 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -2,7 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc}; use crate::{ json::{self, ToJson, Value}, - AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, Vector2FExt, ViewContext, + AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, TypeTag, Vector2FExt, + ViewContext, }; use pathfinder_geometry::{ rect::RectF, @@ -10,10 +11,10 @@ use pathfinder_geometry::{ }; use serde_json::json; -#[derive(Default)] struct ScrollState { scroll_to: Cell>, scroll_position: Cell, + type_tag: TypeTag, } pub struct Flex { @@ -66,8 +67,14 @@ impl Flex { where Tag: 'static, { - let scroll_state = cx.default_element_state::>(element_id); - scroll_state.read(cx).scroll_to.set(scroll_to); + let scroll_state = cx.element_state::>( + element_id, + Rc::new(ScrollState { + scroll_to: Cell::new(scroll_to), + scroll_position: Default::default(), + type_tag: TypeTag::new::(), + }), + ); self.scroll_state = Some((scroll_state, cx.handle().id())); self } @@ -276,38 +283,44 @@ impl Element for Flex { if let Some((scroll_state, id)) = &self.scroll_state { let scroll_state = scroll_state.read(cx).clone(); cx.scene().push_mouse_region( - crate::MouseRegion::new::(*id, 0, bounds) - .on_scroll({ - let axis = self.axis; - move |e, _: &mut V, cx| { - if remaining_space < 0. { - let scroll_delta = e.delta.raw(); + crate::MouseRegion::from_handlers( + scroll_state.type_tag, + *id, + 0, + bounds, + Default::default(), + ) + .on_scroll({ + let axis = self.axis; + move |e, _: &mut V, cx| { + if remaining_space < 0. { + let scroll_delta = e.delta.raw(); - let mut delta = match axis { - Axis::Horizontal => { - if scroll_delta.x().abs() >= scroll_delta.y().abs() { - scroll_delta.x() - } else { - scroll_delta.y() - } + let mut delta = match axis { + Axis::Horizontal => { + if scroll_delta.x().abs() >= scroll_delta.y().abs() { + scroll_delta.x() + } else { + scroll_delta.y() } - Axis::Vertical => scroll_delta.y(), - }; - if !e.delta.precise() { - delta *= 20.; } - - scroll_state - .scroll_position - .set(scroll_state.scroll_position.get() - delta); - - cx.notify(); - } else { - cx.propagate_event(); + Axis::Vertical => scroll_delta.y(), + }; + if !e.delta.precise() { + delta *= 20.; } + + scroll_state + .scroll_position + .set(scroll_state.scroll_position.get() - delta); + + cx.notify(); + } else { + cx.propagate_event(); } - }) - .on_move(|_, _: &mut V, _| { /* Capture move events */ }), + } + }) + .on_move(|_, _: &mut V, _| { /* Capture move events */ }), ) } From eced842dfc281dc24dcb8b3cd9ed4511d7102376 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 22 Aug 2023 16:12:59 -0400 Subject: [PATCH 20/92] Get started with a prettier server package Co-Authored-By: Antonio Scandurra --- Cargo.lock | 4 ++ Cargo.toml | 1 + crates/prettier/Cargo.toml | 9 +++ .../prettier_server/.zed/settings.json | 7 +++ crates/prettier/prettier_server/package.json | 11 ++++ crates/prettier/prettier_server/src/index.js | 56 +++++++++++++++++++ crates/prettier/src/prettier.rs | 14 +++++ 7 files changed, 102 insertions(+) create mode 100644 crates/prettier/Cargo.toml create mode 100644 crates/prettier/prettier_server/.zed/settings.json create mode 100644 crates/prettier/prettier_server/package.json create mode 100644 crates/prettier/prettier_server/src/index.js create mode 100644 crates/prettier/src/prettier.rs diff --git a/Cargo.lock b/Cargo.lock index cd36221de0..8b1278b3dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5517,6 +5517,10 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettier" +version = "0.1.0" + [[package]] name = "pretty_assertions" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 7dae3bd81f..25aec39cdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ members = [ "crates/plugin", "crates/plugin_macros", "crates/plugin_runtime", + "crates/prettier", "crates/project", "crates/project_panel", "crates/project_symbols", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml new file mode 100644 index 0000000000..798899df0e --- /dev/null +++ b/crates/prettier/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "prettier" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[lib] +path = "src/prettier.rs" diff --git a/crates/prettier/prettier_server/.zed/settings.json b/crates/prettier/prettier_server/.zed/settings.json new file mode 100644 index 0000000000..856536c62d --- /dev/null +++ b/crates/prettier/prettier_server/.zed/settings.json @@ -0,0 +1,7 @@ +{ + "languages": { + "JavaScript": { + "tab_size": 4 + } + } +} diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json new file mode 100644 index 0000000000..70c834b37e --- /dev/null +++ b/crates/prettier/prettier_server/package.json @@ -0,0 +1,11 @@ +{ + "name": "prettier_server", + "version": "1.0.0", + "description": "", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Zed Industries" +} diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js new file mode 100644 index 0000000000..c8f2f3bb37 --- /dev/null +++ b/crates/prettier/prettier_server/src/index.js @@ -0,0 +1,56 @@ +const { Buffer } = require('buffer'); + +let buffer = Buffer.alloc(0); +process.stdin.resume(); +process.stdin.on('data', (data) => { + buffer = Buffer.concat([buffer, data]); + handleData(); +}); +process.stdin.on('end', () => { + handleData(); +}); + +function handleData() { + if (buffer.length < 4) { + return; + } + + const length = buffer.readUInt32LE(0); + console.log(length); + if (buffer.length < 4 + length) { + return; + } + + const bytes = buffer.subarray(4, 4 + length); + buffer = buffer.subarray(4 + length); + + try { + const message = JSON.parse(bytes); + handleMessage(message); + } catch (_) { + sendResponse(makeError("Request JSON parse error")); + return; + } +} + +// format +// clear_cache +// shutdown +// error + +function handleMessage(message) { + console.log(message); + sendResponse({ method: "hi", result: null }); +} + +function makeError(message) { + return { method: "error", message }; +} + +function sendResponse(response) { + let message = Buffer.from(JSON.stringify(response)); + let length = Buffer.alloc(4); + length.writeUInt32LE(message.length); + process.stdout.write(length); + process.stdout.write(message); +} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs new file mode 100644 index 0000000000..7d12d9af81 --- /dev/null +++ b/crates/prettier/src/prettier.rs @@ -0,0 +1,14 @@ +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From 553abd01beded42f9fdb64feecf8dcff30eac5cb Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 1 Sep 2023 18:31:16 +0300 Subject: [PATCH 21/92] Draft a project part of the prettier --- Cargo.lock | 7 +++ crates/language/src/language_settings.rs | 7 ++- crates/prettier/Cargo.toml | 15 ++++++- crates/prettier/src/prettier.rs | 50 +++++++++++++++++---- crates/project/Cargo.toml | 1 + crates/project/src/project.rs | 56 +++++++++++++++++++++++- 6 files changed, 122 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b1278b3dc..120669f037 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5520,6 +5520,12 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettier" version = "0.1.0" +dependencies = [ + "anyhow", + "fs", + "gpui", + "language", +] [[package]] name = "pretty_assertions" @@ -5635,6 +5641,7 @@ dependencies = [ "lsp", "parking_lot 0.11.2", "postage", + "prettier", "pretty_assertions", "rand 0.8.5", "regex", diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index c3f706802a..8778ba8d64 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -149,10 +149,15 @@ pub enum ShowWhitespaceSetting { All, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Formatter { + #[default] + Auto, LanguageServer, + Prettier { + config: (), // Support some of the most important settings in the prettier-vscode extension. + }, External { command: Arc, arguments: Arc<[String]>, diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 798899df0e..c2e4b2a3cc 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -3,7 +3,18 @@ name = "prettier" version = "0.1.0" edition = "2021" -[dependencies] - [lib] path = "src/prettier.rs" + +[dependencies] +language = { path = "../language" } +gpui = { path = "../gpui" } +fs = { path = "../fs" } + +anyhow.workspace = true + + +[dev-dependencies] +language = { path = "../language", features = ["test-support"] } +gpui = { path = "../gpui", features = ["test-support"] } +fs = { path = "../fs", features = ["test-support"] } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 7d12d9af81..9b1b9c1e9d 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,14 +1,46 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right +pub use std::path::{Path, PathBuf}; +pub use std::sync::Arc; + +use fs::Fs; +use gpui::ModelHandle; +use language::{Buffer, Diff}; + +pub struct Prettier { + _private: (), } -#[cfg(test)] -mod tests { - use super::*; +type NodeRuntime = (); - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); +impl Prettier { + // This was taken from the prettier-vscode extension. + pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ + ".prettierrc", + ".prettierrc.json", + ".prettierrc.json5", + ".prettierrc.yaml", + ".prettierrc.yml", + ".prettierrc.toml", + ".prettierrc.js", + ".prettierrc.cjs", + "package.json", + "prettier.config.js", + "prettier.config.cjs", + ".editorconfig", + ]; + + pub async fn locate(starting_path: Option<&Path>, fs: Arc) -> PathBuf { + todo!() + } + + pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { + todo!() + } + + pub async fn format(&self, buffer: &ModelHandle) -> anyhow::Result { + todo!() + } + + pub async fn clear_cache(&self) -> anyhow::Result<()> { + todo!() } } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index ffea6646e9..c22193f130 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -31,6 +31,7 @@ git = { path = "../git" } gpui = { path = "../gpui" } language = { path = "../language" } lsp = { path = "../lsp" } +prettier = { path = "../prettier" } rpc = { path = "../rpc" } settings = { path = "../settings" } sum_tree = { path = "../sum_tree" } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a50e02a631..42b0a13c51 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -50,6 +50,7 @@ use lsp::{ }; use lsp_command::*; use postage::watch; +use prettier::Prettier; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -152,6 +153,7 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, + prettier_instances: HashMap<(WorktreeId, PathBuf), Shared>>>>, } struct DelayedDebounced { @@ -660,6 +662,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + prettier_instances: HashMap::default(), } }) } @@ -757,6 +760,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + prettier_instances: HashMap::default(), }; for worktree in worktrees { let _ = this.add_worktree(&worktree, cx); @@ -4027,6 +4031,7 @@ impl Project { enum FormatOperation { Lsp(Vec<(Range, String)>), External(Diff), + Prettier(Diff), } // Apply language-specific formatting using either a language server @@ -4062,8 +4067,8 @@ impl Project { | (_, FormatOnSave::External { command, arguments }) => { if let Some(buffer_abs_path) = buffer_abs_path { format_operation = Self::format_via_external_command( - &buffer, - &buffer_abs_path, + buffer, + buffer_abs_path, &command, &arguments, &mut cx, @@ -4076,6 +4081,45 @@ impl Project { .map(FormatOperation::External); } } + (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => { + if let Some(prettier) = this.update(&mut cx, |project, _| { + project.prettier_instance_for_buffer(buffer) + }) { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("autoformatting via prettier")?, + )); + } else if let Some((language_server, buffer_abs_path)) = + language_server.as_ref().zip(buffer_abs_path.as_ref()) + { + format_operation = Some(FormatOperation::Lsp( + Self::format_via_lsp( + &this, + &buffer, + buffer_abs_path, + &language_server, + tab_size, + &mut cx, + ) + .await + .context("failed to format via language server")?, + )); + } + } + (Formatter::Prettier { .. }, FormatOnSave::On | FormatOnSave::Off) => { + if let Some(prettier) = this.update(&mut cx, |project, _| { + project.prettier_instance_for_buffer(buffer) + }) { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("formatting via prettier")?, + )); + } + } }; buffer.update(&mut cx, |b, cx| { @@ -4100,6 +4144,9 @@ impl Project { FormatOperation::External(diff) => { b.apply_diff(diff, cx); } + FormatOperation::Prettier(diff) => { + b.apply_diff(diff, cx); + } } if let Some(transaction_id) = whitespace_transaction_id { @@ -8109,6 +8156,11 @@ impl Project { Vec::new() } } + + fn prettier_instance_for_buffer(&self, buffer: &ModelHandle) -> Option { + // TODO kb + None + } } fn subscribe_for_copilot_events( From 92f23e626eba18fb4653a653cd600e11ada45c90 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 5 Sep 2023 15:51:46 +0300 Subject: [PATCH 22/92] Properly connect prettier lookup/creation methods --- crates/prettier/src/prettier.rs | 2 +- crates/project/src/project.rs | 115 +++++++++++++++++++++++++------- 2 files changed, 93 insertions(+), 24 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 9b1b9c1e9d..ecd4376477 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -9,7 +9,7 @@ pub struct Prettier { _private: (), } -type NodeRuntime = (); +pub struct NodeRuntime; impl Prettier { // This was taken from the prettier-vscode extension. diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 42b0a13c51..d24fe17380 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -50,7 +50,7 @@ use lsp::{ }; use lsp_command::*; use postage::watch; -use prettier::Prettier; +use prettier::{NodeRuntime, Prettier}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -153,7 +153,10 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, - prettier_instances: HashMap<(WorktreeId, PathBuf), Shared>>>>, + prettier_instances: HashMap< + (Option, PathBuf), + Shared, Arc>>>, + >, } struct DelayedDebounced { @@ -3953,7 +3956,7 @@ impl Project { push_to_history: bool, trigger: FormatTrigger, cx: &mut ModelContext, - ) -> Task> { + ) -> Task> { if self.is_local() { let mut buffers_with_paths_and_servers = buffers .into_iter() @@ -4082,15 +4085,26 @@ impl Project { } } (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => { - if let Some(prettier) = this.update(&mut cx, |project, _| { - project.prettier_instance_for_buffer(buffer) - }) { - format_operation = Some(FormatOperation::Prettier( - prettier - .format(buffer) + if let Some(prettier_task) = this + .update(&mut cx, |project, cx| { + project.prettier_instance_for_buffer(buffer, cx) + }) { + match prettier_task .await - .context("autoformatting via prettier")?, - )); + .await + { + Ok(prettier) => { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("formatting via prettier")?, + )); + } + Err(e) => anyhow::bail!( + "Failed to create prettier instance for buffer during autoformatting: {e:#}" + ), + } } else if let Some((language_server, buffer_abs_path)) = language_server.as_ref().zip(buffer_abs_path.as_ref()) { @@ -4109,16 +4123,27 @@ impl Project { } } (Formatter::Prettier { .. }, FormatOnSave::On | FormatOnSave::Off) => { - if let Some(prettier) = this.update(&mut cx, |project, _| { - project.prettier_instance_for_buffer(buffer) - }) { - format_operation = Some(FormatOperation::Prettier( - prettier - .format(buffer) + if let Some(prettier_task) = this + .update(&mut cx, |project, cx| { + project.prettier_instance_for_buffer(buffer, cx) + }) { + match prettier_task .await - .context("formatting via prettier")?, - )); - } + .await + { + Ok(prettier) => { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("formatting via prettier")?, + )); + } + Err(e) => anyhow::bail!( + "Failed to create prettier instance for buffer during formatting: {e:#}" + ), + } + } } }; @@ -8157,9 +8182,53 @@ impl Project { } } - fn prettier_instance_for_buffer(&self, buffer: &ModelHandle) -> Option { - // TODO kb - None + fn prettier_instance_for_buffer( + &mut self, + buffer: &ModelHandle, + cx: &mut ModelContext, + ) -> Option, Arc>>>>> { + let buffer_file = File::from_dyn(buffer.read(cx).file()); + let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); + let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); + + // TODO kb return None if config opted out of prettier + + let task = cx.spawn(|this, mut cx| async move { + let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); + // TODO kb can we have a cache for this instead? + let prettier_path = Prettier::locate(buffer_path.as_deref(), fs).await; + if let Some(existing_prettier) = this.update(&mut cx, |project, _| { + project + .prettier_instances + .get(&(worktree_id, prettier_path.clone())) + .cloned() + }) { + return existing_prettier; + } + + let task_node_runtime = Arc::new(NodeRuntime); + let task_prettier_path = prettier_path.clone(); + let new_prettier_task = cx + .background() + .spawn(async move { + Ok(Arc::new( + Prettier::start(&task_prettier_path, task_node_runtime) + .await + .with_context(|| { + format!("starting new prettier for path {task_prettier_path:?}") + })?, + )) + .map_err(Arc::new) + }) + .shared(); + this.update(&mut cx, |project, _| { + project + .prettier_instances + .insert((worktree_id, prettier_path), new_prettier_task.clone()); + }); + new_prettier_task + }); + Some(task) } } From a8dfa013621833d342902ed887f0e5c49da19bde Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 5 Sep 2023 17:09:22 +0300 Subject: [PATCH 23/92] Prepare prettier file lookup code infra --- Cargo.lock | 1 + assets/settings/default.json | 3 +- crates/fs/src/fs.rs | 5 +- crates/prettier/Cargo.toml | 2 +- crates/prettier/src/prettier.rs | 111 +++++++++++++++++++++++++++++++- crates/project/src/project.rs | 51 ++++++++++++++- 6 files changed, 165 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 120669f037..a0daee9305 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5523,6 +5523,7 @@ version = "0.1.0" dependencies = [ "anyhow", "fs", + "futures 0.3.28", "gpui", "language", ] diff --git a/assets/settings/default.json b/assets/settings/default.json index cc724657c0..be47ac9c8c 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -199,7 +199,8 @@ // "arguments": ["--stdin-filepath", "{buffer_path}"] // } // } - "formatter": "language_server", + // TODO kb description + "formatter": "auto", // How to soft-wrap long lines of text. This setting can take // three values: // diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 1d95db9b6c..bb5d6387e0 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -85,7 +85,7 @@ pub struct RemoveOptions { pub ignore_if_not_exists: bool, } -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Debug)] pub struct Metadata { pub inode: u64, pub mtime: SystemTime, @@ -229,11 +229,12 @@ impl Fs for RealFs { } else { symlink_metadata }; + let file_type_metadata = metadata.file_type(); Ok(Some(Metadata { inode: metadata.ino(), mtime: metadata.modified().unwrap(), is_symlink, - is_dir: metadata.file_type().is_dir(), + is_dir: file_type_metadata.is_dir(), })) } diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index c2e4b2a3cc..821cde7b3a 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -12,7 +12,7 @@ gpui = { path = "../gpui" } fs = { path = "../fs" } anyhow.workspace = true - +futures.workspace = true [dev-dependencies] language = { path = "../language", features = ["test-support"] } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index ecd4376477..a38f8f8651 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,6 +1,8 @@ +use std::collections::VecDeque; pub use std::path::{Path, PathBuf}; pub use std::sync::Arc; +use anyhow::Context; use fs::Fs; use gpui::ModelHandle; use language::{Buffer, Diff}; @@ -11,6 +13,12 @@ pub struct Prettier { pub struct NodeRuntime; +#[derive(Debug)] +pub struct LocateStart { + pub worktree_root_path: Arc, + pub starting_path: Arc, +} + impl Prettier { // This was taken from the prettier-vscode extension. pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ @@ -28,8 +36,107 @@ impl Prettier { ".editorconfig", ]; - pub async fn locate(starting_path: Option<&Path>, fs: Arc) -> PathBuf { - todo!() + pub async fn locate( + starting_path: Option, + fs: Arc, + ) -> anyhow::Result { + let paths_to_check = match starting_path { + Some(starting_path) => { + let worktree_root = starting_path + .worktree_root_path + .components() + .into_iter() + .take_while(|path_component| { + path_component.as_os_str().to_str() != Some("node_modules") + }) + .collect::(); + + if worktree_root != starting_path.worktree_root_path.as_ref() { + vec![worktree_root] + } else { + let (worktree_root_metadata, start_path_metadata) = if starting_path + .starting_path + .as_ref() + == Path::new("") + { + let worktree_root_data = + fs.metadata(&worktree_root).await.with_context(|| { + format!( + "FS metadata fetch for worktree root path {worktree_root:?}", + ) + })?; + (worktree_root_data.unwrap_or_else(|| { + panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}") + }), None) + } else { + let full_starting_path = worktree_root.join(&starting_path.starting_path); + let (worktree_root_data, start_path_data) = futures::try_join!( + fs.metadata(&worktree_root), + fs.metadata(&full_starting_path), + ) + .with_context(|| { + format!("FS metadata fetch for starting path {full_starting_path:?}",) + })?; + ( + worktree_root_data.unwrap_or_else(|| { + panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}") + }), + start_path_data, + ) + }; + + match start_path_metadata { + Some(start_path_metadata) => { + anyhow::ensure!(worktree_root_metadata.is_dir, + "For non-empty start path, worktree root {starting_path:?} should be a directory"); + anyhow::ensure!( + !start_path_metadata.is_dir, + "For non-empty start path, it should not be a directory {starting_path:?}" + ); + anyhow::ensure!( + !start_path_metadata.is_symlink, + "For non-empty start path, it should not be a symlink {starting_path:?}" + ); + + let file_to_format = starting_path.starting_path.as_ref(); + let mut paths_to_check = VecDeque::from(vec![worktree_root.clone()]); + let mut current_path = worktree_root; + for path_component in file_to_format.components().into_iter() { + current_path = current_path.join(path_component); + paths_to_check.push_front(current_path.clone()); + if path_component.as_os_str().to_str() == Some("node_modules") { + break; + } + } + paths_to_check.pop_front(); // last one is the file itself or node_modules, skip it + Vec::from(paths_to_check) + } + None => { + anyhow::ensure!( + !worktree_root_metadata.is_dir, + "For empty start path, worktree root should not be a directory {starting_path:?}" + ); + anyhow::ensure!( + !worktree_root_metadata.is_symlink, + "For empty start path, worktree root should not be a symlink {starting_path:?}" + ); + worktree_root + .parent() + .map(|path| vec![path.to_path_buf()]) + .unwrap_or_default() + } + } + } + } + None => Vec::new(), + }; + + if dbg!(paths_to_check).is_empty() { + // TODO kb return the default prettier, how, without state? + } else { + // TODO kb now check all paths to check for prettier + } + Ok(PathBuf::new()) } pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d24fe17380..6538a26540 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -50,7 +50,7 @@ use lsp::{ }; use lsp_command::*; use postage::watch; -use prettier::{NodeRuntime, Prettier}; +use prettier::{LocateStart, NodeRuntime, Prettier}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -8189,14 +8189,61 @@ impl Project { ) -> Option, Arc>>>>> { let buffer_file = File::from_dyn(buffer.read(cx).file()); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); + let worktree_path = buffer_file + .as_ref() + .map(|file| file.worktree.read(cx).abs_path()); let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); // TODO kb return None if config opted out of prettier + if true { + let fs = Arc::clone(&self.fs); + let buffer_path = buffer_path.clone(); + let worktree_path = worktree_path.clone(); + cx.spawn(|_, _| async move { + let prettier_path = Prettier::locate( + worktree_path + .zip(buffer_path) + .map(|(worktree_root_path, starting_path)| { + dbg!(LocateStart { + worktree_root_path, + starting_path, + }) + }), + fs, + ) + .await + .unwrap(); + dbg!(prettier_path); + }) + .detach(); + return None; + } let task = cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); // TODO kb can we have a cache for this instead? - let prettier_path = Prettier::locate(buffer_path.as_deref(), fs).await; + let prettier_path = match cx + .background() + .spawn(Prettier::locate( + worktree_path + .zip(buffer_path) + .map(|(worktree_root_path, starting_path)| LocateStart { + worktree_root_path, + starting_path, + }), + fs, + )) + .await + { + Ok(path) => path, + Err(e) => { + return Task::Ready(Some(Result::Err(Arc::new( + e.context("determining prettier path for worktree {worktree_path:?}"), + )))) + .shared(); + } + }; + if let Some(existing_prettier) = this.update(&mut cx, |project, _| { project .prettier_instances From a420d9cdc73738a38e380671364d05f1c829bb12 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 6 Sep 2023 13:57:50 +0300 Subject: [PATCH 24/92] Add prettier search --- Cargo.lock | 3 ++ crates/prettier/Cargo.toml | 3 ++ crates/prettier/src/prettier.rs | 67 +++++++++++++++++++++++++++++---- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0daee9305..9761e040d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5526,6 +5526,9 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "serde", + "serde_derive", + "serde_json", ] [[package]] diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 821cde7b3a..77a845b5b3 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -11,6 +11,9 @@ language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } +serde.workspace = true +serde_derive.workspace = true +serde_json.workspace = true anyhow.workspace = true futures.workspace = true diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index a38f8f8651..ca53bb7a03 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,4 +1,4 @@ -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; pub use std::path::{Path, PathBuf}; pub use std::sync::Arc; @@ -40,7 +40,7 @@ impl Prettier { starting_path: Option, fs: Arc, ) -> anyhow::Result { - let paths_to_check = match starting_path { + let paths_to_check = match starting_path.as_ref() { Some(starting_path) => { let worktree_root = starting_path .worktree_root_path @@ -131,12 +131,16 @@ impl Prettier { None => Vec::new(), }; - if dbg!(paths_to_check).is_empty() { - // TODO kb return the default prettier, how, without state? - } else { - // TODO kb now check all paths to check for prettier + match find_closest_prettier_path(paths_to_check, fs.as_ref()) + .await + .with_context(|| format!("Finding prettier starting with {starting_path:?}"))? + { + Some(prettier_path) => Ok(prettier_path), + None => { + // TODO kb return the default prettier, how, without state? + Ok(PathBuf::new()) + } } - Ok(PathBuf::new()) } pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { @@ -151,3 +155,52 @@ impl Prettier { todo!() } } + +const PRETTIER_PACKAGE_NAME: &str = "prettier"; +async fn find_closest_prettier_path( + paths_to_check: Vec, + fs: &dyn Fs, +) -> anyhow::Result> { + for path in paths_to_check { + let possible_package_json = path.join("package.json"); + if let Some(package_json_metadata) = fs + .metadata(&path) + .await + .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))? + { + if !package_json_metadata.is_dir && !package_json_metadata.is_symlink { + let package_json_contents = fs + .load(&possible_package_json) + .await + .with_context(|| format!("reading {possible_package_json:?} file contents"))?; + if let Ok(json_contents) = serde_json::from_str::>( + &package_json_contents, + ) { + if let Some(serde_json::Value::Object(o)) = json_contents.get("dependencies") { + if o.contains_key(PRETTIER_PACKAGE_NAME) { + return Ok(Some(path)); + } + } + if let Some(serde_json::Value::Object(o)) = json_contents.get("devDependencies") + { + if o.contains_key(PRETTIER_PACKAGE_NAME) { + return Ok(Some(path)); + } + } + } + } + } + + let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME); + if let Some(node_modules_location_metadata) = fs + .metadata(&path) + .await + .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))? + { + if node_modules_location_metadata.is_dir { + return Ok(Some(path)); + } + } + } + Ok(None) +} From a8387b8b19e76c132e829277badeafbbc0220e22 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 6 Sep 2023 17:23:04 +0300 Subject: [PATCH 25/92] Use proper NodeRuntime in the formatter interface --- Cargo.lock | 2 ++ crates/prettier/Cargo.toml | 1 + crates/prettier/prettier_server/.gitignore | 1 + .../prettier_server/package-lock.json | 29 +++++++++++++++++++ crates/prettier/prettier_server/package.json | 21 ++++++++------ crates/prettier/prettier_server/src/index.js | 1 + crates/prettier/src/prettier.rs | 9 +++--- crates/project/Cargo.toml | 1 + crates/project/src/project.rs | 24 +++++++++++---- crates/zed/src/main.rs | 2 +- 10 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 crates/prettier/prettier_server/.gitignore create mode 100644 crates/prettier/prettier_server/package-lock.json diff --git a/Cargo.lock b/Cargo.lock index 9761e040d3..de339e1546 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5526,6 +5526,7 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "node_runtime", "serde", "serde_derive", "serde_json", @@ -5643,6 +5644,7 @@ dependencies = [ "lazy_static", "log", "lsp", + "node_runtime", "parking_lot 0.11.2", "postage", "prettier", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 77a845b5b3..2b8bf99fc2 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -10,6 +10,7 @@ path = "src/prettier.rs" language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } +node_runtime = { path = "../node_runtime"} serde.workspace = true serde_derive.workspace = true diff --git a/crates/prettier/prettier_server/.gitignore b/crates/prettier/prettier_server/.gitignore new file mode 100644 index 0000000000..c2658d7d1b --- /dev/null +++ b/crates/prettier/prettier_server/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/crates/prettier/prettier_server/package-lock.json b/crates/prettier/prettier_server/package-lock.json new file mode 100644 index 0000000000..6d495d72b4 --- /dev/null +++ b/crates/prettier/prettier_server/package-lock.json @@ -0,0 +1,29 @@ +{ + "name": "prettier_server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prettier_server", + "version": "1.0.0", + "dependencies": { + "prettier": "^3.0.3" + } + }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + } + } +} diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json index 70c834b37e..599f308e9f 100644 --- a/crates/prettier/prettier_server/package.json +++ b/crates/prettier/prettier_server/package.json @@ -1,11 +1,14 @@ { - "name": "prettier_server", - "version": "1.0.0", - "description": "", - "main": "src/index.js", - "scripts": { - "start": "node src/index.js", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Zed Industries" + "name": "prettier_server", + "version": "1.0.0", + "description": "", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Zed Industries", + "dependencies": { + "prettier": "^3.0" + } } diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index c8f2f3bb37..5ac35d7ef9 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -17,6 +17,7 @@ function handleData() { const length = buffer.readUInt32LE(0); console.log(length); + console.log(buffer.toString()); if (buffer.length < 4 + length) { return; } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index ca53bb7a03..efb3a0fcf5 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,18 +1,17 @@ use std::collections::{HashMap, VecDeque}; -pub use std::path::{Path, PathBuf}; -pub use std::sync::Arc; +use std::path::{Path, PathBuf}; +use std::sync::Arc; use anyhow::Context; use fs::Fs; use gpui::ModelHandle; use language::{Buffer, Diff}; +use node_runtime::NodeRuntime; pub struct Prettier { _private: (), } -pub struct NodeRuntime; - #[derive(Debug)] pub struct LocateStart { pub worktree_root_path: Arc, @@ -143,7 +142,7 @@ impl Prettier { } } - pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { + pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { todo!() } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index c22193f130..9f505c3fd2 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -31,6 +31,7 @@ git = { path = "../git" } gpui = { path = "../gpui" } language = { path = "../language" } lsp = { path = "../lsp" } +node_runtime = { path = "../node_runtime" } prettier = { path = "../prettier" } rpc = { path = "../rpc" } settings = { path = "../settings" } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 6538a26540..b7fc1b8b34 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -49,8 +49,9 @@ use lsp::{ DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf, }; use lsp_command::*; +use node_runtime::NodeRuntime; use postage::watch; -use prettier::{LocateStart, NodeRuntime, Prettier}; +use prettier::{LocateStart, Prettier}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -71,7 +72,7 @@ use std::{ str, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, - Arc, + Arc, OnceLock, }, time::{Duration, Instant}, }; @@ -553,14 +554,27 @@ impl SearchMatchCandidate { } } +static NODE_RUNTIME: OnceLock> = OnceLock::new(); + impl Project { pub fn init_settings(cx: &mut AppContext) { settings::register::(cx); } - pub fn init(client: &Arc, cx: &mut AppContext) { + pub fn init( + client: &Arc, + node_runtime: Option>, + cx: &mut AppContext, + ) { Self::init_settings(cx); + // TODO kb move it to Project::local and other constructors? + if let Some(node_runtime) = node_runtime { + NODE_RUNTIME + .set(node_runtime) + .unwrap_or_else(|_| panic!("multiple init calls tried to set node runtime")); + } + client.add_model_message_handler(Self::handle_add_collaborator); client.add_model_message_handler(Self::handle_update_project_collaborator); client.add_model_message_handler(Self::handle_remove_collaborator); @@ -8187,6 +8201,7 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Option, Arc>>>>> { + let node_runtime = Arc::clone(NODE_RUNTIME.get()?); let buffer_file = File::from_dyn(buffer.read(cx).file()); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file @@ -8253,13 +8268,12 @@ impl Project { return existing_prettier; } - let task_node_runtime = Arc::new(NodeRuntime); let task_prettier_path = prettier_path.clone(); let new_prettier_task = cx .background() .spawn(async move { Ok(Arc::new( - Prettier::start(&task_prettier_path, task_node_runtime) + Prettier::start(&task_prettier_path, node_runtime) .await .with_context(|| { format!("starting new prettier for path {task_prettier_path:?}") diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f89a880c71..8093632d3a 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -138,7 +138,7 @@ fn main() { theme::init(Assets, cx); context_menu::init(cx); - project::Project::init(&client, cx); + project::Project::init(&client, Some(Arc::clone(&node_runtime)), cx); client::init(&client, cx); command_palette::init(cx); language::init(cx); From ce6b31d93897eb8c6059eba11ee5db0c4382c2c5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 6 Sep 2023 18:49:56 +0300 Subject: [PATCH 26/92] Make NodeRuntime non-static for prettier runner --- Cargo.lock | 2 ++ crates/collab/Cargo.toml | 1 + crates/project/src/project.rs | 37 ++++++++++++++++--------------- crates/workspace/Cargo.toml | 1 + crates/workspace/src/workspace.rs | 8 +++++++ crates/zed/src/main.rs | 10 +++++++-- 6 files changed, 39 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de339e1546..24e312803b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,6 +1501,7 @@ dependencies = [ "log", "lsp", "nanoid", + "node_runtime", "parking_lot 0.11.2", "pretty_assertions", "project", @@ -10003,6 +10004,7 @@ dependencies = [ "lazy_static", "log", "menu", + "node_runtime", "parking_lot 0.11.2", "postage", "project", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 6177c23620..cb22147b04 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -72,6 +72,7 @@ fs = { path = "../fs", features = ["test-support"] } git = { path = "../git", features = ["test-support"] } live_kit_client = { path = "../live_kit_client", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } +node_runtime = { path = "../node_runtime" } project = { path = "../project", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b7fc1b8b34..8f56a8be09 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -72,7 +72,7 @@ use std::{ str, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, - Arc, OnceLock, + Arc, }, time::{Duration, Instant}, }; @@ -154,6 +154,7 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, + node_runtime: Option>, prettier_instances: HashMap< (Option, PathBuf), Shared, Arc>>>, @@ -554,27 +555,14 @@ impl SearchMatchCandidate { } } -static NODE_RUNTIME: OnceLock> = OnceLock::new(); - impl Project { pub fn init_settings(cx: &mut AppContext) { settings::register::(cx); } - pub fn init( - client: &Arc, - node_runtime: Option>, - cx: &mut AppContext, - ) { + pub fn init(client: &Arc, cx: &mut AppContext) { Self::init_settings(cx); - // TODO kb move it to Project::local and other constructors? - if let Some(node_runtime) = node_runtime { - NODE_RUNTIME - .set(node_runtime) - .unwrap_or_else(|_| panic!("multiple init calls tried to set node runtime")); - } - client.add_model_message_handler(Self::handle_add_collaborator); client.add_model_message_handler(Self::handle_update_project_collaborator); client.add_model_message_handler(Self::handle_remove_collaborator); @@ -624,6 +612,7 @@ impl Project { pub fn local( client: Arc, + node_runtime: Arc, user_store: ModelHandle, languages: Arc, fs: Arc, @@ -679,6 +668,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + node_runtime: Some(node_runtime), prettier_instances: HashMap::default(), } }) @@ -777,6 +767,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + node_runtime: None, prettier_instances: HashMap::default(), }; for worktree in worktrees { @@ -811,13 +802,23 @@ impl Project { root_paths: impl IntoIterator, cx: &mut gpui::TestAppContext, ) -> ModelHandle { + use node_runtime::FakeNodeRuntime; + let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background()); let http_client = util::http::FakeHttpClient::with_404_response(); let client = cx.update(|cx| client::Client::new(http_client.clone(), cx)); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - let project = - cx.update(|cx| Project::local(client, user_store, Arc::new(languages), fs, cx)); + let project = cx.update(|cx| { + Project::local( + client, + FakeNodeRuntime::new(), + user_store, + Arc::new(languages), + fs, + cx, + ) + }); for path in root_paths { let (tree, _) = project .update(cx, |project, cx| { @@ -8201,7 +8202,7 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Option, Arc>>>>> { - let node_runtime = Arc::clone(NODE_RUNTIME.get()?); + let node_runtime = Arc::clone(self.node_runtime.as_ref()?); let buffer_file = File::from_dyn(buffer.read(cx).file()); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index d1240a45ce..99f19ed9d0 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -30,6 +30,7 @@ gpui = { path = "../gpui" } install_cli = { path = "../install_cli" } language = { path = "../language" } menu = { path = "../menu" } +node_runtime = { path = "../node_runtime" } project = { path = "../project" } settings = { path = "../settings" } terminal = { path = "../terminal" } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8b068fa10c..454b0138e6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -42,6 +42,7 @@ use gpui::{ use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use itertools::Itertools; use language::{LanguageRegistry, Rope}; +use node_runtime::NodeRuntime; use std::{ any::TypeId, borrow::Cow, @@ -456,6 +457,7 @@ pub struct AppState { pub initialize_workspace: fn(WeakViewHandle, bool, Arc, AsyncAppContext) -> Task>, pub background_actions: BackgroundActions, + pub node_runtime: Arc, } pub struct WorkspaceStore { @@ -474,6 +476,7 @@ struct Follower { impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Arc { + use node_runtime::FakeNodeRuntime; use settings::SettingsStore; if !cx.has_global::() { @@ -498,6 +501,7 @@ impl AppState { user_store, // channel_store, workspace_store, + node_runtime: FakeNodeRuntime::new(), initialize_workspace: |_, _, _, _| Task::ready(Ok(())), build_window_options: |_, _, _| Default::default(), background_actions: || &[], @@ -816,6 +820,7 @@ impl Workspace { )> { let project_handle = Project::local( app_state.client.clone(), + app_state.node_runtime.clone(), app_state.user_store.clone(), app_state.languages.clone(), app_state.fs.clone(), @@ -3517,6 +3522,8 @@ impl Workspace { #[cfg(any(test, feature = "test-support"))] pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { + use node_runtime::FakeNodeRuntime; + let client = project.read(cx).client(); let user_store = project.read(cx).user_store(); @@ -3530,6 +3537,7 @@ impl Workspace { build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _, _| Task::ready(Ok(())), background_actions: || &[], + node_runtime: FakeNodeRuntime::new(), }); Self::new(0, project, app_state, cx) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8093632d3a..16189f6c4e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -138,7 +138,7 @@ fn main() { theme::init(Assets, cx); context_menu::init(cx); - project::Project::init(&client, Some(Arc::clone(&node_runtime)), cx); + project::Project::init(&client, cx); client::init(&client, cx); command_palette::init(cx); language::init(cx); @@ -154,7 +154,12 @@ fn main() { semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx); vim::init(cx); terminal_view::init(cx); - copilot::init(copilot_language_server_id, http.clone(), node_runtime, cx); + copilot::init( + copilot_language_server_id, + http.clone(), + node_runtime.clone(), + cx, + ); assistant::init(cx); component_test::init(cx); @@ -181,6 +186,7 @@ fn main() { initialize_workspace, background_actions, workspace_store, + node_runtime, }); cx.set_global(Arc::downgrade(&app_state)); From 4f956d71e2de17dcce6588fef6696570a4918e27 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 8 Sep 2023 15:41:23 +0300 Subject: [PATCH 27/92] Slightly better prettier settings and discovery --- Cargo.lock | 1 + assets/settings/default.json | 2 + crates/collab/src/tests/test_server.rs | 3 ++ crates/language/src/language_settings.rs | 4 ++ crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 32 ++++++++----- crates/project/src/project.rs | 60 ++++++++---------------- crates/util/src/paths.rs | 1 + 8 files changed, 52 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24e312803b..0d7e250377 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5531,6 +5531,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "util", ] [[package]] diff --git a/assets/settings/default.json b/assets/settings/default.json index be47ac9c8c..677cc57820 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -201,6 +201,8 @@ // } // TODO kb description "formatter": "auto", + // TODO kb description + better settings + "prettier": true, // How to soft-wrap long lines of text. This setting can take // three values: // diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 2e13874125..ccd48a0a1b 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -15,6 +15,7 @@ use fs::FakeFs; use futures::{channel::oneshot, StreamExt as _}; use gpui::{executor::Deterministic, ModelHandle, Task, TestAppContext, WindowHandle}; use language::LanguageRegistry; +use node_runtime::FakeNodeRuntime; use parking_lot::Mutex; use project::{Project, WorktreeId}; use rpc::RECEIVE_TIMEOUT; @@ -218,6 +219,7 @@ impl TestServer { build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _, _| Task::ready(Ok(())), background_actions: || &[], + node_runtime: FakeNodeRuntime::new(), }); cx.update(|cx| { @@ -569,6 +571,7 @@ impl TestClient { self.client().clone(), self.app_state.user_store.clone(), self.app_state.languages.clone(), + self.app_state.node_runtime.clone(), self.app_state.fs.clone(), cx, ) diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 8778ba8d64..844e0c7c36 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -47,6 +47,7 @@ pub struct LanguageSettings { pub show_wrap_guides: bool, pub wrap_guides: Vec, pub format_on_save: FormatOnSave, + pub prettier: bool, pub remove_trailing_whitespace_on_save: bool, pub ensure_final_newline_on_save: bool, pub formatter: Formatter, @@ -92,6 +93,8 @@ pub struct LanguageSettingsContent { #[serde(default)] pub format_on_save: Option, #[serde(default)] + pub prettier: Option, + #[serde(default)] pub remove_trailing_whitespace_on_save: Option, #[serde(default)] pub ensure_final_newline_on_save: Option, @@ -398,6 +401,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent ); merge(&mut settings.formatter, src.formatter.clone()); merge(&mut settings.format_on_save, src.format_on_save.clone()); + merge(&mut settings.prettier, src.prettier); merge( &mut settings.remove_trailing_whitespace_on_save, src.remove_trailing_whitespace_on_save, diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 2b8bf99fc2..ab8d4d9e16 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -11,6 +11,7 @@ language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } node_runtime = { path = "../node_runtime"} +util = { path = "../util" } serde.workspace = true serde_derive.workspace = true diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index efb3a0fcf5..454f845aa4 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -130,20 +130,21 @@ impl Prettier { None => Vec::new(), }; - match find_closest_prettier_path(paths_to_check, fs.as_ref()) + match find_closest_prettier_dir(paths_to_check, fs.as_ref()) .await - .with_context(|| format!("Finding prettier starting with {starting_path:?}"))? + .with_context(|| format!("finding prettier starting with {starting_path:?}"))? { - Some(prettier_path) => Ok(prettier_path), - None => { - // TODO kb return the default prettier, how, without state? - Ok(PathBuf::new()) - } + Some(prettier_dir) => Ok(prettier_dir), + None => Ok(util::paths::DEFAULT_PRETTIER_DIR.to_path_buf()), } } - pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { - todo!() + pub async fn start(prettier_dir: &Path, node: Arc) -> anyhow::Result { + anyhow::ensure!( + prettier_dir.is_dir(), + "Prettier dir {prettier_dir:?} is not a directory" + ); + anyhow::bail!("TODO kb: start prettier server in {prettier_dir:?}") } pub async fn format(&self, buffer: &ModelHandle) -> anyhow::Result { @@ -156,14 +157,14 @@ impl Prettier { } const PRETTIER_PACKAGE_NAME: &str = "prettier"; -async fn find_closest_prettier_path( +async fn find_closest_prettier_dir( paths_to_check: Vec, fs: &dyn Fs, ) -> anyhow::Result> { for path in paths_to_check { let possible_package_json = path.join("package.json"); if let Some(package_json_metadata) = fs - .metadata(&path) + .metadata(&possible_package_json) .await .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))? { @@ -192,7 +193,7 @@ async fn find_closest_prettier_path( let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME); if let Some(node_modules_location_metadata) = fs - .metadata(&path) + .metadata(&possible_node_modules_location) .await .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))? { @@ -203,3 +204,10 @@ async fn find_closest_prettier_path( } Ok(None) } + +async fn prepare_default_prettier( + fs: Arc, + node: Arc, +) -> anyhow::Result { + todo!("TODO kb need to call per language that supports it, and have to use extra packages sometimes") +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8f56a8be09..56b86e5274 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -154,7 +154,7 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, - node_runtime: Option>, + node: Option>, prettier_instances: HashMap< (Option, PathBuf), Shared, Arc>>>, @@ -612,7 +612,7 @@ impl Project { pub fn local( client: Arc, - node_runtime: Arc, + node: Arc, user_store: ModelHandle, languages: Arc, fs: Arc, @@ -668,7 +668,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), - node_runtime: Some(node_runtime), + node: Some(node), prettier_instances: HashMap::default(), } }) @@ -767,7 +767,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), - node_runtime: None, + node: None, prettier_instances: HashMap::default(), }; for worktree in worktrees { @@ -802,8 +802,6 @@ impl Project { root_paths: impl IntoIterator, cx: &mut gpui::TestAppContext, ) -> ModelHandle { - use node_runtime::FakeNodeRuntime; - let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background()); let http_client = util::http::FakeHttpClient::with_404_response(); @@ -812,7 +810,7 @@ impl Project { let project = cx.update(|cx| { Project::local( client, - FakeNodeRuntime::new(), + node_runtime::FakeNodeRuntime::new(), user_store, Arc::new(languages), fs, @@ -8202,43 +8200,25 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Option, Arc>>>>> { - let node_runtime = Arc::clone(self.node_runtime.as_ref()?); - let buffer_file = File::from_dyn(buffer.read(cx).file()); + let buffer = buffer.read(cx); + let buffer_file = buffer.file(); + let language_settings = language_settings(buffer.language(), buffer_file, cx).clone(); + if !language_settings.prettier { + return None; + } + + let node = Arc::clone(self.node.as_ref()?); + let buffer_file = File::from_dyn(buffer_file); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file .as_ref() .map(|file| file.worktree.read(cx).abs_path()); let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); - // TODO kb return None if config opted out of prettier - if true { - let fs = Arc::clone(&self.fs); - let buffer_path = buffer_path.clone(); - let worktree_path = worktree_path.clone(); - cx.spawn(|_, _| async move { - let prettier_path = Prettier::locate( - worktree_path - .zip(buffer_path) - .map(|(worktree_root_path, starting_path)| { - dbg!(LocateStart { - worktree_root_path, - starting_path, - }) - }), - fs, - ) - .await - .unwrap(); - dbg!(prettier_path); - }) - .detach(); - return None; - } - let task = cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); // TODO kb can we have a cache for this instead? - let prettier_path = match cx + let prettier_dir = match cx .background() .spawn(Prettier::locate( worktree_path @@ -8263,21 +8243,21 @@ impl Project { if let Some(existing_prettier) = this.update(&mut cx, |project, _| { project .prettier_instances - .get(&(worktree_id, prettier_path.clone())) + .get(&(worktree_id, prettier_dir.clone())) .cloned() }) { return existing_prettier; } - let task_prettier_path = prettier_path.clone(); + let task_prettier_dir = prettier_dir.clone(); let new_prettier_task = cx .background() .spawn(async move { Ok(Arc::new( - Prettier::start(&task_prettier_path, node_runtime) + Prettier::start(&task_prettier_dir, node) .await .with_context(|| { - format!("starting new prettier for path {task_prettier_path:?}") + format!("starting new prettier for path {task_prettier_dir:?}") })?, )) .map_err(Arc::new) @@ -8286,7 +8266,7 @@ impl Project { this.update(&mut cx, |project, _| { project .prettier_instances - .insert((worktree_id, prettier_path), new_prettier_task.clone()); + .insert((worktree_id, prettier_dir), new_prettier_task.clone()); }); new_prettier_task }); diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 4578ce0bc9..96d77236a9 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -11,6 +11,7 @@ lazy_static::lazy_static! { pub static ref SUPPORT_DIR: PathBuf = HOME.join("Library/Application Support/Zed"); pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages"); pub static ref COPILOT_DIR: PathBuf = HOME.join("Library/Application Support/Zed/copilot"); + pub static ref DEFAULT_PRETTIER_DIR: PathBuf = HOME.join("Library/Application Support/Zed/prettier"); pub static ref DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db"); pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json"); pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json"); From 12ea12e4e78d0ae2fcf53d110757f86cb48745e0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 8 Sep 2023 21:46:28 +0300 Subject: [PATCH 28/92] Make language adapters able to require certain bundled formatters --- assets/settings/default.json | 2 -- crates/language/src/language.rs | 24 ++++++++++++++++++++++++ crates/language/src/language_settings.rs | 4 ---- crates/project/src/project.rs | 21 ++++++++++++++------- crates/zed/src/languages/css.rs | 6 +++++- crates/zed/src/languages/html.rs | 6 +++++- crates/zed/src/languages/json.rs | 8 +++++++- crates/zed/src/languages/php.rs | 6 +++++- crates/zed/src/languages/svelte.rs | 6 +++++- crates/zed/src/languages/typescript.rs | 10 +++++++++- crates/zed/src/languages/yaml.rs | 7 ++++++- 11 files changed, 80 insertions(+), 20 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 677cc57820..be47ac9c8c 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -201,8 +201,6 @@ // } // TODO kb description "formatter": "auto", - // TODO kb description + better settings - "prettier": true, // How to soft-wrap long lines of text. This setting can take // three values: // diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 7d113a88af..ed0a0d8ee8 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -227,6 +227,10 @@ impl CachedLspAdapter { ) -> Option { self.adapter.label_for_symbol(name, kind, language).await } + + pub fn enabled_formatters(&self) -> Vec { + self.adapter.enabled_formatters() + } } pub trait LspAdapterDelegate: Send + Sync { @@ -333,6 +337,26 @@ pub trait LspAdapter: 'static + Send + Sync { async fn language_ids(&self) -> HashMap { Default::default() } + + // TODO kb enable this for + // markdown somehow? + // tailwind (needs a css plugin, there are 2 of them) + fn enabled_formatters(&self) -> Vec { + Vec::new() + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BundledFormatter { + Prettier { plugin_names: Vec }, +} + +impl BundledFormatter { + pub fn prettier() -> Self { + Self::Prettier { + plugin_names: Vec::new(), + } + } } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 844e0c7c36..8778ba8d64 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -47,7 +47,6 @@ pub struct LanguageSettings { pub show_wrap_guides: bool, pub wrap_guides: Vec, pub format_on_save: FormatOnSave, - pub prettier: bool, pub remove_trailing_whitespace_on_save: bool, pub ensure_final_newline_on_save: bool, pub formatter: Formatter, @@ -93,8 +92,6 @@ pub struct LanguageSettingsContent { #[serde(default)] pub format_on_save: Option, #[serde(default)] - pub prettier: Option, - #[serde(default)] pub remove_trailing_whitespace_on_save: Option, #[serde(default)] pub ensure_final_newline_on_save: Option, @@ -401,7 +398,6 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent ); merge(&mut settings.formatter, src.formatter.clone()); merge(&mut settings.format_on_save, src.format_on_save.clone()); - merge(&mut settings.prettier, src.prettier); merge( &mut settings.remove_trailing_whitespace_on_save, src.remove_trailing_whitespace_on_save, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 56b86e5274..bfcce50c01 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -37,11 +37,11 @@ use language::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, serialize_anchor, serialize_version, split_operations, }, - range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction, - CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, - File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate, - OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, - ToOffset, ToPointUtf16, Transaction, Unclipped, + range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter, + CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, + Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, + LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, + TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, }; use log::error; use lsp::{ @@ -2651,6 +2651,8 @@ impl Project { } } + // TODO kb 2 usages of this method (buffer language select + settings change) should take care of + // `LspAdapter::enabled_formatters` collecting and initializing. Remove `Option` for prettier instances? fn start_language_servers( &mut self, worktree: &ModelHandle, @@ -8202,8 +8204,13 @@ impl Project { ) -> Option, Arc>>>>> { let buffer = buffer.read(cx); let buffer_file = buffer.file(); - let language_settings = language_settings(buffer.language(), buffer_file, cx).clone(); - if !language_settings.prettier { + let buffer_language = buffer.language()?; + if !buffer_language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .any(|formatter| matches!(formatter, BundledFormatter::Prettier { .. })) + { return None; } diff --git a/crates/zed/src/languages/css.rs b/crates/zed/src/languages/css.rs index fdbc179209..a28523a741 100644 --- a/crates/zed/src/languages/css.rs +++ b/crates/zed/src/languages/css.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -96,6 +96,10 @@ impl LspAdapter for CssLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index b8f1c70cce..af8fb1690c 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -96,6 +96,10 @@ impl LspAdapter for HtmlLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 63f909ae2a..e66a5d96a6 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -4,7 +4,9 @@ use collections::HashMap; use feature_flags::FeatureFlagAppExt; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; -use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{ + BundledFormatter, LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate, +}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -144,6 +146,10 @@ impl LspAdapter for JsonLspAdapter { async fn language_ids(&self) -> HashMap { [("JSON".into(), "jsonc".into())].into_iter().collect() } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/php.rs b/crates/zed/src/languages/php.rs index 3096fd16e6..f8f9351111 100644 --- a/crates/zed/src/languages/php.rs +++ b/crates/zed/src/languages/php.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use collections::HashMap; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; @@ -103,6 +103,10 @@ impl LspAdapter for IntelephenseLspAdapter { async fn language_ids(&self) -> HashMap { HashMap::from_iter([("PHP".into(), "php".into())]) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs index 5e42d80e77..487720ce46 100644 --- a/crates/zed/src/languages/svelte.rs +++ b/crates/zed/src/languages/svelte.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -95,6 +95,10 @@ impl LspAdapter for SvelteLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 676d0fd4c0..78b1214b1a 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -4,7 +4,7 @@ use async_tar::Archive; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt}; use gpui::AppContext; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::{CodeActionKind, LanguageServerBinary}; use node_runtime::NodeRuntime; use serde_json::{json, Value}; @@ -161,6 +161,10 @@ impl LspAdapter for TypeScriptLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_ts_server_binary( @@ -309,6 +313,10 @@ impl LspAdapter for EsLintLspAdapter { async fn initialization_options(&self) -> Option { None } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_eslint_server_binary( diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index 8b438d0949..c9168b3480 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; use language::{ - language_settings::all_language_settings, LanguageServerName, LspAdapter, LspAdapterDelegate, + language_settings::all_language_settings, BundledFormatter, LanguageServerName, LspAdapter, + LspAdapterDelegate, }; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; @@ -108,6 +109,10 @@ impl LspAdapter for YamlLspAdapter { })) .boxed() } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( From 1ff17bd15d23bf0b78424a24fbd448f4b97c5665 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 12 Sep 2023 14:04:18 +0300 Subject: [PATCH 29/92] Install default prettier and plugins on startup --- crates/prettier/src/prettier.rs | 7 -- crates/project/src/project.rs | 124 +++++++++++++++++++++++++++++--- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 454f845aa4..d68af349d9 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -204,10 +204,3 @@ async fn find_closest_prettier_dir( } Ok(None) } - -async fn prepare_default_prettier( - fs: Arc, - node: Arc, -) -> anyhow::Result { - todo!("TODO kb need to call per language that supports it, and have to use extra packages sometimes") -} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bfcce50c01..ffc15ee0d3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -20,7 +20,7 @@ use futures::{ mpsc::{self, UnboundedReceiver}, oneshot, }, - future::{try_join_all, Shared}, + future::{self, try_join_all, Shared}, stream::FuturesUnordered, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, }; @@ -31,7 +31,9 @@ use gpui::{ }; use itertools::Itertools; use language::{ - language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind}, + language_settings::{ + language_settings, FormatOnSave, Formatter, InlayHintKind, LanguageSettings, + }, point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, @@ -79,8 +81,11 @@ use std::{ use terminals::Terminals; use text::Anchor; use util::{ - debug_panic, defer, http::HttpClient, merge_json_value_into, - paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _, + debug_panic, defer, + http::HttpClient, + merge_json_value_into, + paths::{DEFAULT_PRETTIER_DIR, LOCAL_SETTINGS_RELATIVE_PATH}, + post_inc, ResultExt, TryFutureExt as _, }; pub use fs::*; @@ -832,17 +837,28 @@ impl Project { fn on_settings_changed(&mut self, cx: &mut ModelContext) { let mut language_servers_to_start = Vec::new(); + let mut language_formatters_to_check = Vec::new(); for buffer in self.opened_buffers.values() { if let Some(buffer) = buffer.upgrade(cx) { let buffer = buffer.read(cx); - if let Some((file, language)) = buffer.file().zip(buffer.language()) { - let settings = language_settings(Some(language), Some(file), cx); + let buffer_file = buffer.file(); + let buffer_language = buffer.language(); + let settings = language_settings(buffer_language, buffer_file, cx); + if let Some(language) = buffer_language { if settings.enable_language_server { - if let Some(file) = File::from_dyn(Some(file)) { + if let Some(file) = File::from_dyn(buffer_file) { language_servers_to_start - .push((file.worktree.clone(), language.clone())); + .push((file.worktree.clone(), Arc::clone(language))); } } + let worktree = buffer_file + .map(|f| f.worktree_id()) + .map(WorktreeId::from_usize); + language_formatters_to_check.push(( + worktree, + Arc::clone(language), + settings.clone(), + )); } } } @@ -895,6 +911,11 @@ impl Project { .detach(); } + // TODO kb restart all formatters if settings change + for (worktree, language, settings) in language_formatters_to_check { + self.maybe_start_default_formatters(worktree, &language, &settings, cx); + } + // Start all the newly-enabled language servers. for (worktree, language) in language_servers_to_start { let worktree_path = worktree.read(cx).abs_path(); @@ -2643,7 +2664,15 @@ impl Project { } }); - if let Some(file) = File::from_dyn(buffer.read(cx).file()) { + let buffer_file = buffer.read(cx).file().cloned(); + let worktree = buffer_file + .as_ref() + .map(|f| f.worktree_id()) + .map(WorktreeId::from_usize); + let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); + self.maybe_start_default_formatters(worktree, &new_language, &settings, cx); + + if let Some(file) = File::from_dyn(buffer_file.as_ref()) { let worktree = file.worktree.clone(); if let Some(tree) = worktree.read(cx).as_local() { self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx); @@ -2651,8 +2680,6 @@ impl Project { } } - // TODO kb 2 usages of this method (buffer language select + settings change) should take care of - // `LspAdapter::enabled_formatters` collecting and initializing. Remove `Option` for prettier instances? fn start_language_servers( &mut self, worktree: &ModelHandle, @@ -8279,6 +8306,81 @@ impl Project { }); Some(task) } + + fn maybe_start_default_formatters( + &mut self, + worktree: Option, + new_language: &Language, + language_settings: &LanguageSettings, + cx: &mut ModelContext, + ) { + match &language_settings.formatter { + Formatter::Prettier { .. } | Formatter::Auto => {} + Formatter::LanguageServer | Formatter::External { .. } => return, + }; + let Some(node) = self.node.as_ref().cloned() else { + return; + }; + + let mut prettier_plugins = None; + for formatter in new_language + .lsp_adapters() + .into_iter() + .flat_map(|adapter| adapter.enabled_formatters()) + { + match formatter { + BundledFormatter::Prettier { plugin_names } => prettier_plugins + .get_or_insert_with(|| HashSet::default()) + .extend(plugin_names), + } + } + let Some(prettier_plugins) = prettier_plugins else { + return; + }; + + let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); + if let Some(_already_running) = self + .prettier_instances + .get(&(worktree, default_prettier_dir.to_path_buf())) + { + // TODO kb need to compare plugins, install missing and restart prettier + return; + } + + let fs = Arc::clone(&self.fs); + cx.background() + .spawn(async move { + let prettier_dir_metadata = fs.metadata(default_prettier_dir).await.with_context(|| format!("fetching FS metadata for prettier default dir {default_prettier_dir:?}"))?; + if prettier_dir_metadata.is_none() { + fs.create_dir(default_prettier_dir).await.with_context(|| format!("creating prettier default dir {default_prettier_dir:?}"))?; + } + + let packages_to_versions = future::try_join_all( + prettier_plugins + .iter() + .map(|s| s.as_str()) + .chain(Some("prettier")) + .map(|package_name| async { + let returned_package_name = package_name.to_string(); + let latest_version = node.npm_package_latest_version(package_name) + .await + .with_context(|| { + format!("fetching latest npm version for package {returned_package_name}") + })?; + anyhow::Ok((returned_package_name, latest_version)) + }), + ) + .await + .context("fetching latest npm versions")?; + + let borrowed_packages = packages_to_versions.iter().map(|(package, version)| { + (package.as_str(), version.as_str()) + }).collect::>(); + node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } } fn subscribe_for_copilot_events( From 86618a64c6ab00e51d569aaa99142d37e1583c3e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 12 Sep 2023 16:34:56 +0300 Subject: [PATCH 30/92] Require prettier argument and library in the wrapper --- .../prettier_server/package-lock.json | 35 ++++-------- crates/prettier/prettier_server/package.json | 2 +- crates/prettier/prettier_server/src/index.js | 53 ++++++++++++++++++- 3 files changed, 61 insertions(+), 29 deletions(-) diff --git a/crates/prettier/prettier_server/package-lock.json b/crates/prettier/prettier_server/package-lock.json index 6d495d72b4..01fd75f3dd 100644 --- a/crates/prettier/prettier_server/package-lock.json +++ b/crates/prettier/prettier_server/package-lock.json @@ -1,29 +1,12 @@ { - "name": "prettier_server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "prettier_server", - "version": "1.0.0", - "dependencies": { - "prettier": "^3.0.3" - } - }, - "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } + "name": "prettier_server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prettier_server", + "version": "1.0.0" + } } - } } diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json index 599f308e9f..a591a37bdb 100644 --- a/crates/prettier/prettier_server/package.json +++ b/crates/prettier/prettier_server/package.json @@ -8,7 +8,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Zed Industries", - "dependencies": { + "dev-dependencies": { "prettier": "^3.0" } } diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index 5ac35d7ef9..482e996887 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -1,4 +1,36 @@ const { Buffer } = require('buffer'); +const fs = require("fs"); +const path = require("path"); + +let prettierContainerPath = process.argv[2]; +if (prettierContainerPath == null || prettierContainerPath.length == 0) { + console.error(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); + process.exit(1); +} +fs.stat(prettierContainerPath, (err, stats) => { + if (err) { + console.error(`Path '${prettierContainerPath}' does not exist.`); + process.exit(1); + } + + if (!stats.isDirectory()) { + console.log(`Path '${prettierContainerPath}' exists but is not a directory.`); + process.exit(1); + } +}); +let prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); + +(async () => { + let prettier; + try { + prettier = await loadPrettier(prettierPath); + } catch (error) { + console.error(error); + process.exit(1); + } + console.log("Prettier loadded successfully."); + // TODO kb do the rest here +})() let buffer = Buffer.alloc(0); process.stdin.resume(); @@ -28,14 +60,15 @@ function handleData() { try { const message = JSON.parse(bytes); handleMessage(message); - } catch (_) { - sendResponse(makeError("Request JSON parse error")); + } catch (e) { + sendResponse(makeError(`Request JSON parse error: ${e}`)); return; } } // format // clear_cache +// // shutdown // error @@ -55,3 +88,19 @@ function sendResponse(response) { process.stdout.write(length); process.stdout.write(message); } + +function loadPrettier(prettierPath) { + return new Promise((resolve, reject) => { + fs.access(prettierPath, fs.constants.F_OK, (err) => { + if (err) { + reject(`Path '${prettierPath}' does not exist.Error: ${err}`); + } else { + try { + resolve(require(prettierPath)); + } catch (err) { + reject(`Error requiring prettier module from path '${prettierPath}'.Error: ${err}`); + } + } + }); + }); +} From bb2cc2d157a5f3aa4a8a715d6eeb9df97f4f4d3d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Sep 2023 17:14:22 +0300 Subject: [PATCH 31/92] Async-ify prettier wrapper --- crates/prettier/prettier_server/src/index.js | 100 +++++++++++++------ 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index 482e996887..15aeb30dca 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -1,8 +1,9 @@ const { Buffer } = require('buffer'); const fs = require("fs"); const path = require("path"); +const { once } = require('events'); -let prettierContainerPath = process.argv[2]; +const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { console.error(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); process.exit(1); @@ -18,48 +19,83 @@ fs.stat(prettierContainerPath, (err, stats) => { process.exit(1); } }); -let prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); +const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); + (async () => { let prettier; try { prettier = await loadPrettier(prettierPath); } catch (error) { - console.error(error); + console.error("Failed to load prettier: ", error); process.exit(1); } console.log("Prettier loadded successfully."); - // TODO kb do the rest here + process.stdin.resume(); + handleBuffer(prettier); })() -let buffer = Buffer.alloc(0); -process.stdin.resume(); -process.stdin.on('data', (data) => { - buffer = Buffer.concat([buffer, data]); - handleData(); -}); -process.stdin.on('end', () => { - handleData(); -}); - -function handleData() { - if (buffer.length < 4) { - return; +async function handleBuffer(prettier) { + for await (let messageText of readStdin()) { + handleData(messageText, prettier).catch(e => { + console.error("Failed to handle formatter request", e); + }); } +} - const length = buffer.readUInt32LE(0); - console.log(length); - console.log(buffer.toString()); - if (buffer.length < 4 + length) { - return; - } - - const bytes = buffer.subarray(4, 4 + length); - buffer = buffer.subarray(4 + length); +async function* readStdin() { + const bufferLengthOffset = 4; + let buffer = Buffer.alloc(0); + let streamEnded = false; + process.stdin.on('end', () => { + streamEnded = true; + }); + process.stdin.on('data', (data) => { + buffer = Buffer.concat([buffer, data]); + }); try { - const message = JSON.parse(bytes); - handleMessage(message); + main_loop: while (true) { + while (buffer.length < bufferLengthOffset) { + if (streamEnded) { + sendResponse(makeError(`Unexpected end of stream: less than ${bufferLengthOffset} characters passed`)); + buffer = Buffer.alloc(0); + streamEnded = false; + await once(process.stdin, 'readable'); + continue main_loop; + } + await once(process.stdin, 'readable'); + } + + const length = buffer.readUInt32LE(0); + + while (buffer.length < (bufferLengthOffset + length)) { + if (streamEnded) { + sendResponse(makeError( + `Unexpected end of stream: buffer length ${buffer.length} does not match expected length ${bufferLengthOffset} + ${length}`)); + buffer = Buffer.alloc(0); + streamEnded = false; + await once(process.stdin, 'readable'); + continue main_loop; + } + await once(process.stdin, 'readable'); + } + + const message = buffer.subarray(4, 4 + length); + buffer = buffer.subarray(4 + length); + yield message.toString('utf8'); + } + } catch (e) { + console.error(`Error reading stdin: ${e}`); + } finally { + process.stdin.off('data'); + } +} + +async function handleData(messageText, prettier) { + try { + const message = JSON.parse(messageText); + await handleMessage(prettier, message); } catch (e) { sendResponse(makeError(`Request JSON parse error: ${e}`)); return; @@ -72,8 +108,8 @@ function handleData() { // shutdown // error -function handleMessage(message) { - console.log(message); +async function handleMessage(prettier, message) { + console.log(`message: ${message}`); sendResponse({ method: "hi", result: null }); } @@ -82,8 +118,8 @@ function makeError(message) { } function sendResponse(response) { - let message = Buffer.from(JSON.stringify(response)); - let length = Buffer.alloc(4); + const message = Buffer.from(JSON.stringify(response)); + const length = Buffer.alloc(4); length.writeUInt32LE(message.length); process.stdout.write(length); process.stdout.write(message); From 010bb73ac2a28903a0822486d34b00042c868d7b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Sep 2023 23:10:42 +0300 Subject: [PATCH 32/92] Use LSP-like protocol for prettier wrapper commands --- crates/prettier/prettier_server/src/index.js | 71 +++++++++++++------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index 15aeb30dca..d232ac9efe 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -44,7 +44,6 @@ async function handleBuffer(prettier) { } async function* readStdin() { - const bufferLengthOffset = 4; let buffer = Buffer.alloc(0); let streamEnded = false; process.stdin.on('end', () => { @@ -54,41 +53,63 @@ async function* readStdin() { buffer = Buffer.concat([buffer, data]); }); + async function handleStreamEnded(errorMessage) { + sendResponse(makeError(errorMessage)); + buffer = Buffer.alloc(0); + messageLength = null; + await once(process.stdin, 'readable'); + streamEnded = false; + } + try { + const headersSeparator = "\r\n\r\n"; + let contentLengthHeaderName = 'Content-Length'; + let headersLength = null; + let messageLength = null; main_loop: while (true) { - while (buffer.length < bufferLengthOffset) { - if (streamEnded) { - sendResponse(makeError(`Unexpected end of stream: less than ${bufferLengthOffset} characters passed`)); - buffer = Buffer.alloc(0); - streamEnded = false; + if (messageLength === null) { + while (buffer.indexOf(headersSeparator) === -1) { + if (streamEnded) { + await handleStreamEnded('Unexpected end of stream: headers not found'); + continue main_loop; + } else if (buffer.length > contentLengthHeaderName.length * 10) { + await handleStreamEnded(`Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`); + continue main_loop; + } await once(process.stdin, 'readable'); + } + const headers = buffer.subarray(0, buffer.indexOf(headersSeparator)).toString('ascii'); + const contentLengthHeader = headers.split('\r\n').map(header => header.split(': ')) + .filter(header => header[2] === undefined) + .filter(header => (header[1] || '').length > 0) + .find(header => header[0].trim() === contentLengthHeaderName); + if (contentLengthHeader === undefined) { + await handleStreamEnded(`Missing or incorrect Content-Length header: ${headers}`); + continue main_loop; + } + headersLength = headers.length + headersSeparator.length; + messageLength = parseInt(contentLengthHeader[1], 10); + } + + while (buffer.length < (headersLength + messageLength)) { + if (streamEnded) { + await handleStreamEnded( + `Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`); continue main_loop; } await once(process.stdin, 'readable'); } - const length = buffer.readUInt32LE(0); - - while (buffer.length < (bufferLengthOffset + length)) { - if (streamEnded) { - sendResponse(makeError( - `Unexpected end of stream: buffer length ${buffer.length} does not match expected length ${bufferLengthOffset} + ${length}`)); - buffer = Buffer.alloc(0); - streamEnded = false; - await once(process.stdin, 'readable'); - continue main_loop; - } - await once(process.stdin, 'readable'); - } - - const message = buffer.subarray(4, 4 + length); - buffer = buffer.subarray(4 + length); + const messageEnd = headersLength + messageLength; + const message = buffer.subarray(headersLength, messageEnd); + buffer = buffer.subarray(messageEnd); + messageLength = null; yield message.toString('utf8'); } } catch (e) { console.error(`Error reading stdin: ${e}`); } finally { - process.stdin.off('data'); + process.stdin.off('data', () => { }); } } @@ -98,7 +119,6 @@ async function handleData(messageText, prettier) { await handleMessage(prettier, message); } catch (e) { sendResponse(makeError(`Request JSON parse error: ${e}`)); - return; } } @@ -109,7 +129,8 @@ async function handleData(messageText, prettier) { // error async function handleMessage(prettier, message) { - console.log(`message: ${message}`); + // TODO kb handle message.method, message.params and message.id + console.log(`message: ${JSON.stringify(message)}`); sendResponse({ method: "hi", result: null }); } From dca93fb1770f658a1fd92a2ced73f66553dff1cf Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 17 Sep 2023 01:21:46 +0300 Subject: [PATCH 33/92] Initialize prettier_server.js wrapper along with default prettier --- crates/prettier/prettier_server/.gitignore | 1 - crates/prettier/prettier_server/.zed/settings.json | 7 ------- crates/prettier/prettier_server/package-lock.json | 12 ------------ crates/prettier/prettier_server/package.json | 14 -------------- crates/prettier/src/prettier.rs | 2 ++ .../src/index.js => src/prettier_server.js} | 0 crates/project/src/project.rs | 10 +++++----- 7 files changed, 7 insertions(+), 39 deletions(-) delete mode 100644 crates/prettier/prettier_server/.gitignore delete mode 100644 crates/prettier/prettier_server/.zed/settings.json delete mode 100644 crates/prettier/prettier_server/package-lock.json delete mode 100644 crates/prettier/prettier_server/package.json rename crates/prettier/{prettier_server/src/index.js => src/prettier_server.js} (100%) diff --git a/crates/prettier/prettier_server/.gitignore b/crates/prettier/prettier_server/.gitignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/crates/prettier/prettier_server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/crates/prettier/prettier_server/.zed/settings.json b/crates/prettier/prettier_server/.zed/settings.json deleted file mode 100644 index 856536c62d..0000000000 --- a/crates/prettier/prettier_server/.zed/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "languages": { - "JavaScript": { - "tab_size": 4 - } - } -} diff --git a/crates/prettier/prettier_server/package-lock.json b/crates/prettier/prettier_server/package-lock.json deleted file mode 100644 index 01fd75f3dd..0000000000 --- a/crates/prettier/prettier_server/package-lock.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "prettier_server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "prettier_server", - "version": "1.0.0" - } - } -} diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json deleted file mode 100644 index a591a37bdb..0000000000 --- a/crates/prettier/prettier_server/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "prettier_server", - "version": "1.0.0", - "description": "", - "main": "src/index.js", - "scripts": { - "start": "node src/index.js", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Zed Industries", - "dev-dependencies": { - "prettier": "^3.0" - } -} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index d68af349d9..e5b001f226 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -18,6 +18,8 @@ pub struct LocateStart { pub starting_path: Arc, } +pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); + impl Prettier { // This was taken from the prettier-vscode extension. pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/src/prettier_server.js similarity index 100% rename from crates/prettier/prettier_server/src/index.js rename to crates/prettier/src/prettier_server.js diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ffc15ee0d3..b3d00afd8a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -79,7 +79,7 @@ use std::{ time::{Duration, Instant}, }; use terminals::Terminals; -use text::Anchor; +use text::{Anchor, LineEnding, Rope}; use util::{ debug_panic, defer, http::HttpClient, @@ -8350,10 +8350,10 @@ impl Project { let fs = Arc::clone(&self.fs); cx.background() .spawn(async move { - let prettier_dir_metadata = fs.metadata(default_prettier_dir).await.with_context(|| format!("fetching FS metadata for prettier default dir {default_prettier_dir:?}"))?; - if prettier_dir_metadata.is_none() { - fs.create_dir(default_prettier_dir).await.with_context(|| format!("creating prettier default dir {default_prettier_dir:?}"))?; - } + let prettier_wrapper_path = default_prettier_dir.join("prettier_server.js"); + // method creates parent directory if it doesn't exist + fs.save(&prettier_wrapper_path, &Rope::from(prettier::PRETTIER_SERVER_JS), LineEnding::Unix).await + .with_context(|| format!("writing prettier_server.js file at {prettier_wrapper_path:?}"))?; let packages_to_versions = future::try_join_all( prettier_plugins From 2a68f014027b501d1a15cdfbf58358540543b086 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 14:56:40 +0300 Subject: [PATCH 34/92] Draft prettier_server formatting --- Cargo.lock | 1 + crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 85 +++++++++++++++++++++---- crates/prettier/src/prettier_server.js | 86 ++++++++++++++++---------- crates/project/src/project.rs | 25 +++----- 5 files changed, 139 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d7e250377..1795d3d748 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5527,6 +5527,7 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "lsp", "node_runtime", "serde", "serde_derive", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index ab8d4d9e16..d10e8abdf9 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -10,6 +10,7 @@ path = "src/prettier.rs" language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } +lsp = { path = "../lsp" } node_runtime = { path = "../node_runtime"} util = { path = "../util" } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index e5b001f226..fc7736b0e0 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -4,12 +4,15 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; -use gpui::ModelHandle; +use gpui::{AsyncAppContext, ModelHandle, Task}; use language::{Buffer, Diff}; +use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; +use serde::{Deserialize, Serialize}; +use util::paths::DEFAULT_PRETTIER_DIR; pub struct Prettier { - _private: (), + server: Arc, } #[derive(Debug)] @@ -18,7 +21,9 @@ pub struct LocateStart { pub starting_path: Arc, } +pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js"; pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); +const PRETTIER_PACKAGE_NAME: &str = "prettier"; impl Prettier { // This was taken from the prettier-vscode extension. @@ -141,16 +146,55 @@ impl Prettier { } } - pub async fn start(prettier_dir: &Path, node: Arc) -> anyhow::Result { - anyhow::ensure!( - prettier_dir.is_dir(), - "Prettier dir {prettier_dir:?} is not a directory" - ); - anyhow::bail!("TODO kb: start prettier server in {prettier_dir:?}") + pub fn start( + prettier_dir: PathBuf, + node: Arc, + cx: AsyncAppContext, + ) -> Task> { + cx.spawn(|cx| async move { + anyhow::ensure!( + prettier_dir.is_dir(), + "Prettier dir {prettier_dir:?} is not a directory" + ); + let prettier_server = DEFAULT_PRETTIER_DIR.join(PRETTIER_SERVER_FILE); + anyhow::ensure!( + prettier_server.is_file(), + "no prettier server package found at {prettier_server:?}" + ); + + let node_path = node.binary_path().await?; + let server = LanguageServer::new( + LanguageServerId(0), + LanguageServerBinary { + path: node_path, + arguments: vec![prettier_server.into(), prettier_dir.into()], + }, + Path::new("/"), + None, + cx, + ) + .context("prettier server creation")?; + let server = server + .initialize(None) + .await + .context("prettier server initialization")?; + Ok(Self { server }) + }) } - pub async fn format(&self, buffer: &ModelHandle) -> anyhow::Result { - todo!() + pub async fn format( + &self, + buffer: &ModelHandle, + cx: &AsyncAppContext, + ) -> anyhow::Result { + let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text()); + let response = self + .server + .request::(PrettierFormatParams { text: buffer_text }) + .await + .context("prettier format request")?; + dbg!("Formatted text", response.text); + anyhow::bail!("TODO kb calculate the diff") } pub async fn clear_cache(&self) -> anyhow::Result<()> { @@ -158,7 +202,6 @@ impl Prettier { } } -const PRETTIER_PACKAGE_NAME: &str = "prettier"; async fn find_closest_prettier_dir( paths_to_check: Vec, fs: &dyn Fs, @@ -206,3 +249,23 @@ async fn find_closest_prettier_dir( } Ok(None) } + +enum PrettierFormat {} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct PrettierFormatParams { + text: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct PrettierFormatResult { + text: String, +} + +impl lsp::request::Request for PrettierFormat { + type Params = PrettierFormatParams; + type Result = PrettierFormatResult; + const METHOD: &'static str = "prettier/format"; +} diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index d232ac9efe..0caa45b917 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -5,17 +5,17 @@ const { once } = require('events'); const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { - console.error(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); + sendResponse(makeError(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`)); process.exit(1); } fs.stat(prettierContainerPath, (err, stats) => { if (err) { - console.error(`Path '${prettierContainerPath}' does not exist.`); + sendResponse(makeError(`Path '${prettierContainerPath}' does not exist.`)); process.exit(1); } if (!stats.isDirectory()) { - console.log(`Path '${prettierContainerPath}' exists but is not a directory.`); + sendResponse(makeError(`Path '${prettierContainerPath}' exists but is not a directory.`)); process.exit(1); } }); @@ -26,19 +26,19 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); let prettier; try { prettier = await loadPrettier(prettierPath); - } catch (error) { - console.error("Failed to load prettier: ", error); + } catch (e) { + sendResponse(makeError(`Failed to load prettier: ${e}`)); process.exit(1); } - console.log("Prettier loadded successfully."); + sendResponse(makeError("Prettier loadded successfully.")); process.stdin.resume(); handleBuffer(prettier); })() async function handleBuffer(prettier) { for await (let messageText of readStdin()) { - handleData(messageText, prettier).catch(e => { - console.error("Failed to handle formatter request", e); + handleMessage(messageText, prettier).catch(e => { + sendResponse(makeError(`error during message handling: ${e}`)); }); } } @@ -107,43 +107,63 @@ async function* readStdin() { yield message.toString('utf8'); } } catch (e) { - console.error(`Error reading stdin: ${e}`); + sendResponse(makeError(`Error reading stdin: ${e}`)); } finally { process.stdin.off('data', () => { }); } } -async function handleData(messageText, prettier) { - try { - const message = JSON.parse(messageText); - await handleMessage(prettier, message); - } catch (e) { - sendResponse(makeError(`Request JSON parse error: ${e}`)); +// ? +// shutdown +// error +async function handleMessage(messageText, prettier) { + const message = JSON.parse(messageText); + const { method, id, params } = message; + if (method === undefined) { + throw new Error(`Message method is undefined: ${messageText}`); + } + if (id === undefined) { + throw new Error(`Message id is undefined: ${messageText}`); + } + + if (method === 'prettier/format') { + if (params === undefined || params.text === undefined) { + throw new Error(`Message params.text is undefined: ${messageText}`); + } + let formattedText = await prettier.format(params.text); + sendResponse({ id, result: { text: formattedText } }); + } else if (method === 'prettier/clear_cache') { + prettier.clearConfigCache(); + sendResponse({ id, result: null }); + } else if (method === 'initialize') { + sendResponse({ + id, + result: { + "capabilities": {} + } + }); + } else { + throw new Error(`Unknown method: ${method}`); } } -// format -// clear_cache -// -// shutdown -// error - -async function handleMessage(prettier, message) { - // TODO kb handle message.method, message.params and message.id - console.log(`message: ${JSON.stringify(message)}`); - sendResponse({ method: "hi", result: null }); -} - function makeError(message) { - return { method: "error", message }; + return { + error: { + "code": -32600, // invalid request code + message, + } + }; } function sendResponse(response) { - const message = Buffer.from(JSON.stringify(response)); - const length = Buffer.alloc(4); - length.writeUInt32LE(message.length); - process.stdout.write(length); - process.stdout.write(message); + let responsePayloadString = JSON.stringify({ + jsonrpc: "2.0", + ...response + }); + let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}\r\n\r\n`; + let dataToSend = headers + responsePayloadString; + process.stdout.write(dataToSend); } function loadPrettier(prettierPath) { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b3d00afd8a..84b5f09cac 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -53,7 +53,7 @@ use lsp::{ use lsp_command::*; use node_runtime::NodeRuntime; use postage::watch; -use prettier::{LocateStart, Prettier}; +use prettier::{LocateStart, Prettier, PRETTIER_SERVER_FILE, PRETTIER_SERVER_JS}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -4138,7 +4138,7 @@ impl Project { Ok(prettier) => { format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer) + .format(buffer, &cx) .await .context("formatting via prettier")?, )); @@ -4176,7 +4176,7 @@ impl Project { Ok(prettier) => { format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer) + .format(buffer, &cx) .await .context("formatting via prettier")?, )); @@ -8283,18 +8283,12 @@ impl Project { return existing_prettier; } - let task_prettier_dir = prettier_dir.clone(); + let start_task = Prettier::start(prettier_dir.clone(), node, cx.clone()); let new_prettier_task = cx .background() .spawn(async move { - Ok(Arc::new( - Prettier::start(&task_prettier_dir, node) - .await - .with_context(|| { - format!("starting new prettier for path {task_prettier_dir:?}") - })?, - )) - .map_err(Arc::new) + Ok(Arc::new(start_task.await.context("starting new prettier")?)) + .map_err(Arc::new) }) .shared(); this.update(&mut cx, |project, _| { @@ -8344,16 +8338,17 @@ impl Project { .get(&(worktree, default_prettier_dir.to_path_buf())) { // TODO kb need to compare plugins, install missing and restart prettier + // TODO kb move the entire prettier init logic into prettier.rs return; } let fs = Arc::clone(&self.fs); cx.background() .spawn(async move { - let prettier_wrapper_path = default_prettier_dir.join("prettier_server.js"); + let prettier_wrapper_path = default_prettier_dir.join(PRETTIER_SERVER_FILE); // method creates parent directory if it doesn't exist - fs.save(&prettier_wrapper_path, &Rope::from(prettier::PRETTIER_SERVER_JS), LineEnding::Unix).await - .with_context(|| format!("writing prettier_server.js file at {prettier_wrapper_path:?}"))?; + fs.save(&prettier_wrapper_path, &Rope::from(PRETTIER_SERVER_JS), LineEnding::Unix).await + .with_context(|| format!("writing {PRETTIER_SERVER_FILE} file at {prettier_wrapper_path:?}"))?; let packages_to_versions = future::try_join_all( prettier_plugins From 6a8e3fd02d4cd1936b277cd3a40ddac4a076f0fe Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 17:16:16 +0300 Subject: [PATCH 35/92] Add more parameters into prettier invocations --- crates/prettier/src/prettier.rs | 12 ++++++++++-- crates/prettier/src/prettier_server.js | 24 ++++++++++++++++-------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index fc7736b0e0..180b9c3de5 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -187,10 +187,15 @@ impl Prettier { buffer: &ModelHandle, cx: &AsyncAppContext, ) -> anyhow::Result { - let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text()); + let (buffer_text, buffer_language) = + buffer.read_with(cx, |buffer, _| (buffer.text(), buffer.language().cloned())); let response = self .server - .request::(PrettierFormatParams { text: buffer_text }) + .request::(PrettierFormatParams { + text: buffer_text, + path: None, + parser: None, + }) .await .context("prettier format request")?; dbg!("Formatted text", response.text); @@ -256,6 +261,9 @@ enum PrettierFormat {} #[serde(rename_all = "camelCase")] struct PrettierFormatParams { text: String, + // TODO kb have "options" or something more generic instead? + parser: Option, + path: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 0caa45b917..a0f7f0da5e 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -43,6 +43,8 @@ async function handleBuffer(prettier) { } } +const headerSeparator = "\r\n"; + async function* readStdin() { let buffer = Buffer.alloc(0); let streamEnded = false; @@ -62,13 +64,12 @@ async function* readStdin() { } try { - const headersSeparator = "\r\n\r\n"; let contentLengthHeaderName = 'Content-Length'; let headersLength = null; let messageLength = null; main_loop: while (true) { if (messageLength === null) { - while (buffer.indexOf(headersSeparator) === -1) { + while (buffer.indexOf(`${headerSeparator}${headerSeparator}`) === -1) { if (streamEnded) { await handleStreamEnded('Unexpected end of stream: headers not found'); continue main_loop; @@ -78,16 +79,16 @@ async function* readStdin() { } await once(process.stdin, 'readable'); } - const headers = buffer.subarray(0, buffer.indexOf(headersSeparator)).toString('ascii'); - const contentLengthHeader = headers.split('\r\n').map(header => header.split(': ')) + const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii'); + const contentLengthHeader = headers.split(headerSeparator).map(header => header.split(':')) .filter(header => header[2] === undefined) .filter(header => (header[1] || '').length > 0) .find(header => header[0].trim() === contentLengthHeaderName); if (contentLengthHeader === undefined) { - await handleStreamEnded(`Missing or incorrect Content-Length header: ${headers}`); + await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`); continue main_loop; } - headersLength = headers.length + headersSeparator.length; + headersLength = headers.length + headerSeparator.length * 2; messageLength = parseInt(contentLengthHeader[1], 10); } @@ -130,7 +131,14 @@ async function handleMessage(messageText, prettier) { if (params === undefined || params.text === undefined) { throw new Error(`Message params.text is undefined: ${messageText}`); } - let formattedText = await prettier.format(params.text); + + let options = {}; + if (message.path !== undefined) { + options.filepath = message.path; + } else { + options.parser = message.parser || 'babel'; + } + const formattedText = await prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { prettier.clearConfigCache(); @@ -161,7 +169,7 @@ function sendResponse(response) { jsonrpc: "2.0", ...response }); - let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}\r\n\r\n`; + let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; let dataToSend = headers + responsePayloadString; process.stdout.write(dataToSend); } From e2056756ef97af2add0221ad8916315939d476db Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 17:34:58 +0300 Subject: [PATCH 36/92] Calculate the diff --- crates/prettier/src/prettier.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 180b9c3de5..f94801c01b 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -187,6 +187,7 @@ impl Prettier { buffer: &ModelHandle, cx: &AsyncAppContext, ) -> anyhow::Result { + // TODO kb prettier needs either a path or a `parser` (depends on a language) option to format let (buffer_text, buffer_language) = buffer.read_with(cx, |buffer, _| (buffer.text(), buffer.language().cloned())); let response = self @@ -198,8 +199,8 @@ impl Prettier { }) .await .context("prettier format request")?; - dbg!("Formatted text", response.text); - anyhow::bail!("TODO kb calculate the diff") + let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); + Ok(diff_task.await) } pub async fn clear_cache(&self) -> anyhow::Result<()> { From 2a5b9b635ba1d250453984f9f8dc7fef32811d9d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 18:08:55 +0300 Subject: [PATCH 37/92] Better pass prettier options --- crates/language/src/language.rs | 9 ++++-- crates/prettier/src/prettier.rs | 41 +++++++++++++++++++------- crates/prettier/src/prettier_server.js | 12 +++++--- crates/project/src/project.rs | 2 +- crates/zed/src/languages/css.rs | 2 +- crates/zed/src/languages/html.rs | 2 +- crates/zed/src/languages/json.rs | 2 +- crates/zed/src/languages/php.rs | 7 ++--- crates/zed/src/languages/svelte.rs | 6 +--- crates/zed/src/languages/typescript.rs | 4 +-- crates/zed/src/languages/yaml.rs | 2 +- 11 files changed, 55 insertions(+), 34 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index ed0a0d8ee8..31415a2dd6 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -341,6 +341,7 @@ pub trait LspAdapter: 'static + Send + Sync { // TODO kb enable this for // markdown somehow? // tailwind (needs a css plugin, there are 2 of them) + // svelte (needs a plugin) fn enabled_formatters(&self) -> Vec { Vec::new() } @@ -348,12 +349,16 @@ pub trait LspAdapter: 'static + Send + Sync { #[derive(Clone, Debug, PartialEq, Eq)] pub enum BundledFormatter { - Prettier { plugin_names: Vec }, + Prettier { + parser_name: &'static str, + plugin_names: Vec, + }, } impl BundledFormatter { - pub fn prettier() -> Self { + pub fn prettier(parser_name: &'static str) -> Self { Self::Prettier { + parser_name, plugin_names: Vec::new(), } } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index f94801c01b..68374d8a03 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle, Task}; -use language::{Buffer, Diff}; +use language::{Buffer, BundledFormatter, Diff}; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; @@ -187,16 +187,30 @@ impl Prettier { buffer: &ModelHandle, cx: &AsyncAppContext, ) -> anyhow::Result { - // TODO kb prettier needs either a path or a `parser` (depends on a language) option to format - let (buffer_text, buffer_language) = - buffer.read_with(cx, |buffer, _| (buffer.text(), buffer.language().cloned())); + let params = buffer.read_with(cx, |buffer, cx| { + let path = buffer + .file() + .map(|file| file.full_path(cx)) + .map(|path| path.to_path_buf()); + let parser = buffer.language().and_then(|language| { + language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .find_map(|formatter| match formatter { + BundledFormatter::Prettier { parser_name, .. } => { + Some(parser_name.to_string()) + } + }) + }); + PrettierFormatParams { + text: buffer.text(), + options: FormatOptions { parser, path }, + } + }); let response = self .server - .request::(PrettierFormatParams { - text: buffer_text, - path: None, - parser: None, - }) + .request::(params) .await .context("prettier format request")?; let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); @@ -262,9 +276,14 @@ enum PrettierFormat {} #[serde(rename_all = "camelCase")] struct PrettierFormatParams { text: String, - // TODO kb have "options" or something more generic instead? + options: FormatOptions, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct FormatOptions { parser: Option, - path: Option, + path: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index a0f7f0da5e..5e86720cd8 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -131,12 +131,16 @@ async function handleMessage(messageText, prettier) { if (params === undefined || params.text === undefined) { throw new Error(`Message params.text is undefined: ${messageText}`); } + if (params.options === undefined) { + throw new Error(`Message params.options is undefined: ${messageText}`); + } let options = {}; - if (message.path !== undefined) { - options.filepath = message.path; - } else { - options.parser = message.parser || 'babel'; + if (params.options.path !== undefined) { + options.filepath = params.options.path; + } + if (params.options.parser !== undefined) { + options.parser = params.options.parser; } const formattedText = await prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 84b5f09cac..cf8df9eafd 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8323,7 +8323,7 @@ impl Project { .flat_map(|adapter| adapter.enabled_formatters()) { match formatter { - BundledFormatter::Prettier { plugin_names } => prettier_plugins + BundledFormatter::Prettier { plugin_names, .. } => prettier_plugins .get_or_insert_with(|| HashSet::default()) .extend(plugin_names), } diff --git a/crates/zed/src/languages/css.rs b/crates/zed/src/languages/css.rs index a28523a741..f046437d75 100644 --- a/crates/zed/src/languages/css.rs +++ b/crates/zed/src/languages/css.rs @@ -98,7 +98,7 @@ impl LspAdapter for CssLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("css")] } } diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index af8fb1690c..6f27b7ca8f 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -98,7 +98,7 @@ impl LspAdapter for HtmlLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("html")] } } diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index e66a5d96a6..f017af0a22 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -148,7 +148,7 @@ impl LspAdapter for JsonLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("json")] } } diff --git a/crates/zed/src/languages/php.rs b/crates/zed/src/languages/php.rs index f8f9351111..bf65deb642 100644 --- a/crates/zed/src/languages/php.rs +++ b/crates/zed/src/languages/php.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use collections::HashMap; -use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; @@ -100,13 +100,10 @@ impl LspAdapter for IntelephenseLspAdapter { async fn initialization_options(&self) -> Option { None } + async fn language_ids(&self) -> HashMap { HashMap::from_iter([("PHP".into(), "php".into())]) } - - fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] - } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs index 487720ce46..5e42d80e77 100644 --- a/crates/zed/src/languages/svelte.rs +++ b/crates/zed/src/languages/svelte.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -95,10 +95,6 @@ impl LspAdapter for SvelteLspAdapter { "provideFormatter": true })) } - - fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] - } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 78b1214b1a..f09c964588 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -163,7 +163,7 @@ impl LspAdapter for TypeScriptLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("typescript")] } } @@ -315,7 +315,7 @@ impl LspAdapter for EsLintLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("babel")] } } diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index c9168b3480..1c1ce18668 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -111,7 +111,7 @@ impl LspAdapter for YamlLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("yaml")] } } From 39ad3a625c29f32f30a032b70e30ff7bd0811eb8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 18:36:18 +0300 Subject: [PATCH 38/92] Generify prettier properties, add tabWidth --- crates/prettier/src/prettier.rs | 18 ++++++++++++++---- crates/prettier/src/prettier_server.js | 10 +--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 68374d8a03..a02b139510 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle, Task}; +use language::language_settings::language_settings; use language::{Buffer, BundledFormatter, Diff}; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; @@ -188,11 +189,13 @@ impl Prettier { cx: &AsyncAppContext, ) -> anyhow::Result { let params = buffer.read_with(cx, |buffer, cx| { - let path = buffer - .file() + let buffer_file = buffer.file(); + let buffer_language = buffer.language(); + let language_settings = language_settings(buffer_language, buffer_file, cx); + let path = buffer_file .map(|file| file.full_path(cx)) .map(|path| path.to_path_buf()); - let parser = buffer.language().and_then(|language| { + let parser = buffer_language.and_then(|language| { language .lsp_adapters() .iter() @@ -203,9 +206,14 @@ impl Prettier { } }) }); + let tab_width = Some(language_settings.tab_size.get()); PrettierFormatParams { text: buffer.text(), - options: FormatOptions { parser, path }, + options: FormatOptions { + parser, + path, + tab_width, + }, } }); let response = self @@ -283,7 +291,9 @@ struct PrettierFormatParams { #[serde(rename_all = "camelCase")] struct FormatOptions { parser: Option, + #[serde(rename = "filepath")] path: Option, + tab_width: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 5e86720cd8..2725914334 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -134,15 +134,7 @@ async function handleMessage(messageText, prettier) { if (params.options === undefined) { throw new Error(`Message params.options is undefined: ${messageText}`); } - - let options = {}; - if (params.options.path !== undefined) { - options.filepath = params.options.path; - } - if (params.options.parser !== undefined) { - options.parser = params.options.parser; - } - const formattedText = await prettier.format(params.text, options); + const formattedText = await prettier.format(params.text, params.options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { prettier.clearConfigCache(); From e8409a0108f719a624af2113f2c8cf7f94bc6fcc Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 19 Sep 2023 10:25:33 +0300 Subject: [PATCH 39/92] Even more generic header printing in prettier_server --- crates/prettier/src/prettier_server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 2725914334..02c21811e2 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -44,6 +44,7 @@ async function handleBuffer(prettier) { } const headerSeparator = "\r\n"; +const contentLengthHeaderName = 'Content-Length'; async function* readStdin() { let buffer = Buffer.alloc(0); @@ -64,7 +65,6 @@ async function* readStdin() { } try { - let contentLengthHeaderName = 'Content-Length'; let headersLength = null; let messageLength = null; main_loop: while (true) { @@ -165,7 +165,7 @@ function sendResponse(response) { jsonrpc: "2.0", ...response }); - let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; + let headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; let dataToSend = headers + responsePayloadString; process.stdout.write(dataToSend); } From 4b15a2bd63c1e702178ff83b15ea29dafb437c5e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 12:19:12 +0300 Subject: [PATCH 40/92] Rebase fixes --- crates/semantic_index/examples/eval.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/semantic_index/examples/eval.rs b/crates/semantic_index/examples/eval.rs index 573cf73d78..33d6b3689c 100644 --- a/crates/semantic_index/examples/eval.rs +++ b/crates/semantic_index/examples/eval.rs @@ -494,6 +494,7 @@ fn main() { let project = cx.update(|cx| { Project::local( client.clone(), + node_runtime::FakeNodeRuntime::new(), user_store.clone(), languages.clone(), fs.clone(), From 6cac58b34c67507d555ffccda6a04d86a312e47c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 13:07:10 +0300 Subject: [PATCH 41/92] Add prettier language servers to LSP logs panel --- crates/prettier/src/prettier.rs | 68 ++++++++++++++++++--------------- crates/project/src/project.rs | 27 ++++++++++--- 2 files changed, 59 insertions(+), 36 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index a02b139510..26368ddcdc 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; -use gpui::{AsyncAppContext, ModelHandle, Task}; +use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; use language::{Buffer, BundledFormatter, Diff}; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; @@ -147,40 +147,42 @@ impl Prettier { } } - pub fn start( + pub async fn start( + server_id: LanguageServerId, prettier_dir: PathBuf, node: Arc, cx: AsyncAppContext, - ) -> Task> { - cx.spawn(|cx| async move { - anyhow::ensure!( - prettier_dir.is_dir(), - "Prettier dir {prettier_dir:?} is not a directory" - ); - let prettier_server = DEFAULT_PRETTIER_DIR.join(PRETTIER_SERVER_FILE); - anyhow::ensure!( - prettier_server.is_file(), - "no prettier server package found at {prettier_server:?}" - ); + ) -> anyhow::Result { + let backgroud = cx.background(); + anyhow::ensure!( + prettier_dir.is_dir(), + "Prettier dir {prettier_dir:?} is not a directory" + ); + let prettier_server = DEFAULT_PRETTIER_DIR.join(PRETTIER_SERVER_FILE); + anyhow::ensure!( + prettier_server.is_file(), + "no prettier server package found at {prettier_server:?}" + ); - let node_path = node.binary_path().await?; - let server = LanguageServer::new( - LanguageServerId(0), - LanguageServerBinary { - path: node_path, - arguments: vec![prettier_server.into(), prettier_dir.into()], - }, - Path::new("/"), - None, - cx, - ) - .context("prettier server creation")?; - let server = server - .initialize(None) - .await - .context("prettier server initialization")?; - Ok(Self { server }) - }) + let node_path = backgroud + .spawn(async move { node.binary_path().await }) + .await?; + let server = LanguageServer::new( + server_id, + LanguageServerBinary { + path: node_path, + arguments: vec![prettier_server.into(), prettier_dir.into()], + }, + Path::new("/"), + None, + cx, + ) + .context("prettier server creation")?; + let server = backgroud + .spawn(server.initialize(None)) + .await + .context("prettier server initialization")?; + Ok(Self { server }) } pub async fn format( @@ -228,6 +230,10 @@ impl Prettier { pub async fn clear_cache(&self) -> anyhow::Result<()> { todo!() } + + pub fn server(&self) -> &Arc { + &self.server + } } async fn find_closest_prettier_dir( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index cf8df9eafd..8e080b594e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8283,12 +8283,29 @@ impl Project { return existing_prettier; } - let start_task = Prettier::start(prettier_dir.clone(), node, cx.clone()); + let task_prettier_dir = prettier_dir.clone(); + let weak_project = this.downgrade(); + let new_server_id = + this.update(&mut cx, |this, _| this.languages.next_language_server_id()); let new_prettier_task = cx - .background() - .spawn(async move { - Ok(Arc::new(start_task.await.context("starting new prettier")?)) - .map_err(Arc::new) + .spawn(|mut cx| async move { + let prettier = + Prettier::start(new_server_id, task_prettier_dir, node, cx.clone()) + .await + .context("prettier start") + .map_err(Arc::new)?; + if let Some(project) = weak_project.upgrade(&mut cx) { + let prettier_server = Arc::clone(prettier.server()); + project.update(&mut cx, |project, cx| { + project.supplementary_language_servers.insert( + new_server_id, + // TODO kb same name repeats for different prettiers, distinguish + (LanguageServerName(Arc::from("prettier")), prettier_server), + ); + cx.emit(Event::LanguageServerAdded(new_server_id)); + }); + } + anyhow::Ok(Arc::new(prettier)).map_err(Arc::new) }) .shared(); this.update(&mut cx, |project, _| { From 06cac18d788c1373d1e38d22c9fcc0fe21ac6546 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 14:51:02 +0300 Subject: [PATCH 42/92] Return message id in prettier_server error responses --- crates/prettier/src/prettier_server.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 02c21811e2..2e7e1fd2e6 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -37,8 +37,15 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); async function handleBuffer(prettier) { for await (let messageText of readStdin()) { - handleMessage(messageText, prettier).catch(e => { - sendResponse(makeError(`error during message handling: ${e}`)); + let message; + try { + message = JSON.parse(messageText); + } catch (e) { + sendResponse(makeError(`Failed to parse message '${messageText}': ${e}`)); + continue; + } + handleMessage(message, prettier).catch(e => { + sendResponse({ id: message.id, ...makeError(`error during message handling: ${e}`) }); }); } } @@ -117,22 +124,21 @@ async function* readStdin() { // ? // shutdown // error -async function handleMessage(messageText, prettier) { - const message = JSON.parse(messageText); +async function handleMessage(message, prettier) { const { method, id, params } = message; if (method === undefined) { - throw new Error(`Message method is undefined: ${messageText}`); + throw new Error(`Message method is undefined: ${JSON.stringify(message)}`); } if (id === undefined) { - throw new Error(`Message id is undefined: ${messageText}`); + throw new Error(`Message id is undefined: ${JSON.stringify(message)}`); } if (method === 'prettier/format') { if (params === undefined || params.text === undefined) { - throw new Error(`Message params.text is undefined: ${messageText}`); + throw new Error(`Message params.text is undefined: ${JSON.stringify(message)}`); } if (params.options === undefined) { - throw new Error(`Message params.options is undefined: ${messageText}`); + throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } const formattedText = await prettier.format(params.text, params.options); sendResponse({ id, result: { text: formattedText } }); From b6872702070c7f7681324df94ebf8721a28172d5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 14:54:20 +0300 Subject: [PATCH 43/92] Implement missing prettier_server clear method --- crates/prettier/src/prettier.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 26368ddcdc..d5df6f092c 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -209,7 +209,7 @@ impl Prettier { }) }); let tab_width = Some(language_settings.tab_size.get()); - PrettierFormatParams { + FormatParams { text: buffer.text(), options: FormatOptions { parser, @@ -220,7 +220,7 @@ impl Prettier { }); let response = self .server - .request::(params) + .request::(params) .await .context("prettier format request")?; let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); @@ -228,7 +228,10 @@ impl Prettier { } pub async fn clear_cache(&self) -> anyhow::Result<()> { - todo!() + self.server + .request::(()) + .await + .context("prettier clear cache") } pub fn server(&self) -> &Arc { @@ -284,11 +287,11 @@ async fn find_closest_prettier_dir( Ok(None) } -enum PrettierFormat {} +enum Format {} #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct PrettierFormatParams { +struct FormatParams { text: String, options: FormatOptions, } @@ -304,12 +307,20 @@ struct FormatOptions { #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct PrettierFormatResult { +struct FormatResult { text: String, } -impl lsp::request::Request for PrettierFormat { - type Params = PrettierFormatParams; - type Result = PrettierFormatResult; +impl lsp::request::Request for Format { + type Params = FormatParams; + type Result = FormatResult; const METHOD: &'static str = "prettier/format"; } + +enum ClearCache {} + +impl lsp::request::Request for ClearCache { + type Params = (); + type Result = (); + const METHOD: &'static str = "prettier/clear_cache"; +} From 1b70e7d0df21d74f5133636580550d33fa4fbb28 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 15:00:26 +0300 Subject: [PATCH 44/92] Before server startup, log to stderr --- crates/prettier/src/prettier_server.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 2e7e1fd2e6..e5c131cc00 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -5,17 +5,17 @@ const { once } = require('events'); const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { - sendResponse(makeError(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`)); + process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); process.exit(1); } fs.stat(prettierContainerPath, (err, stats) => { if (err) { - sendResponse(makeError(`Path '${prettierContainerPath}' does not exist.`)); + process.stderr.write(`Path '${prettierContainerPath}' does not exist.`); process.exit(1); } if (!stats.isDirectory()) { - sendResponse(makeError(`Path '${prettierContainerPath}' exists but is not a directory.`)); + process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory.`); process.exit(1); } }); @@ -27,10 +27,10 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); try { prettier = await loadPrettier(prettierPath); } catch (e) { - sendResponse(makeError(`Failed to load prettier: ${e}`)); + process.stderr.write(`Failed to load prettier: ${e}`); process.exit(1); } - sendResponse(makeError("Prettier loadded successfully.")); + process.stderr.write("Prettier loadded successfully."); process.stdin.resume(); handleBuffer(prettier); })() From f42cb109a0f115bed92720721c4cf59a73f93574 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 21 Sep 2023 15:46:50 +0300 Subject: [PATCH 45/92] Improve prettier_server LSP names in the log panel --- crates/prettier/src/prettier.rs | 27 ++++++++++++-- crates/project/src/project.rs | 63 +++++++++++++++++++++++++++------ 2 files changed, 76 insertions(+), 14 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index d5df6f092c..5723cc67fc 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -13,6 +13,9 @@ use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; pub struct Prettier { + worktree_id: Option, + default: bool, + prettier_dir: PathBuf, server: Arc, } @@ -143,11 +146,12 @@ impl Prettier { .with_context(|| format!("finding prettier starting with {starting_path:?}"))? { Some(prettier_dir) => Ok(prettier_dir), - None => Ok(util::paths::DEFAULT_PRETTIER_DIR.to_path_buf()), + None => Ok(DEFAULT_PRETTIER_DIR.to_path_buf()), } } pub async fn start( + worktree_id: Option, server_id: LanguageServerId, prettier_dir: PathBuf, node: Arc, @@ -171,7 +175,7 @@ impl Prettier { server_id, LanguageServerBinary { path: node_path, - arguments: vec![prettier_server.into(), prettier_dir.into()], + arguments: vec![prettier_server.into(), prettier_dir.as_path().into()], }, Path::new("/"), None, @@ -182,7 +186,12 @@ impl Prettier { .spawn(server.initialize(None)) .await .context("prettier server initialization")?; - Ok(Self { server }) + Ok(Self { + worktree_id, + server, + default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), + prettier_dir, + }) } pub async fn format( @@ -237,6 +246,18 @@ impl Prettier { pub fn server(&self) -> &Arc { &self.server } + + pub fn is_default(&self) -> bool { + self.default + } + + pub fn prettier_dir(&self) -> &Path { + &self.prettier_dir + } + + pub fn worktree_id(&self) -> Option { + self.worktree_id + } } async fn find_closest_prettier_dir( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8e080b594e..2afedecad6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8289,19 +8289,60 @@ impl Project { this.update(&mut cx, |this, _| this.languages.next_language_server_id()); let new_prettier_task = cx .spawn(|mut cx| async move { - let prettier = - Prettier::start(new_server_id, task_prettier_dir, node, cx.clone()) - .await - .context("prettier start") - .map_err(Arc::new)?; + let prettier = Prettier::start( + worktree_id.map(|id| id.to_usize()), + new_server_id, + task_prettier_dir, + node, + cx.clone(), + ) + .await + .context("prettier start") + .map_err(Arc::new)?; if let Some(project) = weak_project.upgrade(&mut cx) { - let prettier_server = Arc::clone(prettier.server()); project.update(&mut cx, |project, cx| { - project.supplementary_language_servers.insert( - new_server_id, - // TODO kb same name repeats for different prettiers, distinguish - (LanguageServerName(Arc::from("prettier")), prettier_server), - ); + let name = if prettier.is_default() { + LanguageServerName(Arc::from("prettier (default)")) + } else { + let prettier_dir = prettier.prettier_dir(); + let worktree_path = prettier + .worktree_id() + .map(WorktreeId::from_usize) + .and_then(|id| project.worktree_for_id(id, cx)) + .map(|worktree| worktree.read(cx).abs_path()); + match worktree_path { + Some(worktree_path) => { + if worktree_path.as_ref() == prettier_dir { + LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default() + ))) + } else { + let dir_to_display = match prettier_dir + .strip_prefix(&worktree_path) + .ok() + { + Some(relative_path) => relative_path, + None => prettier_dir, + }; + LanguageServerName(Arc::from(format!( + "prettier ({})", + dir_to_display.display(), + ))) + } + } + None => LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir.display(), + ))), + } + }; + project + .supplementary_language_servers + .insert(new_server_id, (name, Arc::clone(prettier.server()))); cx.emit(Event::LanguageServerAdded(new_server_id)); }); } From d021842fa1ff8dcc76f00e5b5a1950134edb11c9 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 21 Sep 2023 23:49:22 +0300 Subject: [PATCH 46/92] Properly log pre-lsp prettier_server events --- crates/prettier/src/prettier_server.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index e5c131cc00..001adca474 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -5,17 +5,17 @@ const { once } = require('events'); const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { - process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); + process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`); process.exit(1); } fs.stat(prettierContainerPath, (err, stats) => { if (err) { - process.stderr.write(`Path '${prettierContainerPath}' does not exist.`); + process.stderr.write(`Path '${prettierContainerPath}' does not exist\n`); process.exit(1); } if (!stats.isDirectory()) { - process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory.`); + process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory\n`); process.exit(1); } }); @@ -27,10 +27,10 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); try { prettier = await loadPrettier(prettierPath); } catch (e) { - process.stderr.write(`Failed to load prettier: ${e}`); + process.stderr.write(`Failed to load prettier: ${e}\n`); process.exit(1); } - process.stderr.write("Prettier loadded successfully."); + process.stderr.write("Prettier loadded successfully\n"); process.stdin.resume(); handleBuffer(prettier); })() From f4667cbc3374463053f6e315bd90a535909a8ae0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 15:52:34 +0300 Subject: [PATCH 47/92] Resolve prettier config on server init --- crates/prettier/src/prettier.rs | 1 + crates/prettier/src/prettier_server.js | 20 +++++++++++++++----- crates/project/src/project.rs | 24 +++++++++++++++++++++++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 5723cc67fc..01f1a055d3 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -222,6 +222,7 @@ impl Prettier { text: buffer.text(), options: FormatOptions { parser, + // TODO kb is not absolute now path, tab_width, }, diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 001adca474..5896c295f6 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -21,18 +21,27 @@ fs.stat(prettierContainerPath, (err, stats) => { }); const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); +class Prettier { + constructor(path, prettier, config) { + this.path = path; + this.prettier = prettier; + this.config = config; + } +} (async () => { let prettier; + let config; try { prettier = await loadPrettier(prettierPath); + config = await prettier.resolveConfig(prettierPath) || {}; } catch (e) { process.stderr.write(`Failed to load prettier: ${e}\n`); process.exit(1); } - process.stderr.write("Prettier loadded successfully\n"); + process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${config}\n`); process.stdin.resume(); - handleBuffer(prettier); + handleBuffer(new Prettier(prettierPath, prettier, config)); })() async function handleBuffer(prettier) { @@ -121,7 +130,7 @@ async function* readStdin() { } } -// ? +// TODO kb, more methods? // shutdown // error async function handleMessage(message, prettier) { @@ -140,10 +149,11 @@ async function handleMessage(message, prettier) { if (params.options === undefined) { throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } - const formattedText = await prettier.format(params.text, params.options); + const formattedText = await prettier.prettier.format(params.text, { ...prettier.config, ...params.options }); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { - prettier.clearConfigCache(); + prettier.prettier.clearConfigCache(); + prettier.config = await prettier.prettier.resolveConfig(prettier.path) || {}; sendResponse({ id, result: null }); } else if (method === 'initialize') { sendResponse({ diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2afedecad6..a25fc2e178 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -911,7 +911,7 @@ impl Project { .detach(); } - // TODO kb restart all formatters if settings change + // TODO kb restart all default formatters if Zed prettier settings change for (worktree, language, settings) in language_formatters_to_check { self.maybe_start_default_formatters(worktree, &language, &settings, cx); } @@ -5987,6 +5987,7 @@ impl Project { this.update_local_worktree_buffers(&worktree, changes, cx); this.update_local_worktree_language_servers(&worktree, changes, cx); this.update_local_worktree_settings(&worktree, changes, cx); + this.update_prettier_settings(&worktree, changes, cx); cx.emit(Event::WorktreeUpdatedEntries( worktree.read(cx).id(), changes.clone(), @@ -6366,6 +6367,27 @@ impl Project { .detach(); } + fn update_prettier_settings( + &self, + worktree: &ModelHandle, + changes: &[(Arc, ProjectEntryId, PathChange)], + cx: &mut ModelContext<'_, Project>, + ) { + let prettier_config_files = Prettier::CONFIG_FILE_NAMES + .iter() + .map(Path::new) + .collect::>(); + let prettier_config_changes = changes + .iter() + .filter(|(path, _, _)| prettier_config_files.contains(path.as_ref())) + .collect::>(); + dbg!(prettier_config_changes); + dbg!(changes.len()); + // TODO kb: reset caches for all worktree-related prettiers (including the default one) on prettier config file _changes_ + // prepare node + prettier + plugins + prettier_server on files with Languages that have prettier formatter. Do not start it yet. + // !! Ignore **/node_modules/** files in both checks above. + } + pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { let new_active_entry = entry.and_then(|project_path| { let worktree = self.worktree_for_id(project_path.worktree_id, cx)?; From b109075bf2e3eaa3aa5b11ea3b2e21ba8daf3827 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 16:40:45 +0300 Subject: [PATCH 48/92] Watch for prettier file changes --- crates/project/src/project.rs | 60 +++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a25fc2e178..bcf640d865 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6377,15 +6377,58 @@ impl Project { .iter() .map(Path::new) .collect::>(); - let prettier_config_changes = changes + + let prettier_config_file_changed = changes .iter() - .filter(|(path, _, _)| prettier_config_files.contains(path.as_ref())) + .filter(|(_, _, change)| !matches!(change, PathChange::Loaded)) + .filter(|(path, _, _)| { + !path + .components() + .any(|component| component.as_os_str().to_str() == Some("node_modules")) + }) + .find(|(path, _, _)| prettier_config_files.contains(path.as_ref())); + let current_worktree_id = worktree.read(cx).id(); + if let Some((config_path, _, _)) = prettier_config_file_changed { + log::info!( + "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}" + ); + } + + let prettiers_to_reload = self + .prettier_instances + .iter() + .filter_map(|((worktree_id, prettier_path), prettier_task)| { + if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) { + Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) + } else { + None + } + }) .collect::>(); - dbg!(prettier_config_changes); - dbg!(changes.len()); - // TODO kb: reset caches for all worktree-related prettiers (including the default one) on prettier config file _changes_ - // prepare node + prettier + plugins + prettier_server on files with Languages that have prettier formatter. Do not start it yet. - // !! Ignore **/node_modules/** files in both checks above. + + cx.background() + .spawn(async move { + for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| { + async move { + prettier_task.await? + .clear_cache() + .await + .with_context(|| { + format!( + "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" + ) + }) + .map_err(Arc::new) + } + })) + .await + { + if let Err(e) = task_result { + log::error!("Failed to clear cache for prettier: {e:#}"); + } + } + }) + .detach(); } pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { @@ -8305,6 +8348,7 @@ impl Project { return existing_prettier; } + log::info!("Found prettier at {prettier_dir:?}, starting."); let task_prettier_dir = prettier_dir.clone(); let weak_project = this.downgrade(); let new_server_id = @@ -8321,6 +8365,8 @@ impl Project { .await .context("prettier start") .map_err(Arc::new)?; + log::info!("Had started prettier in {:?}", prettier.prettier_dir()); + if let Some(project) = weak_project.upgrade(&mut cx) { project.update(&mut cx, |project, cx| { let name = if prettier.is_default() { From 6ec3927dd3bdd0e0729990d9dd9fbdcf196b6743 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 18:40:12 +0300 Subject: [PATCH 49/92] Allow to configure default prettier --- Cargo.lock | 1 + assets/settings/default.json | 16 ++++++++++- crates/language/src/language_settings.rs | 8 ++++-- crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 36 ++++++++++++++++++++---- crates/prettier/src/prettier_server.js | 8 +++++- crates/project/src/project.rs | 3 +- 7 files changed, 60 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1795d3d748..4d9b3ee713 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5523,6 +5523,7 @@ name = "prettier" version = "0.1.0" dependencies = [ "anyhow", + "collections", "fs", "futures 0.3.28", "gpui", diff --git a/assets/settings/default.json b/assets/settings/default.json index be47ac9c8c..1611d80e2f 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -199,7 +199,11 @@ // "arguments": ["--stdin-filepath", "{buffer_path}"] // } // } - // TODO kb description + // 3. Format code using Zed's Prettier integration: + // "formatter": "prettier" + // 4. Default. Format files using Zed's Prettier integration (if applicable), + // or falling back to formatting via language server: + // "formatter": "auto" "formatter": "auto", // How to soft-wrap long lines of text. This setting can take // three values: @@ -430,6 +434,16 @@ "tab_size": 2 } }, + // Zed's Prettier integration settings. + // If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if + // project has no other Prettier installed. + "prettier": { + // Use regular Prettier json configuration: + // "trailingComma": "es5", + // "tabWidth": 4, + // "semi": false, + // "singleQuote": true + }, // LSP Specific settings. "lsp": { // Specify the LSP name as a key here. diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 8778ba8d64..9cac5c523e 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -50,6 +50,7 @@ pub struct LanguageSettings { pub remove_trailing_whitespace_on_save: bool, pub ensure_final_newline_on_save: bool, pub formatter: Formatter, + pub prettier: HashMap, pub enable_language_server: bool, pub show_copilot_suggestions: bool, pub show_whitespaces: ShowWhitespaceSetting, @@ -98,6 +99,8 @@ pub struct LanguageSettingsContent { #[serde(default)] pub formatter: Option, #[serde(default)] + pub prettier: Option>, + #[serde(default)] pub enable_language_server: Option, #[serde(default)] pub show_copilot_suggestions: Option, @@ -155,9 +158,7 @@ pub enum Formatter { #[default] Auto, LanguageServer, - Prettier { - config: (), // Support some of the most important settings in the prettier-vscode extension. - }, + Prettier, External { command: Arc, arguments: Arc<[String]>, @@ -397,6 +398,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent src.preferred_line_length, ); merge(&mut settings.formatter, src.formatter.clone()); + merge(&mut settings.prettier, src.prettier.clone()); merge(&mut settings.format_on_save, src.format_on_save.clone()); merge( &mut settings.remove_trailing_whitespace_on_save, diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index d10e8abdf9..129e928da2 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" path = "src/prettier.rs" [dependencies] +collections = { path = "../collections"} language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 01f1a055d3..77baf6cbf3 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,8 +1,9 @@ -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; +use collections::HashMap; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; @@ -202,7 +203,6 @@ impl Prettier { let params = buffer.read_with(cx, |buffer, cx| { let buffer_file = buffer.file(); let buffer_language = buffer.language(); - let language_settings = language_settings(buffer_language, buffer_file, cx); let path = buffer_file .map(|file| file.full_path(cx)) .map(|path| path.to_path_buf()); @@ -217,14 +217,38 @@ impl Prettier { } }) }); - let tab_width = Some(language_settings.tab_size.get()); + + let prettier_options = if self.default { + let language_settings = language_settings(buffer_language, buffer_file, cx); + let mut options = language_settings.prettier.clone(); + if !options.contains_key("tabWidth") { + options.insert( + "tabWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.tab_size.get(), + )), + ); + } + if !options.contains_key("printWidth") { + options.insert( + "printWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.preferred_line_length, + )), + ); + } + Some(options) + } else { + None + }; + FormatParams { text: buffer.text(), options: FormatOptions { parser, // TODO kb is not absolute now path, - tab_width, + prettier_options, }, } }); @@ -318,13 +342,13 @@ struct FormatParams { options: FormatOptions, } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FormatOptions { parser: Option, #[serde(rename = "filepath")] path: Option, - tab_width: Option, + prettier_options: Option>, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 5896c295f6..0a16a73b14 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -149,7 +149,13 @@ async function handleMessage(message, prettier) { if (params.options === undefined) { throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } - const formattedText = await prettier.prettier.format(params.text, { ...prettier.config, ...params.options }); + + const options = { + ...(params.options.prettierOptions || prettier.config), + parser: params.options.parser, + path: params.options.path + }; + const formattedText = await prettier.prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { prettier.prettier.clearConfigCache(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bcf640d865..059256ee3f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -911,7 +911,6 @@ impl Project { .detach(); } - // TODO kb restart all default formatters if Zed prettier settings change for (worktree, language, settings) in language_formatters_to_check { self.maybe_start_default_formatters(worktree, &language, &settings, cx); } @@ -8428,7 +8427,7 @@ impl Project { } fn maybe_start_default_formatters( - &mut self, + &self, worktree: Option, new_language: &Language, language_settings: &LanguageSettings, From afee29ad3f9df1eb79d43ed12a593e3449bc0c55 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 18:48:30 +0300 Subject: [PATCH 50/92] Do not clear cache for default prettiers --- crates/prettier/src/prettier_server.js | 3 ++ crates/project/src/project.rs | 72 +++++++++++++------------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 0a16a73b14..dcc625b877 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -155,6 +155,9 @@ async function handleMessage(message, prettier) { parser: params.options.parser, path: params.options.path }; + // TODO kb always resolve prettier config for each file. + // need to understand if default prettier can be affected by other configs in the project + // (restart default prettiers on config changes too then) const formattedText = await prettier.prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 059256ee3f..df504ad506 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6391,43 +6391,42 @@ impl Project { log::info!( "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}" ); + let prettiers_to_reload = self + .prettier_instances + .iter() + .filter_map(|((worktree_id, prettier_path), prettier_task)| { + if worktree_id == &Some(current_worktree_id) { + Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) + } else { + None + } + }) + .collect::>(); + + cx.background() + .spawn(async move { + for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| { + async move { + prettier_task.await? + .clear_cache() + .await + .with_context(|| { + format!( + "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" + ) + }) + .map_err(Arc::new) + } + })) + .await + { + if let Err(e) = task_result { + log::error!("Failed to clear cache for prettier: {e:#}"); + } + } + }) + .detach(); } - - let prettiers_to_reload = self - .prettier_instances - .iter() - .filter_map(|((worktree_id, prettier_path), prettier_task)| { - if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) { - Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) - } else { - None - } - }) - .collect::>(); - - cx.background() - .spawn(async move { - for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| { - async move { - prettier_task.await? - .clear_cache() - .await - .with_context(|| { - format!( - "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" - ) - }) - .map_err(Arc::new) - } - })) - .await - { - if let Err(e) = task_result { - log::error!("Failed to clear cache for prettier: {e:#}"); - } - } - }) - .detach(); } pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { @@ -8410,6 +8409,7 @@ impl Project { project .supplementary_language_servers .insert(new_server_id, (name, Arc::clone(prettier.server()))); + // TODO kb could there be a race with multiple default prettier instances added? cx.emit(Event::LanguageServerAdded(new_server_id)); }); } From 8a807102a668c57eb18c8dfbdeef467066f25685 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 23:49:03 +0300 Subject: [PATCH 51/92] Properly support prettier plugins --- Cargo.lock | 1 + crates/language/src/language.rs | 10 ++-- crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 72 ++++++++++++++++++++++---- crates/prettier/src/prettier_server.js | 1 + crates/project/src/project.rs | 30 ++++++----- crates/zed/src/languages/svelte.rs | 9 +++- crates/zed/src/languages/tailwind.rs | 9 +++- 8 files changed, 100 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d9b3ee713..0175755f96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5528,6 +5528,7 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "log", "lsp", "node_runtime", "serde", diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 31415a2dd6..c565d09c98 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -338,10 +338,6 @@ pub trait LspAdapter: 'static + Send + Sync { Default::default() } - // TODO kb enable this for - // markdown somehow? - // tailwind (needs a css plugin, there are 2 of them) - // svelte (needs a plugin) fn enabled_formatters(&self) -> Vec { Vec::new() } @@ -350,15 +346,15 @@ pub trait LspAdapter: 'static + Send + Sync { #[derive(Clone, Debug, PartialEq, Eq)] pub enum BundledFormatter { Prettier { - parser_name: &'static str, - plugin_names: Vec, + parser_name: Option<&'static str>, + plugin_names: Vec<&'static str>, }, } impl BundledFormatter { pub fn prettier(parser_name: &'static str) -> Self { Self::Prettier { - parser_name, + parser_name: Some(parser_name), plugin_names: Vec::new(), } } diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 129e928da2..cfac4c4757 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -15,6 +15,7 @@ lsp = { path = "../lsp" } node_runtime = { path = "../node_runtime"} util = { path = "../util" } +log.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 77baf6cbf3..3e21cad272 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; -use collections::HashMap; +use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; @@ -29,6 +29,7 @@ pub struct LocateStart { pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js"; pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); const PRETTIER_PACKAGE_NAME: &str = "prettier"; +const TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME: &str = "prettier-plugin-tailwindcss"; impl Prettier { // This was taken from the prettier-vscode extension. @@ -206,17 +207,64 @@ impl Prettier { let path = buffer_file .map(|file| file.full_path(cx)) .map(|path| path.to_path_buf()); - let parser = buffer_language.and_then(|language| { - language - .lsp_adapters() - .iter() - .flat_map(|adapter| adapter.enabled_formatters()) - .find_map(|formatter| match formatter { - BundledFormatter::Prettier { parser_name, .. } => { - Some(parser_name.to_string()) + let parsers_with_plugins = buffer_language + .into_iter() + .flat_map(|language| { + language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .filter_map(|formatter| match formatter { + BundledFormatter::Prettier { + parser_name, + plugin_names, + } => Some((parser_name, plugin_names)), + }) + }) + .fold( + HashMap::default(), + |mut parsers_with_plugins, (parser_name, plugins)| { + match parser_name { + Some(parser_name) => parsers_with_plugins + .entry(parser_name) + .or_insert_with(HashSet::default) + .extend(plugins), + None => parsers_with_plugins.values_mut().for_each(|existing_plugins| { + existing_plugins.extend(plugins.iter()); + }), } - }) - }); + parsers_with_plugins + }, + ); + + let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len()); + if parsers_with_plugins.len() > 1 { + log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); + } + + // TODO kb move the entire prettier server js file into *.mjs one instead? + let plugin_name_into_path = |plugin_name: &str| self.prettier_dir.join("node_modules").join(plugin_name).join("dist").join("index.mjs"); + let (parser, plugins) = match selected_parser_with_plugins { + Some((parser, plugins)) => { + // Tailwind plugin requires being added last + // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins + let mut add_tailwind_back = false; + + let mut plugins = plugins.into_iter().filter(|&&plugin_name| { + if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME { + add_tailwind_back = true; + false + } else { + true + } + }).map(|plugin_name| plugin_name_into_path(plugin_name)).collect::>(); + if add_tailwind_back { + plugins.push(plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME)); + } + (Some(parser.to_string()), plugins) + }, + None => (None, Vec::new()), + }; let prettier_options = if self.default { let language_settings = language_settings(buffer_language, buffer_file, cx); @@ -246,6 +294,7 @@ impl Prettier { text: buffer.text(), options: FormatOptions { parser, + plugins, // TODO kb is not absolute now path, prettier_options, @@ -345,6 +394,7 @@ struct FormatParams { #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FormatOptions { + plugins: Vec, parser: Option, #[serde(rename = "filepath")] path: Option, diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index dcc625b877..780a9c3106 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -153,6 +153,7 @@ async function handleMessage(message, prettier) { const options = { ...(params.options.prettierOptions || prettier.config), parser: params.options.parser, + plugins: params.options.plugins, path: params.options.path }; // TODO kb always resolve prettier config for each file. diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index df504ad506..f66ca039d7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -852,6 +852,7 @@ impl Project { } } let worktree = buffer_file + // TODO kb wrong usage (+ look around for another one like this) .map(|f| f.worktree_id()) .map(WorktreeId::from_usize); language_formatters_to_check.push(( @@ -912,7 +913,7 @@ impl Project { } for (worktree, language, settings) in language_formatters_to_check { - self.maybe_start_default_formatters(worktree, &language, &settings, cx); + self.install_default_formatters(worktree, &language, &settings, cx); } // Start all the newly-enabled language servers. @@ -2669,7 +2670,7 @@ impl Project { .map(|f| f.worktree_id()) .map(WorktreeId::from_usize); let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); - self.maybe_start_default_formatters(worktree, &new_language, &settings, cx); + self.install_default_formatters(worktree, &new_language, &settings, cx); if let Some(file) = File::from_dyn(buffer_file.as_ref()) { let worktree = file.worktree.clone(); @@ -6395,7 +6396,7 @@ impl Project { .prettier_instances .iter() .filter_map(|((worktree_id, prettier_path), prettier_task)| { - if worktree_id == &Some(current_worktree_id) { + if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) { Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) } else { None @@ -6412,7 +6413,7 @@ impl Project { .await .with_context(|| { format!( - "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" + "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update" ) }) .map_err(Arc::new) @@ -8410,6 +8411,7 @@ impl Project { .supplementary_language_servers .insert(new_server_id, (name, Arc::clone(prettier.server()))); // TODO kb could there be a race with multiple default prettier instances added? + // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) cx.emit(Event::LanguageServerAdded(new_server_id)); }); } @@ -8426,7 +8428,7 @@ impl Project { Some(task) } - fn maybe_start_default_formatters( + fn install_default_formatters( &self, worktree: Option, new_language: &Language, @@ -8458,14 +8460,10 @@ impl Project { }; let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); - if let Some(_already_running) = self + let already_running_prettier = self .prettier_instances .get(&(worktree, default_prettier_dir.to_path_buf())) - { - // TODO kb need to compare plugins, install missing and restart prettier - // TODO kb move the entire prettier init logic into prettier.rs - return; - } + .cloned(); let fs = Arc::clone(&self.fs); cx.background() @@ -8478,8 +8476,7 @@ impl Project { let packages_to_versions = future::try_join_all( prettier_plugins .iter() - .map(|s| s.as_str()) - .chain(Some("prettier")) + .chain(Some(&"prettier")) .map(|package_name| async { let returned_package_name = package_name.to_string(); let latest_version = node.npm_package_latest_version(package_name) @@ -8497,6 +8494,13 @@ impl Project { (package.as_str(), version.as_str()) }).collect::>(); node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?; + + if !prettier_plugins.is_empty() { + if let Some(prettier) = already_running_prettier { + prettier.await.map_err(|e| anyhow::anyhow!("Default prettier startup await failure: {e:#}"))?.clear_cache().await.context("clearing default prettier cache after plugins install")?; + } + } + anyhow::Ok(()) }) .detach_and_log_err(cx); diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs index 5e42d80e77..2089fe88b1 100644 --- a/crates/zed/src/languages/svelte.rs +++ b/crates/zed/src/languages/svelte.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -95,6 +95,13 @@ impl LspAdapter for SvelteLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::Prettier { + parser_name: Some("svelte"), + plugin_names: vec!["prettier-plugin-svelte"], + }] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/tailwind.rs b/crates/zed/src/languages/tailwind.rs index cf07fa71c9..8e81f728dc 100644 --- a/crates/zed/src/languages/tailwind.rs +++ b/crates/zed/src/languages/tailwind.rs @@ -6,7 +6,7 @@ use futures::{ FutureExt, StreamExt, }; use gpui::AppContext; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::{json, Value}; @@ -127,6 +127,13 @@ impl LspAdapter for TailwindLspAdapter { .into_iter(), ) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::Prettier { + parser_name: None, + plugin_names: vec!["prettier-plugin-tailwindcss"], + }] + } } async fn get_cached_server_binary( From 658b58378ec8e86e52da83bc089ffe6f49af7574 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 23 Sep 2023 00:04:49 +0300 Subject: [PATCH 52/92] Properly use WorktreeId --- crates/project/src/project.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f66ca039d7..90e7f2446b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -841,22 +841,18 @@ impl Project { for buffer in self.opened_buffers.values() { if let Some(buffer) = buffer.upgrade(cx) { let buffer = buffer.read(cx); - let buffer_file = buffer.file(); + let buffer_file = File::from_dyn(buffer.file()); let buffer_language = buffer.language(); - let settings = language_settings(buffer_language, buffer_file, cx); + let settings = language_settings(buffer_language, buffer.file(), cx); if let Some(language) = buffer_language { if settings.enable_language_server { - if let Some(file) = File::from_dyn(buffer_file) { + if let Some(file) = buffer_file { language_servers_to_start .push((file.worktree.clone(), Arc::clone(language))); } } - let worktree = buffer_file - // TODO kb wrong usage (+ look around for another one like this) - .map(|f| f.worktree_id()) - .map(WorktreeId::from_usize); language_formatters_to_check.push(( - worktree, + buffer_file.map(|f| f.worktree_id(cx)), Arc::clone(language), settings.clone(), )); @@ -2665,14 +2661,12 @@ impl Project { }); let buffer_file = buffer.read(cx).file().cloned(); - let worktree = buffer_file - .as_ref() - .map(|f| f.worktree_id()) - .map(WorktreeId::from_usize); let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); + let buffer_file = File::from_dyn(buffer_file.as_ref()); + let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); self.install_default_formatters(worktree, &new_language, &settings, cx); - if let Some(file) = File::from_dyn(buffer_file.as_ref()) { + if let Some(file) = buffer_file { let worktree = file.worktree.clone(); if let Some(tree) = worktree.read(cx).as_local() { self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx); From a9f80a603ca98470080589e9badf5abe275e9532 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 23 Sep 2023 00:39:46 +0300 Subject: [PATCH 53/92] Resolve prettier config before every formatting --- crates/prettier/src/prettier.rs | 11 +++-------- crates/prettier/src/prettier_server.js | 12 ++++++++---- crates/project/src/project.rs | 11 ++++++++--- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 3e21cad272..c08098ba23 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -199,14 +199,11 @@ impl Prettier { pub async fn format( &self, buffer: &ModelHandle, + buffer_path: Option, cx: &AsyncAppContext, ) -> anyhow::Result { let params = buffer.read_with(cx, |buffer, cx| { - let buffer_file = buffer.file(); let buffer_language = buffer.language(); - let path = buffer_file - .map(|file| file.full_path(cx)) - .map(|path| path.to_path_buf()); let parsers_with_plugins = buffer_language .into_iter() .flat_map(|language| { @@ -242,7 +239,6 @@ impl Prettier { log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); } - // TODO kb move the entire prettier server js file into *.mjs one instead? let plugin_name_into_path = |plugin_name: &str| self.prettier_dir.join("node_modules").join(plugin_name).join("dist").join("index.mjs"); let (parser, plugins) = match selected_parser_with_plugins { Some((parser, plugins)) => { @@ -267,7 +263,7 @@ impl Prettier { }; let prettier_options = if self.default { - let language_settings = language_settings(buffer_language, buffer_file, cx); + let language_settings = language_settings(buffer_language, buffer.file(), cx); let mut options = language_settings.prettier.clone(); if !options.contains_key("tabWidth") { options.insert( @@ -295,8 +291,7 @@ impl Prettier { options: FormatOptions { parser, plugins, - // TODO kb is not absolute now - path, + path: buffer_path, prettier_options, }, } diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 780a9c3106..7e8d1b67cb 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -150,15 +150,19 @@ async function handleMessage(message, prettier) { throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } + let resolvedConfig = {}; + if (params.options.filepath !== undefined) { + resolvedConfig = await prettier.prettier.resolveConfig(params.options.filepath) || {}; + } + const options = { ...(params.options.prettierOptions || prettier.config), + ...resolvedConfig, parser: params.options.parser, plugins: params.options.plugins, - path: params.options.path + path: params.options.filepath }; - // TODO kb always resolve prettier config for each file. - // need to understand if default prettier can be affected by other configs in the project - // (restart default prettiers on config changes too then) + process.stderr.write(`Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${params.options.filepath || ''}' with options: ${JSON.stringify(options)}\n`); const formattedText = await prettier.prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 90e7f2446b..9786afa71b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4130,9 +4130,12 @@ impl Project { .await { Ok(prettier) => { + let buffer_path = buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) + }); format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer, &cx) + .format(buffer, buffer_path, &cx) .await .context("formatting via prettier")?, )); @@ -4168,9 +4171,12 @@ impl Project { .await { Ok(prettier) => { + let buffer_path = buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) + }); format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer, &cx) + .format(buffer, buffer_path, &cx) .await .context("formatting via prettier")?, )); @@ -8309,7 +8315,6 @@ impl Project { let task = cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); - // TODO kb can we have a cache for this instead? let prettier_dir = match cx .background() .spawn(Prettier::locate( From 2d5741aef8eb2f7c0986a2aa39bec73131350ddd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 25 Sep 2023 15:08:39 +0300 Subject: [PATCH 54/92] Better prettier format logging --- crates/prettier/src/prettier.rs | 1 + crates/project/src/project.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index c08098ba23..7871d78d21 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -285,6 +285,7 @@ impl Prettier { } else { None }; + log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); FormatParams { text: buffer.text(), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9786afa71b..53797b5bb9 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8489,6 +8489,7 @@ impl Project { .await .context("fetching latest npm versions")?; + log::info!("Fetching default prettier and plugins: {packages_to_versions:?}"); let borrowed_packages = packages_to_versions.iter().map(|(package, version)| { (package.as_str(), version.as_str()) }).collect::>(); From 6c1c7eaf75e666281af278e6365e71d59638b3a5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 25 Sep 2023 22:43:57 +0300 Subject: [PATCH 55/92] Better detect Svelte plugins --- crates/prettier/src/prettier.rs | 39 +++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 7871d78d21..eb25ec20fc 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -239,8 +239,24 @@ impl Prettier { log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); } - let plugin_name_into_path = |plugin_name: &str| self.prettier_dir.join("node_modules").join(plugin_name).join("dist").join("index.mjs"); - let (parser, plugins) = match selected_parser_with_plugins { + let prettier_node_modules = self.prettier_dir.join("node_modules"); + anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); + let plugin_name_into_path = |plugin_name: &str| { + let prettier_plugin_dir = prettier_node_modules.join(plugin_name); + for possible_plugin_path in [ + prettier_plugin_dir.join("dist").join("index.mjs"), + prettier_plugin_dir.join("index.mjs"), + prettier_plugin_dir.join("plugin.js"), + prettier_plugin_dir.join("index.js"), + prettier_plugin_dir, + ] { + if possible_plugin_path.is_file() { + return Some(possible_plugin_path); + } + } + None + }; + let (parser, located_plugins) = match selected_parser_with_plugins { Some((parser, plugins)) => { // Tailwind plugin requires being added last // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins @@ -253,9 +269,9 @@ impl Prettier { } else { true } - }).map(|plugin_name| plugin_name_into_path(plugin_name)).collect::>(); + }).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::>(); if add_tailwind_back { - plugins.push(plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME)); + plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME))); } (Some(parser.to_string()), plugins) }, @@ -285,9 +301,18 @@ impl Prettier { } else { None }; + + let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| { + match located_plugin_path { + Some(path) => Some(path), + None => { + log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}"); + None}, + } + }).collect(); log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); - FormatParams { + anyhow::Ok(FormatParams { text: buffer.text(), options: FormatOptions { parser, @@ -295,8 +320,8 @@ impl Prettier { path: buffer_path, prettier_options, }, - } - }); + }) + }).context("prettier params calculation")?; let response = self .server .request::(params) From faf1d38a6debe283ccd4245d03205c285c5e8676 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 26 Sep 2023 01:53:28 +0300 Subject: [PATCH 56/92] Draft local and remote prettier separation --- Cargo.lock | 1 + crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 58 +++- crates/prettier/src/prettier_server.js | 3 - crates/project/src/project.rs | 368 +++++++++++++++++-------- crates/rpc/proto/zed.proto | 29 +- crates/rpc/src/proto.rs | 12 +- 7 files changed, 332 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0175755f96..fd10a3c3b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5523,6 +5523,7 @@ name = "prettier" version = "0.1.0" dependencies = [ "anyhow", + "client", "collections", "fs", "futures 0.3.28", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index cfac4c4757..586341e66c 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" path = "src/prettier.rs" [dependencies] +client = { path = "../client" } collections = { path = "../collections"} language = { path = "../language" } gpui = { path = "../gpui" } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index eb25ec20fc..147262baed 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; +use client::Client; use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; @@ -13,13 +14,24 @@ use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; -pub struct Prettier { +pub enum Prettier { + Local(Local), + Remote(Remote), +} + +pub struct Local { worktree_id: Option, default: bool, prettier_dir: PathBuf, server: Arc, } +pub struct Remote { + worktree_id: Option, + prettier_dir: PathBuf, + client: Arc, +} + #[derive(Debug)] pub struct LocateStart { pub worktree_root_path: Arc, @@ -48,6 +60,14 @@ impl Prettier { ".editorconfig", ]; + pub fn remote(worktree_id: Option, prettier_dir: PathBuf, client: Arc) -> Self { + Self::Remote(Remote { + worktree_id, + prettier_dir, + client, + }) + } + pub async fn locate( starting_path: Option, fs: Arc, @@ -188,12 +208,12 @@ impl Prettier { .spawn(server.initialize(None)) .await .context("prettier server initialization")?; - Ok(Self { + Ok(Self::Local(Local { worktree_id, server, default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), prettier_dir, - }) + })) } pub async fn format( @@ -239,7 +259,7 @@ impl Prettier { log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); } - let prettier_node_modules = self.prettier_dir.join("node_modules"); + let prettier_node_modules = self.prettier_dir().join("node_modules"); anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); let plugin_name_into_path = |plugin_name: &str| { let prettier_plugin_dir = prettier_node_modules.join(plugin_name); @@ -278,7 +298,7 @@ impl Prettier { None => (None, Vec::new()), }; - let prettier_options = if self.default { + let prettier_options = if self.is_default() { let language_settings = language_settings(buffer_language, buffer.file(), cx); let mut options = language_settings.prettier.clone(); if !options.contains_key("tabWidth") { @@ -323,7 +343,8 @@ impl Prettier { }) }).context("prettier params calculation")?; let response = self - .server + .server() + .expect("TODO kb split into local and remote") .request::(params) .await .context("prettier format request")?; @@ -332,26 +353,39 @@ impl Prettier { } pub async fn clear_cache(&self) -> anyhow::Result<()> { - self.server + self.server() + .expect("TODO kb split into local and remote") .request::(()) .await .context("prettier clear cache") } - pub fn server(&self) -> &Arc { - &self.server + pub fn server(&self) -> Option<&Arc> { + match self { + Prettier::Local(local) => Some(&local.server), + Prettier::Remote(_) => None, + } } pub fn is_default(&self) -> bool { - self.default + match self { + Prettier::Local(local) => local.default, + Prettier::Remote(_) => false, + } } pub fn prettier_dir(&self) -> &Path { - &self.prettier_dir + match self { + Prettier::Local(local) => &local.prettier_dir, + Prettier::Remote(remote) => &remote.prettier_dir, + } } pub fn worktree_id(&self) -> Option { - self.worktree_id + match self { + Prettier::Local(local) => local.worktree_id, + Prettier::Remote(remote) => remote.worktree_id, + } } } diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 7e8d1b67cb..db3e26ef6d 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -130,9 +130,6 @@ async function* readStdin() { } } -// TODO kb, more methods? -// shutdown -// error async function handleMessage(message, prettier) { const { method, id, params } = message; if (method === undefined) { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 53797b5bb9..3e85e70728 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -613,6 +613,8 @@ impl Project { client.add_model_request_handler(Self::handle_open_buffer_by_path); client.add_model_request_handler(Self::handle_save_buffer); client.add_model_message_handler(Self::handle_update_diff_base); + client.add_model_request_handler(Self::handle_prettier_instance_for_buffer); + client.add_model_request_handler(Self::handle_invoke_prettier); } pub fn local( @@ -4124,10 +4126,8 @@ impl Project { if let Some(prettier_task) = this .update(&mut cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) - }) { - match prettier_task - .await - .await + }).await { + match prettier_task.await { Ok(prettier) => { let buffer_path = buffer.read_with(&cx, |buffer, cx| { @@ -4165,10 +4165,8 @@ impl Project { if let Some(prettier_task) = this .update(&mut cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) - }) { - match prettier_task - .await - .await + }).await { + match prettier_task.await { Ok(prettier) => { let buffer_path = buffer.read_with(&cx, |buffer, cx| { @@ -8288,143 +8286,269 @@ impl Project { } } + async fn handle_prettier_instance_for_buffer( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> anyhow::Result { + let prettier_instance_for_buffer_task = this.update(&mut cx, |this, cx| { + let buffer = this + .opened_buffers + .get(&envelope.payload.buffer_id) + .and_then(|buffer| buffer.upgrade(cx)) + .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; + anyhow::Ok(this.prettier_instance_for_buffer(&buffer, cx)) + })?; + + let prettier_path = match prettier_instance_for_buffer_task.await { + Some(prettier) => match prettier.await { + Ok(prettier) => Some(prettier.prettier_dir().display().to_string()), + Err(e) => { + anyhow::bail!("Failed to create prettier instance for remote request: {e:#}") + } + }, + None => None, + }; + Ok(proto::PrettierInstanceForBufferResponse { prettier_path }) + } + + async fn handle_invoke_prettier( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> anyhow::Result { + let prettier = this + .read_with(&cx, |this, _| { + this.prettier_instances + .get(&( + envelope.payload.worktree_id.map(WorktreeId::from_proto), + PathBuf::from(&envelope.payload.buffer_path), + )) + .cloned() + }) + .with_context(|| { + format!( + "Missing prettier for worktree {:?} and path {}", + envelope.payload.worktree_id, envelope.payload.buffer_path, + ) + })? + .await; + let prettier = match prettier { + Ok(prettier) => prettier, + Err(e) => anyhow::bail!("Prettier instance failed to start: {e:#}"), + }; + + let buffer = this + .update(&mut cx, |this, cx| { + this.opened_buffers + .get(&envelope.payload.buffer_id) + .and_then(|buffer| buffer.upgrade(cx)) + }) + .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; + + let buffer_path = buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) + }); + + let diff = prettier + .format(&buffer, buffer_path, &cx) + .await + .context("handle buffer formatting")?; + todo!("TODO kb serialize diff") + } + fn prettier_instance_for_buffer( &mut self, buffer: &ModelHandle, cx: &mut ModelContext, - ) -> Option, Arc>>>>> { + ) -> Task, Arc>>>>> { let buffer = buffer.read(cx); let buffer_file = buffer.file(); - let buffer_language = buffer.language()?; + let Some(buffer_language) = buffer.language() else { + return Task::ready(None); + }; if !buffer_language .lsp_adapters() .iter() .flat_map(|adapter| adapter.enabled_formatters()) .any(|formatter| matches!(formatter, BundledFormatter::Prettier { .. })) { - return None; + return Task::ready(None); } - let node = Arc::clone(self.node.as_ref()?); let buffer_file = File::from_dyn(buffer_file); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file .as_ref() - .map(|file| file.worktree.read(cx).abs_path()); + .and_then(|file| Some(file.worktree.read(cx).abs_path())); let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); - - let task = cx.spawn(|this, mut cx| async move { - let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); - let prettier_dir = match cx - .background() - .spawn(Prettier::locate( - worktree_path - .zip(buffer_path) - .map(|(worktree_root_path, starting_path)| LocateStart { - worktree_root_path, - starting_path, - }), - fs, - )) - .await - { - Ok(path) => path, - Err(e) => { - return Task::Ready(Some(Result::Err(Arc::new( - e.context("determining prettier path for worktree {worktree_path:?}"), - )))) - .shared(); - } + if self.is_local() || worktree_id.is_none() || worktree_path.is_none() { + let Some(node) = self.node.as_ref().map(Arc::clone) else { + return Task::ready(None); }; - - if let Some(existing_prettier) = this.update(&mut cx, |project, _| { - project - .prettier_instances - .get(&(worktree_id, prettier_dir.clone())) - .cloned() - }) { - return existing_prettier; - } - - log::info!("Found prettier at {prettier_dir:?}, starting."); - let task_prettier_dir = prettier_dir.clone(); - let weak_project = this.downgrade(); - let new_server_id = - this.update(&mut cx, |this, _| this.languages.next_language_server_id()); - let new_prettier_task = cx - .spawn(|mut cx| async move { - let prettier = Prettier::start( - worktree_id.map(|id| id.to_usize()), - new_server_id, - task_prettier_dir, - node, - cx.clone(), - ) + let task = cx.spawn(|this, mut cx| async move { + let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); + let prettier_dir = match cx + .background() + .spawn(Prettier::locate( + worktree_path.zip(buffer_path).map( + |(worktree_root_path, starting_path)| LocateStart { + worktree_root_path, + starting_path, + }, + ), + fs, + )) .await - .context("prettier start") - .map_err(Arc::new)?; - log::info!("Had started prettier in {:?}", prettier.prettier_dir()); - - if let Some(project) = weak_project.upgrade(&mut cx) { - project.update(&mut cx, |project, cx| { - let name = if prettier.is_default() { - LanguageServerName(Arc::from("prettier (default)")) - } else { - let prettier_dir = prettier.prettier_dir(); - let worktree_path = prettier - .worktree_id() - .map(WorktreeId::from_usize) - .and_then(|id| project.worktree_for_id(id, cx)) - .map(|worktree| worktree.read(cx).abs_path()); - match worktree_path { - Some(worktree_path) => { - if worktree_path.as_ref() == prettier_dir { - LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier_dir - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or_default() - ))) - } else { - let dir_to_display = match prettier_dir - .strip_prefix(&worktree_path) - .ok() - { - Some(relative_path) => relative_path, - None => prettier_dir, - }; - LanguageServerName(Arc::from(format!( - "prettier ({})", - dir_to_display.display(), - ))) - } - } - None => LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier_dir.display(), - ))), - } - }; - project - .supplementary_language_servers - .insert(new_server_id, (name, Arc::clone(prettier.server()))); - // TODO kb could there be a race with multiple default prettier instances added? - // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) - cx.emit(Event::LanguageServerAdded(new_server_id)); - }); + { + Ok(path) => path, + Err(e) => { + return Some( + Task::ready(Err(Arc::new(e.context( + "determining prettier path for worktree {worktree_path:?}", + )))) + .shared(), + ); } - anyhow::Ok(Arc::new(prettier)).map_err(Arc::new) - }) - .shared(); - this.update(&mut cx, |project, _| { - project - .prettier_instances - .insert((worktree_id, prettier_dir), new_prettier_task.clone()); + }; + + if let Some(existing_prettier) = this.update(&mut cx, |project, _| { + project + .prettier_instances + .get(&(worktree_id, prettier_dir.clone())) + .cloned() + }) { + return Some(existing_prettier); + } + + log::info!("Found prettier at {prettier_dir:?}, starting."); + let task_prettier_dir = prettier_dir.clone(); + let weak_project = this.downgrade(); + let new_server_id = + this.update(&mut cx, |this, _| this.languages.next_language_server_id()); + let new_prettier_task = cx + .spawn(|mut cx| async move { + let prettier = Prettier::start( + worktree_id.map(|id| id.to_usize()), + new_server_id, + task_prettier_dir, + node, + cx.clone(), + ) + .await + .context("prettier start") + .map_err(Arc::new)?; + log::info!("Had started prettier in {:?}", prettier.prettier_dir()); + + if let Some((project, prettier_server)) = + weak_project.upgrade(&mut cx).zip(prettier.server()) + { + project.update(&mut cx, |project, cx| { + let name = if prettier.is_default() { + LanguageServerName(Arc::from("prettier (default)")) + } else { + let prettier_dir = prettier.prettier_dir(); + let worktree_path = prettier + .worktree_id() + .map(WorktreeId::from_usize) + .and_then(|id| project.worktree_for_id(id, cx)) + .map(|worktree| worktree.read(cx).abs_path()); + match worktree_path { + Some(worktree_path) => { + if worktree_path.as_ref() == prettier_dir { + LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default() + ))) + } else { + let dir_to_display = match prettier_dir + .strip_prefix(&worktree_path) + .ok() + { + Some(relative_path) => relative_path, + None => prettier_dir, + }; + LanguageServerName(Arc::from(format!( + "prettier ({})", + dir_to_display.display(), + ))) + } + } + None => LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir.display(), + ))), + } + }; + + project + .supplementary_language_servers + .insert(new_server_id, (name, Arc::clone(prettier_server))); + // TODO kb could there be a race with multiple default prettier instances added? + // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) + cx.emit(Event::LanguageServerAdded(new_server_id)); + }); + } + Ok(Arc::new(prettier)).map_err(Arc::new) + }) + .shared(); + this.update(&mut cx, |project, _| { + project + .prettier_instances + .insert((worktree_id, prettier_dir), new_prettier_task.clone()); + }); + Some(new_prettier_task) }); - new_prettier_task - }); - Some(task) + task + } else if let Some(project_id) = self.remote_id() { + let client = self.client.clone(); + let request = proto::PrettierInstanceForBuffer { + project_id, + buffer_id: buffer.remote_id(), + }; + let task = cx.spawn(|this, mut cx| async move { + match client.request(request).await { + Ok(response) => { + response + .prettier_path + .map(PathBuf::from) + .map(|prettier_path| { + let prettier_task = Task::ready( + Ok(Arc::new(Prettier::remote( + worktree_id.map(|id| id.to_usize()), + prettier_path.clone(), + client, + ))) + .map_err(Arc::new), + ) + .shared(); + this.update(&mut cx, |project, _| { + project.prettier_instances.insert( + (worktree_id, prettier_path), + prettier_task.clone(), + ); + }); + prettier_task + }) + } + Err(e) => { + log::error!("Prettier init remote request failed: {e:#}"); + None + } + } + }); + + task + } else { + Task::ready(Some( + Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(), + )) + } } fn install_default_formatters( diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 3501e70e6a..64fd8c620d 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -170,7 +170,12 @@ message Envelope { LinkChannel link_channel = 140; UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; // current max: 144 + MoveChannel move_channel = 142; + + PrettierInstanceForBuffer prettier_instance_for_buffer = 145; + PrettierInstanceForBufferResponse prettier_instance_for_buffer_response = 146; + InvokePrettierForBuffer invoke_prettier_for_buffer = 147; + InvokePrettierForBufferResponse invoke_prettier_for_buffer_response = 148; // Current max: 148 } } @@ -1557,3 +1562,25 @@ message UpdateDiffBase { uint64 buffer_id = 2; optional string diff_base = 3; } + +message PrettierInstanceForBuffer { + uint64 project_id = 1; + uint64 buffer_id = 2; +} + +message PrettierInstanceForBufferResponse { + optional string prettier_path = 1; +} + +message InvokePrettierForBuffer { + uint64 project_id = 1; + string buffer_path = 2; + uint64 buffer_id = 3; + optional uint64 worktree_id = 4; + string method = 5; + optional string command_parameters = 6; +} + +message InvokePrettierForBufferResponse { + optional string diff = 1; +} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index f0d7937f6f..b51f1d8ae9 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -273,6 +273,10 @@ messages!( (UpdateChannelBufferCollaborators, Foreground), (AckBufferOperation, Background), (AckChannelMessage, Background), + (PrettierInstanceForBuffer, Background), + (InvokePrettierForBuffer, Background), + (PrettierInstanceForBufferResponse, Background), + (InvokePrettierForBufferResponse, Background), ); request_messages!( @@ -349,7 +353,9 @@ request_messages!( (UpdateProject, Ack), (UpdateWorktree, Ack), (JoinChannelBuffer, JoinChannelBufferResponse), - (LeaveChannelBuffer, Ack) + (LeaveChannelBuffer, Ack), + (PrettierInstanceForBuffer, PrettierInstanceForBufferResponse), + (InvokePrettierForBuffer, InvokePrettierForBufferResponse), ); entity_messages!( @@ -400,7 +406,9 @@ entity_messages!( UpdateProjectCollaborator, UpdateWorktree, UpdateWorktreeSettings, - UpdateDiffBase + UpdateDiffBase, + PrettierInstanceForBuffer, + InvokePrettierForBuffer, ); entity_messages!( From 2ec2036c2f739894cd80c2f26f7f6471a93906ea Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 26 Sep 2023 18:32:55 +0300 Subject: [PATCH 57/92] Invoke remote Prettier commands --- crates/prettier/src/prettier.rs | 289 ++++++++++++++++++-------------- crates/project/src/project.rs | 9 +- crates/rpc/proto/zed.proto | 9 +- 3 files changed, 172 insertions(+), 135 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 147262baed..2df96224b6 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -9,6 +9,7 @@ use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; use language::{Buffer, BundledFormatter, Diff}; +use lsp::request::Request; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; @@ -216,148 +217,176 @@ impl Prettier { })) } + pub async fn invoke( + &self, + buffer: &ModelHandle, + buffer_path: Option, + method: &str, + cx: &AsyncAppContext, + ) -> anyhow::Result> { + match method { + Format::METHOD => self + .format(buffer, buffer_path, cx) + .await + .context("invoke method") + .map(Some), + ClearCache::METHOD => { + self.clear_cache().await.context("invoke method")?; + Ok(None) + } + unknown => anyhow::bail!("Unknown method {unknown}"), + } + } + pub async fn format( &self, buffer: &ModelHandle, buffer_path: Option, cx: &AsyncAppContext, ) -> anyhow::Result { - let params = buffer.read_with(cx, |buffer, cx| { - let buffer_language = buffer.language(); - let parsers_with_plugins = buffer_language - .into_iter() - .flat_map(|language| { - language - .lsp_adapters() - .iter() - .flat_map(|adapter| adapter.enabled_formatters()) - .filter_map(|formatter| match formatter { - BundledFormatter::Prettier { - parser_name, - plugin_names, - } => Some((parser_name, plugin_names)), + match self { + Self::Local(local) => { + let params = buffer.read_with(cx, |buffer, cx| { + let buffer_language = buffer.language(); + let parsers_with_plugins = buffer_language + .into_iter() + .flat_map(|language| { + language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .filter_map(|formatter| match formatter { + BundledFormatter::Prettier { + parser_name, + plugin_names, + } => Some((parser_name, plugin_names)), + }) }) - }) - .fold( - HashMap::default(), - |mut parsers_with_plugins, (parser_name, plugins)| { - match parser_name { - Some(parser_name) => parsers_with_plugins - .entry(parser_name) - .or_insert_with(HashSet::default) - .extend(plugins), - None => parsers_with_plugins.values_mut().for_each(|existing_plugins| { - existing_plugins.extend(plugins.iter()); - }), - } - parsers_with_plugins - }, - ); + .fold( + HashMap::default(), + |mut parsers_with_plugins, (parser_name, plugins)| { + match parser_name { + Some(parser_name) => parsers_with_plugins + .entry(parser_name) + .or_insert_with(HashSet::default) + .extend(plugins), + None => parsers_with_plugins.values_mut().for_each(|existing_plugins| { + existing_plugins.extend(plugins.iter()); + }), + } + parsers_with_plugins + }, + ); - let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len()); - if parsers_with_plugins.len() > 1 { - log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); + let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len()); + if parsers_with_plugins.len() > 1 { + log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); + } + + let prettier_node_modules = self.prettier_dir().join("node_modules"); + anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); + let plugin_name_into_path = |plugin_name: &str| { + let prettier_plugin_dir = prettier_node_modules.join(plugin_name); + for possible_plugin_path in [ + prettier_plugin_dir.join("dist").join("index.mjs"), + prettier_plugin_dir.join("index.mjs"), + prettier_plugin_dir.join("plugin.js"), + prettier_plugin_dir.join("index.js"), + prettier_plugin_dir, + ] { + if possible_plugin_path.is_file() { + return Some(possible_plugin_path); + } + } + None + }; + let (parser, located_plugins) = match selected_parser_with_plugins { + Some((parser, plugins)) => { + // Tailwind plugin requires being added last + // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins + let mut add_tailwind_back = false; + + let mut plugins = plugins.into_iter().filter(|&&plugin_name| { + if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME { + add_tailwind_back = true; + false + } else { + true + } + }).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::>(); + if add_tailwind_back { + plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME))); + } + (Some(parser.to_string()), plugins) + }, + None => (None, Vec::new()), + }; + + let prettier_options = if self.is_default() { + let language_settings = language_settings(buffer_language, buffer.file(), cx); + let mut options = language_settings.prettier.clone(); + if !options.contains_key("tabWidth") { + options.insert( + "tabWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.tab_size.get(), + )), + ); + } + if !options.contains_key("printWidth") { + options.insert( + "printWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.preferred_line_length, + )), + ); + } + Some(options) + } else { + None + }; + + let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| { + match located_plugin_path { + Some(path) => Some(path), + None => { + log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}"); + None}, + } + }).collect(); + log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); + + anyhow::Ok(FormatParams { + text: buffer.text(), + options: FormatOptions { + parser, + plugins, + path: buffer_path, + prettier_options, + }, + }) + }).context("prettier params calculation")?; + let response = local + .server + .request::(params) + .await + .context("prettier format request")?; + let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); + Ok(diff_task.await) } - - let prettier_node_modules = self.prettier_dir().join("node_modules"); - anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); - let plugin_name_into_path = |plugin_name: &str| { - let prettier_plugin_dir = prettier_node_modules.join(plugin_name); - for possible_plugin_path in [ - prettier_plugin_dir.join("dist").join("index.mjs"), - prettier_plugin_dir.join("index.mjs"), - prettier_plugin_dir.join("plugin.js"), - prettier_plugin_dir.join("index.js"), - prettier_plugin_dir, - ] { - if possible_plugin_path.is_file() { - return Some(possible_plugin_path); - } - } - None - }; - let (parser, located_plugins) = match selected_parser_with_plugins { - Some((parser, plugins)) => { - // Tailwind plugin requires being added last - // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins - let mut add_tailwind_back = false; - - let mut plugins = plugins.into_iter().filter(|&&plugin_name| { - if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME { - add_tailwind_back = true; - false - } else { - true - } - }).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::>(); - if add_tailwind_back { - plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME))); - } - (Some(parser.to_string()), plugins) - }, - None => (None, Vec::new()), - }; - - let prettier_options = if self.is_default() { - let language_settings = language_settings(buffer_language, buffer.file(), cx); - let mut options = language_settings.prettier.clone(); - if !options.contains_key("tabWidth") { - options.insert( - "tabWidth".to_string(), - serde_json::Value::Number(serde_json::Number::from( - language_settings.tab_size.get(), - )), - ); - } - if !options.contains_key("printWidth") { - options.insert( - "printWidth".to_string(), - serde_json::Value::Number(serde_json::Number::from( - language_settings.preferred_line_length, - )), - ); - } - Some(options) - } else { - None - }; - - let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| { - match located_plugin_path { - Some(path) => Some(path), - None => { - log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}"); - None}, - } - }).collect(); - log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); - - anyhow::Ok(FormatParams { - text: buffer.text(), - options: FormatOptions { - parser, - plugins, - path: buffer_path, - prettier_options, - }, - }) - }).context("prettier params calculation")?; - let response = self - .server() - .expect("TODO kb split into local and remote") - .request::(params) - .await - .context("prettier format request")?; - let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); - Ok(diff_task.await) + Self::Remote(remote) => todo!("TODO kb"), + } } pub async fn clear_cache(&self) -> anyhow::Result<()> { - self.server() - .expect("TODO kb split into local and remote") - .request::(()) - .await - .context("prettier clear cache") + match self { + Self::Local(local) => local + .server + .request::(()) + .await + .context("prettier clear cache"), + Self::Remote(remote) => todo!("TODO kb"), + } } pub fn server(&self) -> Option<&Arc> { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3e85e70728..f3a92ad00f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8353,10 +8353,13 @@ impl Project { }); let diff = prettier - .format(&buffer, buffer_path, &cx) + .invoke(&buffer, buffer_path, &envelope.payload.method, &cx) .await - .context("handle buffer formatting")?; - todo!("TODO kb serialize diff") + .with_context(|| format!("prettier invoke method {}", &envelope.payload.method))?; + + Ok(proto::InvokePrettierForBufferResponse { + diff: todo!("TODO kb serialize diff"), + }) } fn prettier_instance_for_buffer( diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 64fd8c620d..0622fcef90 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1578,9 +1578,14 @@ message InvokePrettierForBuffer { uint64 buffer_id = 3; optional uint64 worktree_id = 4; string method = 5; - optional string command_parameters = 6; } message InvokePrettierForBufferResponse { - optional string diff = 1; + optional Diff diff = 1; +} + +message Diff { + VectorClockEntry version = 1; + string line_ending = 2; + string edits = 3; } From b5705e079fd90276916b7e553ebecfa9ebe81158 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 27 Sep 2023 16:38:42 +0200 Subject: [PATCH 58/92] Draft remote prettier formatting --- crates/language/src/buffer.rs | 4 +-- crates/language/src/proto.rs | 41 ++++++++++++++++++++++- crates/prettier/src/prettier.rs | 58 ++++++++++++++++++++++++++++----- crates/project/src/project.rs | 35 +++++++++++--------- crates/rpc/proto/zed.proto | 15 ++++++--- 5 files changed, 121 insertions(+), 32 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 207c41e7cd..eccfb0c059 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -317,8 +317,8 @@ pub struct Chunk<'a> { pub struct Diff { pub(crate) base_version: clock::Global, - line_ending: LineEnding, - edits: Vec<(Range, Arc)>, + pub(crate) line_ending: LineEnding, + pub(crate) edits: Vec<(Range, Arc)>, } #[derive(Clone, Copy)] diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index c4abe39d47..5da1d5713f 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -1,6 +1,6 @@ use crate::{ diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, - Language, + Diff, Language, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -587,3 +587,42 @@ pub fn serialize_version(version: &clock::Global) -> Vec proto::Diff { + proto::Diff { + version: serialize_version(&diff.base_version), + line_ending: serialize_line_ending(diff.line_ending) as i32, + edits: diff + .edits + .into_iter() + .map(|(range, edit)| proto::DiffEdit { + range: Some(proto::Range { + start: range.start as u64, + end: range.end as u64, + }), + edit: edit.to_string(), + }) + .collect(), + } +} + +pub fn deserialize_diff(diff: proto::Diff) -> Diff { + Diff { + base_version: deserialize_version(&diff.version), + line_ending: deserialize_line_ending( + rpc::proto::LineEnding::from_i32(diff.line_ending) + .unwrap_or_else(|| panic!("invalid line ending {}", diff.line_ending)), + ), + edits: diff + .edits + .into_iter() + .map(|edit| { + let range = edit.range.expect("incorrect edit without a range"); + ( + range.start as usize..range.end as usize, + Arc::from(edit.edit.as_str()), + ) + }) + .collect(), + } +} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 2df96224b6..5647123c75 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,11 +3,12 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; -use client::Client; +use client::{proto, Client}; use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; +use language::proto::deserialize_diff; use language::{Buffer, BundledFormatter, Diff}; use lsp::request::Request; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; @@ -28,6 +29,7 @@ pub struct Local { } pub struct Remote { + project_id: u64, worktree_id: Option, prettier_dir: PathBuf, client: Arc, @@ -61,8 +63,14 @@ impl Prettier { ".editorconfig", ]; - pub fn remote(worktree_id: Option, prettier_dir: PathBuf, client: Arc) -> Self { + pub fn remote( + project_id: u64, + worktree_id: Option, + prettier_dir: PathBuf, + client: Arc, + ) -> Self { Self::Remote(Remote { + project_id, worktree_id, prettier_dir, client, @@ -80,7 +88,7 @@ impl Prettier { .components() .into_iter() .take_while(|path_component| { - path_component.as_os_str().to_str() != Some("node_modules") + path_component.as_os_str().to_string_lossy() != "node_modules" }) .collect::(); @@ -137,7 +145,7 @@ impl Prettier { for path_component in file_to_format.components().into_iter() { current_path = current_path.join(path_component); paths_to_check.push_front(current_path.clone()); - if path_component.as_os_str().to_str() == Some("node_modules") { + if path_component.as_os_str().to_string_lossy() == "node_modules" { break; } } @@ -219,14 +227,18 @@ impl Prettier { pub async fn invoke( &self, - buffer: &ModelHandle, + buffer: Option<&ModelHandle>, buffer_path: Option, method: &str, cx: &AsyncAppContext, ) -> anyhow::Result> { match method { Format::METHOD => self - .format(buffer, buffer_path, cx) + .format( + buffer.expect("missing buffer for format invocation"), + buffer_path, + cx, + ) .await .context("invoke method") .map(Some), @@ -374,7 +386,21 @@ impl Prettier { let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); Ok(diff_task.await) } - Self::Remote(remote) => todo!("TODO kb"), + Self::Remote(remote) => buffer + .read_with(cx, |buffer, _| { + remote.client.request(proto::InvokePrettierForBuffer { + buffer_id: Some(buffer.remote_id()), + worktree_id: self.worktree_id().map(|id| id as u64), + method: Format::METHOD.to_string(), + project_id: remote.project_id, + prettier_path: remote.prettier_dir.to_string_lossy().to_string(), + }) + }) + .await + .context("prettier diff invoke")? + .diff + .map(deserialize_diff) + .context("missing diff after prettier diff invocation"), } } @@ -385,7 +411,23 @@ impl Prettier { .request::(()) .await .context("prettier clear cache"), - Self::Remote(remote) => todo!("TODO kb"), + Self::Remote(remote) => remote + .client + .request(proto::InvokePrettierForBuffer { + buffer_id: None, + worktree_id: self.worktree_id().map(|id| id as u64), + method: ClearCache::METHOD.to_string(), + project_id: remote.project_id, + prettier_path: remote.prettier_dir.to_string_lossy().to_string(), + }) + .await + .map(|response| { + debug_assert!( + response.diff.is_none(), + "Cleare cache invocation returned diff data" + ) + }) + .context("prettier invoke clear cache"), } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f3a92ad00f..9a1d78f083 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -37,7 +37,7 @@ use language::{ point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, - serialize_anchor, serialize_version, split_operations, + serialize_anchor, serialize_diff, serialize_version, split_operations, }, range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, @@ -6382,7 +6382,7 @@ impl Project { .filter(|(path, _, _)| { !path .components() - .any(|component| component.as_os_str().to_str() == Some("node_modules")) + .any(|component| component.as_os_str().to_string_lossy() == "node_modules") }) .find(|(path, _, _)| prettier_config_files.contains(path.as_ref())); let current_worktree_id = worktree.read(cx).id(); @@ -8324,14 +8324,14 @@ impl Project { this.prettier_instances .get(&( envelope.payload.worktree_id.map(WorktreeId::from_proto), - PathBuf::from(&envelope.payload.buffer_path), + PathBuf::from(&envelope.payload.prettier_path), )) .cloned() }) .with_context(|| { format!( - "Missing prettier for worktree {:?} and path {}", - envelope.payload.worktree_id, envelope.payload.buffer_path, + "Missing prettier for worktree {:?} and path {:?}", + envelope.payload.worktree_id, envelope.payload.prettier_path, ) })? .await; @@ -8340,25 +8340,27 @@ impl Project { Err(e) => anyhow::bail!("Prettier instance failed to start: {e:#}"), }; - let buffer = this - .update(&mut cx, |this, cx| { - this.opened_buffers - .get(&envelope.payload.buffer_id) - .and_then(|buffer| buffer.upgrade(cx)) - }) - .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; + let buffer = this.update(&mut cx, |this, cx| { + envelope + .payload + .buffer_id + .and_then(|id| this.opened_buffers.get(&id)) + .and_then(|buffer| buffer.upgrade(cx)) + }); - let buffer_path = buffer.read_with(&cx, |buffer, cx| { - File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) + let buffer_path = buffer.as_ref().and_then(|buffer| { + buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) + }) }); let diff = prettier - .invoke(&buffer, buffer_path, &envelope.payload.method, &cx) + .invoke(buffer.as_ref(), buffer_path, &envelope.payload.method, &cx) .await .with_context(|| format!("prettier invoke method {}", &envelope.payload.method))?; Ok(proto::InvokePrettierForBufferResponse { - diff: todo!("TODO kb serialize diff"), + diff: diff.map(serialize_diff), }) } @@ -8523,6 +8525,7 @@ impl Project { .map(|prettier_path| { let prettier_task = Task::ready( Ok(Arc::new(Prettier::remote( + project_id, worktree_id.map(|id| id.to_usize()), prettier_path.clone(), client, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 0622fcef90..cbd334e334 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1574,8 +1574,8 @@ message PrettierInstanceForBufferResponse { message InvokePrettierForBuffer { uint64 project_id = 1; - string buffer_path = 2; - uint64 buffer_id = 3; + optional uint64 buffer_id = 3; + string prettier_path = 2; optional uint64 worktree_id = 4; string method = 5; } @@ -1585,7 +1585,12 @@ message InvokePrettierForBufferResponse { } message Diff { - VectorClockEntry version = 1; - string line_ending = 2; - string edits = 3; + repeated VectorClockEntry version = 1; + LineEnding line_ending = 2; + repeated DiffEdit edits = 3; +} + +message DiffEdit { + Range range = 1; + string edit = 2; } From 9bf22c56cdb1adad44ba82b7fcd4b9b04c7451e2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 9 Oct 2023 15:44:48 +0300 Subject: [PATCH 59/92] Rebase fixes --- crates/collab/src/tests/test_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index ccd48a0a1b..7397489b34 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -569,9 +569,9 @@ impl TestClient { cx.update(|cx| { Project::local( self.client().clone(), + self.app_state.node_runtime.clone(), self.app_state.user_store.clone(), self.app_state.languages.clone(), - self.app_state.node_runtime.clone(), self.app_state.fs.clone(), cx, ) From 986a516bf1237b343eb883c860019a692fb9a042 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 10 Oct 2023 17:16:43 +0300 Subject: [PATCH 60/92] Small style fixes --- crates/language/src/language.rs | 6 ++++++ crates/prettier/src/prettier.rs | 1 - crates/project/src/project.rs | 1 + crates/rpc/proto/zed.proto | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c565d09c98..73906250f3 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -346,6 +346,12 @@ pub trait LspAdapter: 'static + Send + Sync { #[derive(Clone, Debug, PartialEq, Eq)] pub enum BundledFormatter { Prettier { + // See https://prettier.io/docs/en/options.html#parser for a list of valid values. + // Usually, every language has a single parser (standard or plugin-provided), hence `Some("parser_name")` can be used. + // There can not be multiple parsers for a single language, in case of a conflict, we would attempt to select the one with most plugins. + // + // But exceptions like Tailwind CSS exist, which uses standard parsers for CSS/JS/HTML/etc. but require an extra plugin to be installed. + // For those cases, `None` will install the plugin but apply other, regular parser defined for the language, and this would not be a conflict. parser_name: Option<&'static str>, plugin_names: Vec<&'static str>, }, diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 5647123c75..07a7cbd73a 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -47,7 +47,6 @@ const PRETTIER_PACKAGE_NAME: &str = "prettier"; const TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME: &str = "prettier-plugin-tailwindcss"; impl Prettier { - // This was taken from the prettier-vscode extension. pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ ".prettierrc", ".prettierrc.json", diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9a1d78f083..9dc3ff8758 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8496,6 +8496,7 @@ impl Project { .insert(new_server_id, (name, Arc::clone(prettier_server))); // TODO kb could there be a race with multiple default prettier instances added? // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) + // also, is there a way to speed up initial prettier startup? now it takes a 1s or so on the first formatting attempt. cx.emit(Event::LanguageServerAdded(new_server_id)); }); } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index cbd334e334..aae207f41c 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1574,8 +1574,8 @@ message PrettierInstanceForBufferResponse { message InvokePrettierForBuffer { uint64 project_id = 1; - optional uint64 buffer_id = 3; string prettier_path = 2; + optional uint64 buffer_id = 3; optional uint64 worktree_id = 4; string method = 5; } From 4a88a9e253d3cafc6dd54fc9c873a9098c2ec730 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 11 Oct 2023 14:46:59 +0300 Subject: [PATCH 61/92] Initialize prettier right after the buffer gets it language --- crates/project/src/project.rs | 41 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9dc3ff8758..ebc2aaaf60 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -911,7 +911,8 @@ impl Project { } for (worktree, language, settings) in language_formatters_to_check { - self.install_default_formatters(worktree, &language, &settings, cx); + self.install_default_formatters(worktree, &language, &settings, cx) + .detach_and_log_err(cx); } // Start all the newly-enabled language servers. @@ -2666,7 +2667,20 @@ impl Project { let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); let buffer_file = File::from_dyn(buffer_file.as_ref()); let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); - self.install_default_formatters(worktree, &new_language, &settings, cx); + + let task_buffer = buffer.clone(); + let prettier_installation_task = + self.install_default_formatters(worktree, &new_language, &settings, cx); + cx.spawn(|project, mut cx| async move { + prettier_installation_task.await?; + let _ = project + .update(&mut cx, |project, cx| { + project.prettier_instance_for_buffer(&task_buffer, cx) + }) + .await; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); if let Some(file) = buffer_file { let worktree = file.worktree.clone(); @@ -8393,7 +8407,7 @@ impl Project { let Some(node) = self.node.as_ref().map(Arc::clone) else { return Task::ready(None); }; - let task = cx.spawn(|this, mut cx| async move { + cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); let prettier_dir = match cx .background() @@ -8494,9 +8508,6 @@ impl Project { project .supplementary_language_servers .insert(new_server_id, (name, Arc::clone(prettier_server))); - // TODO kb could there be a race with multiple default prettier instances added? - // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) - // also, is there a way to speed up initial prettier startup? now it takes a 1s or so on the first formatting attempt. cx.emit(Event::LanguageServerAdded(new_server_id)); }); } @@ -8509,15 +8520,14 @@ impl Project { .insert((worktree_id, prettier_dir), new_prettier_task.clone()); }); Some(new_prettier_task) - }); - task + }) } else if let Some(project_id) = self.remote_id() { let client = self.client.clone(); let request = proto::PrettierInstanceForBuffer { project_id, buffer_id: buffer.remote_id(), }; - let task = cx.spawn(|this, mut cx| async move { + cx.spawn(|this, mut cx| async move { match client.request(request).await { Ok(response) => { response @@ -8548,9 +8558,7 @@ impl Project { None } } - }); - - task + }) } else { Task::ready(Some( Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(), @@ -8564,13 +8572,13 @@ impl Project { new_language: &Language, language_settings: &LanguageSettings, cx: &mut ModelContext, - ) { + ) -> Task> { match &language_settings.formatter { Formatter::Prettier { .. } | Formatter::Auto => {} - Formatter::LanguageServer | Formatter::External { .. } => return, + Formatter::LanguageServer | Formatter::External { .. } => return Task::ready(Ok(())), }; let Some(node) = self.node.as_ref().cloned() else { - return; + return Task::ready(Ok(())); }; let mut prettier_plugins = None; @@ -8586,7 +8594,7 @@ impl Project { } } let Some(prettier_plugins) = prettier_plugins else { - return; + return Task::ready(Ok(())); }; let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); @@ -8634,7 +8642,6 @@ impl Project { anyhow::Ok(()) }) - .detach_and_log_err(cx); } } From 7c867b6e5456eac337d7008fc7ed89ba3b9d669a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Oct 2023 09:23:06 -0600 Subject: [PATCH 62/92] 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 a1d0b305c8..e4eb23b217 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 e50f4c0ee5f3e32f166e6cb16e02042413088074 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 11 Oct 2023 19:08:35 +0300 Subject: [PATCH 63/92] Add prettier tests infrastructure --- crates/editor/src/editor_tests.rs | 66 ++++++++++++++++++++++++- crates/language/src/language.rs | 6 +++ crates/node_runtime/src/node_runtime.rs | 25 +++++++--- crates/prettier/Cargo.toml | 3 ++ crates/prettier/src/prettier.rs | 54 ++++++++++++++++---- crates/project/Cargo.toml | 2 + 6 files changed, 139 insertions(+), 17 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index dc723c7012..ddae186679 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -19,8 +19,8 @@ use gpui::{ use indoc::indoc; use language::{ language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent}, - BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry, - Override, Point, + BracketPairConfig, BundledFormatter, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, + LanguageRegistry, Override, Point, }; use parking_lot::Mutex; use project::project_settings::{LspSettings, ProjectSettings}; @@ -7815,6 +7815,68 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: }); } +#[gpui::test] +async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { + init_test(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::Prettier) + }); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + + let _ = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + enabled_formatters: vec![BundledFormatter::Prettier { + parser_name: Some("test_parser"), + plugin_names: vec!["test_plugin"], + }], + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.background()); + fs.insert_file("/file.rs", Default::default()).await; + + // TODO kb have to specify some test node runtime + let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) + .await + .unwrap(); + + let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); + editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); + + let format = editor.update(cx, |editor, cx| { + editor.perform_format(project.clone(), FormatTrigger::Manual, cx) + }); + format.await.unwrap(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + "one, two\nthree\n" + ); + + editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); + let format = editor.update(cx, |editor, cx| { + editor.perform_format(project, FormatTrigger::Manual, cx) + }); + cx.foreground().advance_clock(super::FORMAT_TIMEOUT); + cx.foreground().start_waiting(); + format.await.unwrap(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + "one\ntwo\nthree\n" + ); +} + fn empty_range(row: usize, column: usize) -> Range { let point = DisplayPoint::new(row as u32, column as u32); point..point diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 73906250f3..bd389652a0 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -498,6 +498,7 @@ pub struct FakeLspAdapter { pub initializer: Option>, pub disk_based_diagnostics_progress_token: Option, pub disk_based_diagnostics_sources: Vec, + pub enabled_formatters: Vec, } #[derive(Clone, Debug, Default)] @@ -1760,6 +1761,7 @@ impl Default for FakeLspAdapter { disk_based_diagnostics_progress_token: None, initialization_options: None, disk_based_diagnostics_sources: Vec::new(), + enabled_formatters: Vec::new(), } } } @@ -1816,6 +1818,10 @@ impl LspAdapter for Arc { async fn initialization_options(&self) -> Option { self.initialization_options.clone() } + + fn enabled_formatters(&self) -> Vec { + self.enabled_formatters.clone() + } } fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option)]) { diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 820a8b6f81..8bfb26cdad 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -230,19 +230,32 @@ impl FakeNodeRuntime { #[async_trait::async_trait] impl NodeRuntime for FakeNodeRuntime { - async fn binary_path(&self) -> Result { - unreachable!() + async fn binary_path(&self) -> anyhow::Result { + // TODO kb move away into a separate type + a Project's setter (for test code) + Ok(PathBuf::from("prettier_fake_node")) } async fn run_npm_subcommand(&self, _: Option<&Path>, _: &str, _: &[&str]) -> Result { unreachable!() } - async fn npm_package_latest_version(&self, _: &str) -> Result { - unreachable!() + async fn npm_package_latest_version(&self, name: &str) -> anyhow::Result { + if name == "prettier" { + Ok("0.0.1".to_string()) + } else { + unreachable!() + } } - async fn npm_install_packages(&self, _: &Path, _: &[(&str, &str)]) -> Result<()> { - unreachable!() + async fn npm_install_packages( + &self, + _: &Path, + packages: &[(&str, &str)], + ) -> anyhow::Result<()> { + if packages == [("prettier", "0.0.1")] { + Ok(()) + } else { + unreachable!() + } } } diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 586341e66c..764bf0f07f 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" [lib] path = "src/prettier.rs" +[features] +test-support = [] + [dependencies] client = { path = "../client" } collections = { path = "../collections"} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 07a7cbd73a..9dfc56f38c 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -11,7 +11,7 @@ use language::language_settings::language_settings; use language::proto::deserialize_diff; use language::{Buffer, BundledFormatter, Diff}; use lsp::request::Request; -use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; +use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; @@ -19,6 +19,8 @@ use util::paths::DEFAULT_PRETTIER_DIR; pub enum Prettier { Local(Local), Remote(Remote), + #[cfg(any(test, feature = "test-support"))] + Test(TestPrettier), } pub struct Local { @@ -35,6 +37,13 @@ pub struct Remote { client: Arc, } +#[cfg(any(test, feature = "test-support"))] +pub struct TestPrettier { + worktree_id: Option, + prettier_dir: PathBuf, + default: bool, +} + #[derive(Debug)] pub struct LocateStart { pub worktree_root_path: Arc, @@ -180,6 +189,22 @@ impl Prettier { } } + #[cfg(any(test, feature = "test-support"))] + pub async fn start( + worktree_id: Option, + _: LanguageServerId, + prettier_dir: PathBuf, + _: Arc, + _: AsyncAppContext, + ) -> anyhow::Result { + Ok(Self::Test(TestPrettier { + worktree_id, + default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), + prettier_dir, + })) + } + + #[cfg(not(any(test, feature = "test-support")))] pub async fn start( worktree_id: Option, server_id: LanguageServerId, @@ -400,6 +425,12 @@ impl Prettier { .diff .map(deserialize_diff) .context("missing diff after prettier diff invocation"), + Self::Test(_) => Ok(buffer + .read_with(cx, |buffer, cx| { + let formatted_text = buffer.text() + "\nformatted by test prettier"; + buffer.diff(formatted_text, cx) + }) + .await), } } @@ -427,34 +458,39 @@ impl Prettier { ) }) .context("prettier invoke clear cache"), + Self::Test(_) => Ok(()), } } pub fn server(&self) -> Option<&Arc> { match self { - Prettier::Local(local) => Some(&local.server), - Prettier::Remote(_) => None, + Self::Local(local) => Some(&local.server), + Self::Remote(_) => None, + Self::Test(_) => None, } } pub fn is_default(&self) -> bool { match self { - Prettier::Local(local) => local.default, - Prettier::Remote(_) => false, + Self::Local(local) => local.default, + Self::Remote(_) => false, + Self::Test(test_prettier) => test_prettier.default, } } pub fn prettier_dir(&self) -> &Path { match self { - Prettier::Local(local) => &local.prettier_dir, - Prettier::Remote(remote) => &remote.prettier_dir, + Self::Local(local) => &local.prettier_dir, + Self::Remote(remote) => &remote.prettier_dir, + Self::Test(test_prettier) => &test_prettier.prettier_dir, } } pub fn worktree_id(&self) -> Option { match self { - Prettier::Local(local) => local.worktree_id, - Prettier::Remote(remote) => remote.worktree_id, + Self::Local(local) => local.worktree_id, + Self::Remote(remote) => remote.worktree_id, + Self::Test(test_prettier) => test_prettier.worktree_id, } } } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 9f505c3fd2..cfa623d534 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -15,6 +15,7 @@ test-support = [ "language/test-support", "settings/test-support", "text/test-support", + "prettier/test-support", ] [dependencies] @@ -75,6 +76,7 @@ gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } +prettier = { path = "../prettier", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } git2.workspace = true From 0cec0c1c1d82c5b2c5bc0fe5b7afaf9f9073337f Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 11 Oct 2023 13:41:58 -0400 Subject: [PATCH 64/92] Fixup layout --- crates/editor/src/editor.rs | 19 ++++++++++++++++--- crates/theme/src/theme.rs | 4 +++- styles/src/style_tree/editor.ts | 4 +++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 06482dbbc6..3fc47f48e9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1240,6 +1240,9 @@ impl CompletionsMenu { ) .map(|task| task.detach()); }) + .constrained() + .with_min_width(style.autocomplete.completion_min_width) + .with_max_width(style.autocomplete.completion_max_width) .into_any(), ); } @@ -1250,7 +1253,7 @@ impl CompletionsMenu { enum MultiLineDocumentation {} Flex::row() - .with_child(list) + .with_child(list.flex(1., false)) .with_children({ let mat = &self.matches[selected_item]; let completions = self.completions.read(); @@ -1263,7 +1266,12 @@ impl CompletionsMenu { .scrollable::(0, None, cx) .with_child( Text::new(text.clone(), style.text.clone()).with_soft_wrap(true), - ), + ) + .contained() + .with_style(style.autocomplete.alongside_docs_container) + .constrained() + .with_max_width(style.autocomplete.alongside_docs_max_width) + .flex(1., false), ), Some(Documentation::MultiLineMarkdown(parsed)) => Some( @@ -1271,7 +1279,12 @@ impl CompletionsMenu { .scrollable::(0, None, cx) .with_child(render_parsed_markdown::( parsed, &style, cx, - )), + )) + .contained() + .with_style(style.autocomplete.alongside_docs_container) + .constrained() + .with_max_width(style.autocomplete.alongside_docs_max_width) + .flex(1., false), ), _ => None, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 9f7530ec18..f335444b58 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -867,10 +867,12 @@ pub struct AutocompleteStyle { pub selected_item: ContainerStyle, pub hovered_item: ContainerStyle, pub match_highlight: HighlightStyle, + pub completion_min_width: f32, + pub completion_max_width: f32, pub inline_docs_container: ContainerStyle, pub inline_docs_color: Color, pub inline_docs_size_percent: f32, - pub alongside_docs_width: f32, + pub alongside_docs_max_width: f32, pub alongside_docs_container: ContainerStyle, } diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index e7717583a8..27a6eaf195 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -206,10 +206,12 @@ export default function editor(): any { match_highlight: foreground(theme.middle, "accent", "active"), background: background(theme.middle, "active"), }, + completion_min_width: 300, + completion_max_width: 700, inline_docs_container: { padding: { left: 40 } }, inline_docs_color: text(theme.middle, "sans", "disabled", {}).color, inline_docs_size_percent: 0.75, - alongside_docs_width: 700, + alongside_docs_max_width: 700, alongside_docs_container: { padding: autocomplete_item.padding } }, diagnostic_header: { From a09ee3a41b2a95f2dc016b588970e9b97537e7b8 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 11 Oct 2023 14:39:34 -0400 Subject: [PATCH 65/92] Fire markdown link on mouse down Previously any amount of mouse movement would disqualify the mouse down and up from being a click, being a drag instead, which is a long standing UX issue. We can get away with just firing on mouse down here for now --- crates/editor/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3fc47f48e9..9c1e0b3c18 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -154,7 +154,7 @@ pub fn render_parsed_markdown( }); cx.scene().push_mouse_region( MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { + .on_down::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) }), ); From f6d0934b5d87af4c2e36a14884f498430e14a980 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Oct 2023 15:17:46 -0600 Subject: [PATCH 66/92] deep considered harmful --- script/bundle | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/script/bundle b/script/bundle index e4eb23b217..dc5022bea5 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 a528c6c68610ab075ace8aad4bb22a1b6cf46d94 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 12:31:30 +0300 Subject: [PATCH 67/92] Prettier server style fixes --- crates/node_runtime/src/node_runtime.rs | 4 ++-- crates/prettier/src/prettier.rs | 21 ++++++++++++++++----- crates/prettier/src/prettier_server.js | 23 +++++++++++++---------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 8bfb26cdad..125318dd67 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -243,7 +243,7 @@ impl NodeRuntime for FakeNodeRuntime { if name == "prettier" { Ok("0.0.1".to_string()) } else { - unreachable!() + unreachable!("Unexpected package name: {name}") } } @@ -255,7 +255,7 @@ impl NodeRuntime for FakeNodeRuntime { if packages == [("prettier", "0.0.1")] { Ok(()) } else { - unreachable!() + unreachable!("Unexpected packages to install: {packages:?}") } } } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 9dfc56f38c..6c1d0665e2 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -197,11 +197,14 @@ impl Prettier { _: Arc, _: AsyncAppContext, ) -> anyhow::Result { - Ok(Self::Test(TestPrettier { - worktree_id, - default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), - prettier_dir, - })) + Ok( + #[cfg(any(test, feature = "test-support"))] + Self::Test(TestPrettier { + worktree_id, + default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), + prettier_dir, + }), + ) } #[cfg(not(any(test, feature = "test-support")))] @@ -212,6 +215,8 @@ impl Prettier { node: Arc, cx: AsyncAppContext, ) -> anyhow::Result { + use lsp::LanguageServerBinary; + let backgroud = cx.background(); anyhow::ensure!( prettier_dir.is_dir(), @@ -425,6 +430,7 @@ impl Prettier { .diff .map(deserialize_diff) .context("missing diff after prettier diff invocation"), + #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(buffer .read_with(cx, |buffer, cx| { let formatted_text = buffer.text() + "\nformatted by test prettier"; @@ -458,6 +464,7 @@ impl Prettier { ) }) .context("prettier invoke clear cache"), + #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(()), } } @@ -466,6 +473,7 @@ impl Prettier { match self { Self::Local(local) => Some(&local.server), Self::Remote(_) => None, + #[cfg(any(test, feature = "test-support"))] Self::Test(_) => None, } } @@ -474,6 +482,7 @@ impl Prettier { match self { Self::Local(local) => local.default, Self::Remote(_) => false, + #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.default, } } @@ -482,6 +491,7 @@ impl Prettier { match self { Self::Local(local) => &local.prettier_dir, Self::Remote(remote) => &remote.prettier_dir, + #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => &test_prettier.prettier_dir, } } @@ -490,6 +500,7 @@ impl Prettier { match self { Self::Local(local) => local.worktree_id, Self::Remote(remote) => remote.worktree_id, + #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.worktree_id, } } diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index db3e26ef6d..a56c220f20 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -39,13 +39,13 @@ class Prettier { process.stderr.write(`Failed to load prettier: ${e}\n`); process.exit(1); } - process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${config}\n`); + process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${JSON.stringify(config)}\n`); process.stdin.resume(); handleBuffer(new Prettier(prettierPath, prettier, config)); })() async function handleBuffer(prettier) { - for await (let messageText of readStdin()) { + for await (const messageText of readStdin()) { let message; try { message = JSON.parse(messageText); @@ -53,6 +53,7 @@ async function handleBuffer(prettier) { sendResponse(makeError(`Failed to parse message '${messageText}': ${e}`)); continue; } + // allow concurrent request handling by not `await`ing the message handling promise (async function) handleMessage(message, prettier).catch(e => { sendResponse({ id: message.id, ...makeError(`error during message handling: ${e}`) }); }); @@ -96,16 +97,18 @@ async function* readStdin() { await once(process.stdin, 'readable'); } const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii'); - const contentLengthHeader = headers.split(headerSeparator).map(header => header.split(':')) + const contentLengthHeader = headers.split(headerSeparator) + .map(header => header.split(':')) .filter(header => header[2] === undefined) .filter(header => (header[1] || '').length > 0) - .find(header => header[0].trim() === contentLengthHeaderName); - if (contentLengthHeader === undefined) { + .find(header => (header[0] || '').trim() === contentLengthHeaderName); + const contentLength = (contentLengthHeader || [])[1]; + if (contentLength === undefined) { await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`); continue main_loop; } headersLength = headers.length + headerSeparator.length * 2; - messageLength = parseInt(contentLengthHeader[1], 10); + messageLength = parseInt(contentLength, 10); } while (buffer.length < (headersLength + messageLength)) { @@ -120,6 +123,7 @@ async function* readStdin() { const messageEnd = headersLength + messageLength; const message = buffer.subarray(headersLength, messageEnd); buffer = buffer.subarray(messageEnd); + headersLength = null; messageLength = null; yield message.toString('utf8'); } @@ -188,13 +192,12 @@ function makeError(message) { } function sendResponse(response) { - let responsePayloadString = JSON.stringify({ + const responsePayloadString = JSON.stringify({ jsonrpc: "2.0", ...response }); - let headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; - let dataToSend = headers + responsePayloadString; - process.stdout.write(dataToSend); + const headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; + process.stdout.write(headers + responsePayloadString); } function loadPrettier(prettierPath) { From 7f4ebf50d3cee3d34733d43e48f19d8c0a0d6122 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 12:40:35 +0300 Subject: [PATCH 68/92] Make the first prettier test pass --- crates/editor/src/editor_tests.rs | 27 +++--- crates/node_runtime/src/node_runtime.rs | 107 +++++++++++++++++++++--- crates/prettier/src/prettier.rs | 5 +- crates/project/src/project.rs | 14 +++- 4 files changed, 124 insertions(+), 29 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index ddae186679..4cb0c009bb 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -7830,11 +7830,12 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { Some(tree_sitter_rust::language()), ); + let test_plugin = "test_plugin"; let _ = language .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { enabled_formatters: vec![BundledFormatter::Prettier { parser_name: Some("test_parser"), - plugin_names: vec!["test_plugin"], + plugin_names: vec![test_plugin], }], ..Default::default() })) @@ -7843,37 +7844,31 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { let fs = FakeFs::new(cx.background()); fs.insert_file("/file.rs", Default::default()).await; - // TODO kb have to specify some test node runtime let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; - project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let prettier_format_suffix = project.update(cx, |project, _| { + let suffix = project.enable_test_prettier(&[test_plugin]); + project.languages().add(Arc::new(language)); + suffix + }); let buffer = project .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) .await .unwrap(); + let buffer_text = "one\ntwo\nthree\n"; let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); - editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); + editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx)); let format = editor.update(cx, |editor, cx| { editor.perform_format(project.clone(), FormatTrigger::Manual, cx) }); format.await.unwrap(); - assert_eq!( - editor.read_with(cx, |editor, cx| editor.text(cx)), - "one, two\nthree\n" - ); - editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); - let format = editor.update(cx, |editor, cx| { - editor.perform_format(project, FormatTrigger::Manual, cx) - }); - cx.foreground().advance_clock(super::FORMAT_TIMEOUT); - cx.foreground().start_waiting(); - format.await.unwrap(); assert_eq!( editor.read_with(cx, |editor, cx| editor.text(cx)), - "one\ntwo\nthree\n" + buffer_text.to_string() + prettier_format_suffix, + "Test prettier formatting was not applied to the original buffer text", ); } diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 125318dd67..dcb8833f8c 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -220,18 +220,83 @@ impl NodeRuntime for RealNodeRuntime { } } -pub struct FakeNodeRuntime; +pub struct FakeNodeRuntime(Option); + +struct PrettierSupport { + plugins: Vec<&'static str>, +} impl FakeNodeRuntime { pub fn new() -> Arc { - Arc::new(FakeNodeRuntime) + Arc::new(FakeNodeRuntime(None)) + } + + pub fn with_prettier_support(plugins: &[&'static str]) -> Arc { + Arc::new(FakeNodeRuntime(Some(PrettierSupport::new(plugins)))) } } #[async_trait::async_trait] impl NodeRuntime for FakeNodeRuntime { async fn binary_path(&self) -> anyhow::Result { - // TODO kb move away into a separate type + a Project's setter (for test code) + if let Some(prettier_support) = &self.0 { + prettier_support.binary_path().await + } else { + unreachable!() + } + } + + async fn run_npm_subcommand( + &self, + directory: Option<&Path>, + subcommand: &str, + args: &[&str], + ) -> anyhow::Result { + if let Some(prettier_support) = &self.0 { + prettier_support + .run_npm_subcommand(directory, subcommand, args) + .await + } else { + unreachable!() + } + } + + async fn npm_package_latest_version(&self, name: &str) -> anyhow::Result { + if let Some(prettier_support) = &self.0 { + prettier_support.npm_package_latest_version(name).await + } else { + unreachable!() + } + } + + async fn npm_install_packages( + &self, + directory: &Path, + packages: &[(&str, &str)], + ) -> anyhow::Result<()> { + if let Some(prettier_support) = &self.0 { + prettier_support + .npm_install_packages(directory, packages) + .await + } else { + unreachable!() + } + } +} + +impl PrettierSupport { + const PACKAGE_VERSION: &str = "0.0.1"; + + fn new(plugins: &[&'static str]) -> Self { + Self { + plugins: plugins.to_vec(), + } + } +} + +#[async_trait::async_trait] +impl NodeRuntime for PrettierSupport { + async fn binary_path(&self) -> anyhow::Result { Ok(PathBuf::from("prettier_fake_node")) } @@ -240,10 +305,10 @@ impl NodeRuntime for FakeNodeRuntime { } async fn npm_package_latest_version(&self, name: &str) -> anyhow::Result { - if name == "prettier" { - Ok("0.0.1".to_string()) + if name == "prettier" || self.plugins.contains(&name) { + Ok(Self::PACKAGE_VERSION.to_string()) } else { - unreachable!("Unexpected package name: {name}") + panic!("Unexpected package name: {name}") } } @@ -252,10 +317,32 @@ impl NodeRuntime for FakeNodeRuntime { _: &Path, packages: &[(&str, &str)], ) -> anyhow::Result<()> { - if packages == [("prettier", "0.0.1")] { - Ok(()) - } else { - unreachable!("Unexpected packages to install: {packages:?}") + assert_eq!( + packages.len(), + self.plugins.len() + 1, + "Unexpected packages length to install: {:?}, expected `prettier` + {:?}", + packages, + self.plugins + ); + for (name, version) in packages { + assert!( + name == &"prettier" || self.plugins.contains(name), + "Unexpected package `{}` to install in packages {:?}, expected {} for `prettier` + {:?}", + name, + packages, + Self::PACKAGE_VERSION, + self.plugins + ); + assert_eq!( + version, + &Self::PACKAGE_VERSION, + "Unexpected package version `{}` to install in packages {:?}, expected {} for `prettier` + {:?}", + version, + packages, + Self::PACKAGE_VERSION, + self.plugins + ); } + Ok(()) } } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 6c1d0665e2..656c84b46c 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -71,6 +71,9 @@ impl Prettier { ".editorconfig", ]; + #[cfg(any(test, feature = "test-support"))] + pub const FORMAT_SUFFIX: &str = "\nformatted by test prettier"; + pub fn remote( project_id: u64, worktree_id: Option, @@ -433,7 +436,7 @@ impl Prettier { #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(buffer .read_with(cx, |buffer, cx| { - let formatted_text = buffer.text() + "\nformatted by test prettier"; + let formatted_text = buffer.text() + Self::FORMAT_SUFFIX; buffer.diff(formatted_text, cx) }) .await), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ebc2aaaf60..e31ecca257 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -837,6 +837,16 @@ impl Project { project } + /// Enables a prettier mock that avoids interacting with node runtime, prettier LSP wrapper, or any real file changes. + /// Instead, if appends the suffix to every input, this suffix is returned by this method. + #[cfg(any(test, feature = "test-support"))] + pub fn enable_test_prettier(&mut self, plugins: &[&'static str]) -> &'static str { + self.node = Some(node_runtime::FakeNodeRuntime::with_prettier_support( + plugins, + )); + Prettier::FORMAT_SUFFIX + } + fn on_settings_changed(&mut self, cx: &mut ModelContext) { let mut language_servers_to_start = Vec::new(); let mut language_formatters_to_check = Vec::new(); @@ -8442,7 +8452,7 @@ impl Project { return Some(existing_prettier); } - log::info!("Found prettier at {prettier_dir:?}, starting."); + log::info!("Found prettier in {prettier_dir:?}, starting."); let task_prettier_dir = prettier_dir.clone(); let weak_project = this.downgrade(); let new_server_id = @@ -8459,7 +8469,7 @@ impl Project { .await .context("prettier start") .map_err(Arc::new)?; - log::info!("Had started prettier in {:?}", prettier.prettier_dir()); + log::info!("Started prettier in {:?}", prettier.prettier_dir()); if let Some((project, prettier_server)) = weak_project.upgrade(&mut cx).zip(prettier.server()) From 1bfde4bfa254ca151ad2f918cb2102fefb68055e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 14:45:56 +0300 Subject: [PATCH 69/92] Add more tests --- crates/collab/src/tests/integration_tests.rs | 138 ++++++++++++++++++- crates/editor/src/editor_tests.rs | 33 ++++- 2 files changed, 162 insertions(+), 9 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 4008a941dd..d6d449fd47 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -15,12 +15,14 @@ use gpui::{executor::Deterministic, test::EmptyView, AppContext, ModelHandle, Te use indoc::indoc; use language::{ language_settings::{AllLanguageSettings, Formatter, InlayHintSettings}, - tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, - LanguageConfig, LineEnding, OffsetRangeExt, Point, Rope, + tree_sitter_rust, Anchor, BundledFormatter, Diagnostic, DiagnosticEntry, FakeLspAdapter, + Language, LanguageConfig, LineEnding, OffsetRangeExt, Point, Rope, }; use live_kit_client::MacOSDisplay; use lsp::LanguageServerId; -use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath}; +use project::{ + search::SearchQuery, DiagnosticSummary, FormatTrigger, HoverBlockKind, Project, ProjectPath, +}; use rand::prelude::*; use serde_json::json; use settings::SettingsStore; @@ -4407,8 +4409,6 @@ async fn test_formatting_buffer( cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, ) { - use project::FormatTrigger; - let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -4511,6 +4511,134 @@ async fn test_formatting_buffer( ); } +#[gpui::test(iterations = 10)] +async fn test_prettier_formatting_buffer( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(&deterministic).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + + // Set up a fake language server. + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let test_plugin = "test_plugin"; + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + enabled_formatters: vec![BundledFormatter::Prettier { + parser_name: Some("test_parser"), + plugin_names: vec![test_plugin], + }], + ..Default::default() + })) + .await; + let language = Arc::new(language); + client_a.language_registry().add(Arc::clone(&language)); + + // Here we insert a fake tree with a directory that exists on disk. This is needed + // because later we'll invoke a command, which requires passing a working directory + // that points to a valid location on disk. + let directory = env::current_dir().unwrap(); + let buffer_text = "let one = \"two\""; + client_a + .fs() + .insert_tree(&directory, json!({ "a.rs": buffer_text })) + .await; + let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await; + let prettier_format_suffix = project_a.update(cx_a, |project, _| { + let suffix = project.enable_test_prettier(&[test_plugin]); + project.languages().add(language); + suffix + }); + let buffer_a = cx_a + .background() + .spawn(project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))) + .await + .unwrap(); + + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + let project_b = client_b.build_remote_project(project_id, cx_b).await; + let buffer_b = cx_b + .background() + .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))) + .await + .unwrap(); + + cx_a.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |file| { + file.defaults.formatter = Some(Formatter::Auto); + }); + }); + }); + cx_b.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |file| { + file.defaults.formatter = Some(Formatter::LanguageServer); + }); + }); + }); + let fake_language_server = fake_language_servers.next().await.unwrap(); + fake_language_server.handle_request::(|_, _| async move { + panic!( + "Unexpected: prettier should be preferred since it's enabled and language supports it" + ) + }); + + project_b + .update(cx_b, |project, cx| { + project.format( + HashSet::from_iter([buffer_b.clone()]), + true, + FormatTrigger::Save, + cx, + ) + }) + .await + .unwrap(); + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + assert_eq!( + buffer_b.read_with(cx_b, |buffer, _| buffer.text()), + buffer_text.to_string() + "\n" + prettier_format_suffix, + "Prettier formatting was not applied to client buffer after client's request" + ); + + project_a + .update(cx_a, |project, cx| { + project.format( + HashSet::from_iter([buffer_a.clone()]), + true, + FormatTrigger::Manual, + cx, + ) + }) + .await + .unwrap(); + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + assert_eq!( + buffer_b.read_with(cx_b, |buffer, _| buffer.text()), + buffer_text.to_string() + "\n" + prettier_format_suffix + "\n" + prettier_format_suffix, + "Prettier formatting was not applied to client buffer after host's request" + ); +} + #[gpui::test(iterations = 10)] async fn test_definition( deterministic: Arc, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 4cb0c009bb..c68f72d16f 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5076,7 +5076,9 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { - init_test(cx, |_| {}); + init_test(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer) + }); let mut language = Language::new( LanguageConfig { @@ -5092,6 +5094,12 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { document_formatting_provider: Some(lsp::OneOf::Left(true)), ..Default::default() }, + // Enable Prettier formatting for the same buffer, and ensure + // LSP is called instead of Prettier. + enabled_formatters: vec![BundledFormatter::Prettier { + parser_name: Some("test_parser"), + plugin_names: Vec::new(), + }], ..Default::default() })) .await; @@ -5100,7 +5108,10 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { fs.insert_file("/file.rs", Default::default()).await; let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; - project.update(cx, |project, _| project.languages().add(Arc::new(language))); + project.update(cx, |project, _| { + project.enable_test_prettier(&[]); + project.languages().add(Arc::new(language)); + }); let buffer = project .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) .await @@ -5218,7 +5229,9 @@ async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) { - init_test(cx, |_| {}); + init_test(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::Auto) + }); let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { @@ -7864,12 +7877,24 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { editor.perform_format(project.clone(), FormatTrigger::Manual, cx) }); format.await.unwrap(); - assert_eq!( editor.read_with(cx, |editor, cx| editor.text(cx)), buffer_text.to_string() + prettier_format_suffix, "Test prettier formatting was not applied to the original buffer text", ); + + update_test_language_settings(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::Auto) + }); + let format = editor.update(cx, |editor, cx| { + editor.perform_format(project.clone(), FormatTrigger::Manual, cx) + }); + format.await.unwrap(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix, + "Autoformatting (via test prettier) was not applied to the original buffer text", + ); } fn empty_range(row: usize, column: usize) -> Range { From 12d7d8db0a85f77c41b2b05944293e83eafe9f1f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 15:26:47 +0300 Subject: [PATCH 70/92] Make all formatting to happen on the client's buffers, as needed --- crates/language/src/proto.rs | 41 +---------- crates/prettier/src/prettier.rs | 104 +++------------------------ crates/project/src/project.rs | 122 +------------------------------- crates/rpc/proto/zed.proto | 39 +--------- crates/rpc/src/proto.rs | 8 --- 5 files changed, 14 insertions(+), 300 deletions(-) diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 5da1d5713f..c4abe39d47 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -1,6 +1,6 @@ use crate::{ diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, - Diff, Language, + Language, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -587,42 +587,3 @@ pub fn serialize_version(version: &clock::Global) -> Vec proto::Diff { - proto::Diff { - version: serialize_version(&diff.base_version), - line_ending: serialize_line_ending(diff.line_ending) as i32, - edits: diff - .edits - .into_iter() - .map(|(range, edit)| proto::DiffEdit { - range: Some(proto::Range { - start: range.start as u64, - end: range.end as u64, - }), - edit: edit.to_string(), - }) - .collect(), - } -} - -pub fn deserialize_diff(diff: proto::Diff) -> Diff { - Diff { - base_version: deserialize_version(&diff.version), - line_ending: deserialize_line_ending( - rpc::proto::LineEnding::from_i32(diff.line_ending) - .unwrap_or_else(|| panic!("invalid line ending {}", diff.line_ending)), - ), - edits: diff - .edits - .into_iter() - .map(|edit| { - let range = edit.range.expect("incorrect edit without a range"); - ( - range.start as usize..range.end as usize, - Arc::from(edit.edit.as_str()), - ) - }) - .collect(), - } -} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 656c84b46c..240ee1e895 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,40 +3,29 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; -use client::{proto, Client}; use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; -use language::proto::deserialize_diff; use language::{Buffer, BundledFormatter, Diff}; -use lsp::request::Request; use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; pub enum Prettier { - Local(Local), - Remote(Remote), + Real(RealPrettier), #[cfg(any(test, feature = "test-support"))] Test(TestPrettier), } -pub struct Local { +pub struct RealPrettier { worktree_id: Option, default: bool, prettier_dir: PathBuf, server: Arc, } -pub struct Remote { - project_id: u64, - worktree_id: Option, - prettier_dir: PathBuf, - client: Arc, -} - #[cfg(any(test, feature = "test-support"))] pub struct TestPrettier { worktree_id: Option, @@ -74,20 +63,6 @@ impl Prettier { #[cfg(any(test, feature = "test-support"))] pub const FORMAT_SUFFIX: &str = "\nformatted by test prettier"; - pub fn remote( - project_id: u64, - worktree_id: Option, - prettier_dir: PathBuf, - client: Arc, - ) -> Self { - Self::Remote(Remote { - project_id, - worktree_id, - prettier_dir, - client, - }) - } - pub async fn locate( starting_path: Option, fs: Arc, @@ -249,7 +224,7 @@ impl Prettier { .spawn(server.initialize(None)) .await .context("prettier server initialization")?; - Ok(Self::Local(Local { + Ok(Self::Real(RealPrettier { worktree_id, server, default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), @@ -257,31 +232,6 @@ impl Prettier { })) } - pub async fn invoke( - &self, - buffer: Option<&ModelHandle>, - buffer_path: Option, - method: &str, - cx: &AsyncAppContext, - ) -> anyhow::Result> { - match method { - Format::METHOD => self - .format( - buffer.expect("missing buffer for format invocation"), - buffer_path, - cx, - ) - .await - .context("invoke method") - .map(Some), - ClearCache::METHOD => { - self.clear_cache().await.context("invoke method")?; - Ok(None) - } - unknown => anyhow::bail!("Unknown method {unknown}"), - } - } - pub async fn format( &self, buffer: &ModelHandle, @@ -289,7 +239,7 @@ impl Prettier { cx: &AsyncAppContext, ) -> anyhow::Result { match self { - Self::Local(local) => { + Self::Real(local) => { let params = buffer.read_with(cx, |buffer, cx| { let buffer_language = buffer.language(); let parsers_with_plugins = buffer_language @@ -418,21 +368,6 @@ impl Prettier { let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); Ok(diff_task.await) } - Self::Remote(remote) => buffer - .read_with(cx, |buffer, _| { - remote.client.request(proto::InvokePrettierForBuffer { - buffer_id: Some(buffer.remote_id()), - worktree_id: self.worktree_id().map(|id| id as u64), - method: Format::METHOD.to_string(), - project_id: remote.project_id, - prettier_path: remote.prettier_dir.to_string_lossy().to_string(), - }) - }) - .await - .context("prettier diff invoke")? - .diff - .map(deserialize_diff) - .context("missing diff after prettier diff invocation"), #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(buffer .read_with(cx, |buffer, cx| { @@ -445,28 +380,11 @@ impl Prettier { pub async fn clear_cache(&self) -> anyhow::Result<()> { match self { - Self::Local(local) => local + Self::Real(local) => local .server .request::(()) .await .context("prettier clear cache"), - Self::Remote(remote) => remote - .client - .request(proto::InvokePrettierForBuffer { - buffer_id: None, - worktree_id: self.worktree_id().map(|id| id as u64), - method: ClearCache::METHOD.to_string(), - project_id: remote.project_id, - prettier_path: remote.prettier_dir.to_string_lossy().to_string(), - }) - .await - .map(|response| { - debug_assert!( - response.diff.is_none(), - "Cleare cache invocation returned diff data" - ) - }) - .context("prettier invoke clear cache"), #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(()), } @@ -474,8 +392,7 @@ impl Prettier { pub fn server(&self) -> Option<&Arc> { match self { - Self::Local(local) => Some(&local.server), - Self::Remote(_) => None, + Self::Real(local) => Some(&local.server), #[cfg(any(test, feature = "test-support"))] Self::Test(_) => None, } @@ -483,8 +400,7 @@ impl Prettier { pub fn is_default(&self) -> bool { match self { - Self::Local(local) => local.default, - Self::Remote(_) => false, + Self::Real(local) => local.default, #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.default, } @@ -492,8 +408,7 @@ impl Prettier { pub fn prettier_dir(&self) -> &Path { match self { - Self::Local(local) => &local.prettier_dir, - Self::Remote(remote) => &remote.prettier_dir, + Self::Real(local) => &local.prettier_dir, #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => &test_prettier.prettier_dir, } @@ -501,8 +416,7 @@ impl Prettier { pub fn worktree_id(&self) -> Option { match self { - Self::Local(local) => local.worktree_id, - Self::Remote(remote) => remote.worktree_id, + Self::Real(local) => local.worktree_id, #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.worktree_id, } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e31ecca257..f9e1b1ce96 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -37,7 +37,7 @@ use language::{ point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, - serialize_anchor, serialize_diff, serialize_version, split_operations, + serialize_anchor, serialize_version, split_operations, }, range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, @@ -613,8 +613,6 @@ impl Project { client.add_model_request_handler(Self::handle_open_buffer_by_path); client.add_model_request_handler(Self::handle_save_buffer); client.add_model_message_handler(Self::handle_update_diff_base); - client.add_model_request_handler(Self::handle_prettier_instance_for_buffer); - client.add_model_request_handler(Self::handle_invoke_prettier); } pub fn local( @@ -8310,84 +8308,6 @@ impl Project { } } - async fn handle_prettier_instance_for_buffer( - this: ModelHandle, - envelope: TypedEnvelope, - _: Arc, - mut cx: AsyncAppContext, - ) -> anyhow::Result { - let prettier_instance_for_buffer_task = this.update(&mut cx, |this, cx| { - let buffer = this - .opened_buffers - .get(&envelope.payload.buffer_id) - .and_then(|buffer| buffer.upgrade(cx)) - .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; - anyhow::Ok(this.prettier_instance_for_buffer(&buffer, cx)) - })?; - - let prettier_path = match prettier_instance_for_buffer_task.await { - Some(prettier) => match prettier.await { - Ok(prettier) => Some(prettier.prettier_dir().display().to_string()), - Err(e) => { - anyhow::bail!("Failed to create prettier instance for remote request: {e:#}") - } - }, - None => None, - }; - Ok(proto::PrettierInstanceForBufferResponse { prettier_path }) - } - - async fn handle_invoke_prettier( - this: ModelHandle, - envelope: TypedEnvelope, - _: Arc, - mut cx: AsyncAppContext, - ) -> anyhow::Result { - let prettier = this - .read_with(&cx, |this, _| { - this.prettier_instances - .get(&( - envelope.payload.worktree_id.map(WorktreeId::from_proto), - PathBuf::from(&envelope.payload.prettier_path), - )) - .cloned() - }) - .with_context(|| { - format!( - "Missing prettier for worktree {:?} and path {:?}", - envelope.payload.worktree_id, envelope.payload.prettier_path, - ) - })? - .await; - let prettier = match prettier { - Ok(prettier) => prettier, - Err(e) => anyhow::bail!("Prettier instance failed to start: {e:#}"), - }; - - let buffer = this.update(&mut cx, |this, cx| { - envelope - .payload - .buffer_id - .and_then(|id| this.opened_buffers.get(&id)) - .and_then(|buffer| buffer.upgrade(cx)) - }); - - let buffer_path = buffer.as_ref().and_then(|buffer| { - buffer.read_with(&cx, |buffer, cx| { - File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) - }) - }); - - let diff = prettier - .invoke(buffer.as_ref(), buffer_path, &envelope.payload.method, &cx) - .await - .with_context(|| format!("prettier invoke method {}", &envelope.payload.method))?; - - Ok(proto::InvokePrettierForBufferResponse { - diff: diff.map(serialize_diff), - }) - } - fn prettier_instance_for_buffer( &mut self, buffer: &ModelHandle, @@ -8531,44 +8451,8 @@ impl Project { }); Some(new_prettier_task) }) - } else if let Some(project_id) = self.remote_id() { - let client = self.client.clone(); - let request = proto::PrettierInstanceForBuffer { - project_id, - buffer_id: buffer.remote_id(), - }; - cx.spawn(|this, mut cx| async move { - match client.request(request).await { - Ok(response) => { - response - .prettier_path - .map(PathBuf::from) - .map(|prettier_path| { - let prettier_task = Task::ready( - Ok(Arc::new(Prettier::remote( - project_id, - worktree_id.map(|id| id.to_usize()), - prettier_path.clone(), - client, - ))) - .map_err(Arc::new), - ) - .shared(); - this.update(&mut cx, |project, _| { - project.prettier_instances.insert( - (worktree_id, prettier_path), - prettier_task.clone(), - ); - }); - prettier_task - }) - } - Err(e) => { - log::error!("Prettier init remote request failed: {e:#}"); - None - } - } - }) + } else if self.remote_id().is_some() { + return Task::ready(None); } else { Task::ready(Some( Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(), diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index aae207f41c..302014ab8e 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -170,12 +170,7 @@ message Envelope { LinkChannel link_channel = 140; UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; - - PrettierInstanceForBuffer prettier_instance_for_buffer = 145; - PrettierInstanceForBufferResponse prettier_instance_for_buffer_response = 146; - InvokePrettierForBuffer invoke_prettier_for_buffer = 147; - InvokePrettierForBufferResponse invoke_prettier_for_buffer_response = 148; // Current max: 148 + MoveChannel move_channel = 142; // Current max: 144 } } @@ -1562,35 +1557,3 @@ message UpdateDiffBase { uint64 buffer_id = 2; optional string diff_base = 3; } - -message PrettierInstanceForBuffer { - uint64 project_id = 1; - uint64 buffer_id = 2; -} - -message PrettierInstanceForBufferResponse { - optional string prettier_path = 1; -} - -message InvokePrettierForBuffer { - uint64 project_id = 1; - string prettier_path = 2; - optional uint64 buffer_id = 3; - optional uint64 worktree_id = 4; - string method = 5; -} - -message InvokePrettierForBufferResponse { - optional Diff diff = 1; -} - -message Diff { - repeated VectorClockEntry version = 1; - LineEnding line_ending = 2; - repeated DiffEdit edits = 3; -} - -message DiffEdit { - Range range = 1; - string edit = 2; -} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index b51f1d8ae9..e976a3d0b5 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -273,10 +273,6 @@ messages!( (UpdateChannelBufferCollaborators, Foreground), (AckBufferOperation, Background), (AckChannelMessage, Background), - (PrettierInstanceForBuffer, Background), - (InvokePrettierForBuffer, Background), - (PrettierInstanceForBufferResponse, Background), - (InvokePrettierForBufferResponse, Background), ); request_messages!( @@ -354,8 +350,6 @@ request_messages!( (UpdateWorktree, Ack), (JoinChannelBuffer, JoinChannelBufferResponse), (LeaveChannelBuffer, Ack), - (PrettierInstanceForBuffer, PrettierInstanceForBufferResponse), - (InvokePrettierForBuffer, InvokePrettierForBufferResponse), ); entity_messages!( @@ -407,8 +401,6 @@ entity_messages!( UpdateWorktree, UpdateWorktreeSettings, UpdateDiffBase, - PrettierInstanceForBuffer, - InvokePrettierForBuffer, ); entity_messages!( From 09ef3ccf6745c4246af964845d72e9269bebab65 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 15:58:00 +0300 Subject: [PATCH 71/92] Fix tailwind prettier plugin discovery --- crates/prettier/src/prettier.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 240ee1e895..c3811b567b 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -283,9 +283,11 @@ impl Prettier { let prettier_plugin_dir = prettier_node_modules.join(plugin_name); for possible_plugin_path in [ prettier_plugin_dir.join("dist").join("index.mjs"), + prettier_plugin_dir.join("dist").join("index.js"), + prettier_plugin_dir.join("dist").join("plugin.js"), prettier_plugin_dir.join("index.mjs"), - prettier_plugin_dir.join("plugin.js"), prettier_plugin_dir.join("index.js"), + prettier_plugin_dir.join("plugin.js"), prettier_plugin_dir, ] { if possible_plugin_path.is_file() { From 7aea95704eb53b35f2d956c30b829a1dc4e91829 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 16:15:42 +0300 Subject: [PATCH 72/92] Revert unnecessary style changes --- crates/fs/src/fs.rs | 3 +-- crates/language/src/buffer.rs | 4 ++-- crates/rpc/proto/zed.proto | 2 +- crates/rpc/src/proto.rs | 4 ++-- crates/zed/src/languages/php.rs | 1 - 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index bb5d6387e0..1bc8fa9a24 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -229,12 +229,11 @@ impl Fs for RealFs { } else { symlink_metadata }; - let file_type_metadata = metadata.file_type(); Ok(Some(Metadata { inode: metadata.ino(), mtime: metadata.modified().unwrap(), is_symlink, - is_dir: file_type_metadata.is_dir(), + is_dir: metadata.file_type().is_dir(), })) } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index eccfb0c059..207c41e7cd 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -317,8 +317,8 @@ pub struct Chunk<'a> { pub struct Diff { pub(crate) base_version: clock::Global, - pub(crate) line_ending: LineEnding, - pub(crate) edits: Vec<(Range, Arc)>, + line_ending: LineEnding, + edits: Vec<(Range, Arc)>, } #[derive(Clone, Copy)] diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 302014ab8e..3501e70e6a 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -170,7 +170,7 @@ message Envelope { LinkChannel link_channel = 140; UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; // Current max: 144 + MoveChannel move_channel = 142; // current max: 144 } } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index e976a3d0b5..f0d7937f6f 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -349,7 +349,7 @@ request_messages!( (UpdateProject, Ack), (UpdateWorktree, Ack), (JoinChannelBuffer, JoinChannelBufferResponse), - (LeaveChannelBuffer, Ack), + (LeaveChannelBuffer, Ack) ); entity_messages!( @@ -400,7 +400,7 @@ entity_messages!( UpdateProjectCollaborator, UpdateWorktree, UpdateWorktreeSettings, - UpdateDiffBase, + UpdateDiffBase ); entity_messages!( diff --git a/crates/zed/src/languages/php.rs b/crates/zed/src/languages/php.rs index bf65deb642..3096fd16e6 100644 --- a/crates/zed/src/languages/php.rs +++ b/crates/zed/src/languages/php.rs @@ -100,7 +100,6 @@ impl LspAdapter for IntelephenseLspAdapter { async fn initialization_options(&self) -> Option { None } - async fn language_ids(&self) -> HashMap { HashMap::from_iter([("PHP".into(), "php".into())]) } From ef73bf799c68425ea9f1a8ec2d6f9cecb367fba0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 16:26:28 +0300 Subject: [PATCH 73/92] Fix license issue --- crates/prettier/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 764bf0f07f..997fa87126 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -2,9 +2,11 @@ name = "prettier" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/prettier.rs" +doctest = false [features] test-support = [] From 4688a94a54501d4604b8aad6de77aecc8d556c7d Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 12:11:27 -0400 Subject: [PATCH 74/92] Allow file links in markdown & filter links a bit aggressively --- crates/editor/src/editor.rs | 38 ++++++++++++++++----- crates/editor/src/element.rs | 10 ++++-- crates/editor/src/hover_popover.rs | 8 +++-- crates/language/src/markdown.rs | 41 ++++++++++++++++++----- crates/terminal_view/src/terminal_view.rs | 7 ++++ 5 files changed, 81 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9c1e0b3c18..0748a0fcf4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -122,6 +122,7 @@ pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); pub fn render_parsed_markdown( parsed: &language::ParsedMarkdown, editor_style: &EditorStyle, + workspace: Option>, cx: &mut ViewContext, ) -> Text { enum RenderedMarkdown {} @@ -147,15 +148,22 @@ pub fn render_parsed_markdown( region_id += 1; let region = parsed.regions[ix].clone(); - if let Some(url) = region.link_url { + if let Some(link) = region.link { cx.scene().push_cursor_region(CursorRegion { bounds, style: CursorStyle::PointingHand, }); cx.scene().push_mouse_region( MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds) - .on_down::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) + .on_down::(MouseButton::Left, move |_, _, cx| match &link { + markdown::Link::Web { url } => cx.platform().open_url(url), + markdown::Link::Path { path } => { + if let Some(workspace) = &workspace { + _ = workspace.update(cx, |workspace, cx| { + workspace.open_abs_path(path.clone(), false, cx).detach(); + }); + } + } }), ); } @@ -916,10 +924,11 @@ impl ContextMenu { &self, cursor_position: DisplayPoint, style: EditorStyle, + workspace: Option>, cx: &mut ViewContext, ) -> (DisplayPoint, AnyElement) { match self { - ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), + ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)), ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), } } @@ -1105,7 +1114,12 @@ impl CompletionsMenu { !self.matches.is_empty() } - fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { + fn render( + &self, + style: EditorStyle, + workspace: Option>, + cx: &mut ViewContext, + ) -> AnyElement { enum CompletionTag {} let widest_completion_ix = self @@ -1278,7 +1292,7 @@ impl CompletionsMenu { Flex::column() .scrollable::(0, None, cx) .with_child(render_parsed_markdown::( - parsed, &style, cx, + parsed, &style, workspace, cx, )) .contained() .with_style(style.autocomplete.alongside_docs_container) @@ -3140,6 +3154,7 @@ impl Editor { false }); } + fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option { let offset = position.to_offset(buffer); let (word_range, kind) = buffer.surrounding_word(offset); @@ -4215,9 +4230,14 @@ impl Editor { style: EditorStyle, cx: &mut ViewContext, ) -> Option<(DisplayPoint, AnyElement)> { - self.context_menu - .as_ref() - .map(|menu| menu.render(cursor_position, style, cx)) + self.context_menu.as_ref().map(|menu| { + menu.render( + cursor_position, + style, + self.workspace.as_ref().map(|(w, _)| w.clone()), + cx, + ) + }) } fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 924d66c21c..316e143413 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2439,9 +2439,13 @@ impl Element for EditorElement { } let visible_rows = start_row..start_row + line_layouts.len() as u32; - let mut hover = editor - .hover_state - .render(&snapshot, &style, visible_rows, cx); + let mut hover = editor.hover_state.render( + &snapshot, + &style, + visible_rows, + editor.workspace.as_ref().map(|(w, _)| w.clone()), + cx, + ); let mode = editor.mode; let mut fold_indicators = editor.render_fold_indicators( diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index e8901ad6c1..00a307df68 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -9,7 +9,7 @@ use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, + AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, WeakViewHandle, }; use language::{ markdown, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, ParsedMarkdown, @@ -17,6 +17,7 @@ use language::{ use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; +use workspace::Workspace; pub const HOVER_DELAY_MILLIS: u64 = 350; pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; @@ -422,6 +423,7 @@ impl HoverState { snapshot: &EditorSnapshot, style: &EditorStyle, visible_rows: Range, + workspace: Option>, cx: &mut ViewContext, ) -> Option<(DisplayPoint, Vec>)> { // If there is a diagnostic, position the popovers based on that. @@ -451,7 +453,7 @@ impl HoverState { elements.push(diagnostic_popover.render(style, cx)); } if let Some(info_popover) = self.info_popover.as_mut() { - elements.push(info_popover.render(style, cx)); + elements.push(info_popover.render(style, workspace, cx)); } Some((point, elements)) @@ -470,6 +472,7 @@ impl InfoPopover { pub fn render( &mut self, style: &EditorStyle, + workspace: Option>, cx: &mut ViewContext, ) -> AnyElement { MouseEventHandler::new::(0, cx, |_, cx| { @@ -478,6 +481,7 @@ impl InfoPopover { .with_child(crate::render_parsed_markdown::( &self.parsed_content, style, + workspace, cx, )) .contained() diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index 8be15e81f6..7f57eba309 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -1,5 +1,5 @@ -use std::ops::Range; use std::sync::Arc; +use std::{ops::Range, path::PathBuf}; use crate::{HighlightId, Language, LanguageRegistry}; use gpui::fonts::{self, HighlightStyle, Weight}; @@ -58,7 +58,28 @@ pub struct MarkdownHighlightStyle { #[derive(Debug, Clone)] pub struct ParsedRegion { pub code: bool, - pub link_url: Option, + pub link: Option, +} + +#[derive(Debug, Clone)] +pub enum Link { + Web { url: String }, + Path { path: PathBuf }, +} + +impl Link { + fn identify(text: String) -> Option { + if text.starts_with("http") { + return Some(Link::Web { url: text }); + } + + let path = PathBuf::from(text); + if path.is_absolute() { + return Some(Link::Path { path }); + } + + None + } } pub async fn parse_markdown( @@ -115,17 +136,20 @@ pub async fn parse_markdown_block( text.push_str(t.as_ref()); let mut style = MarkdownHighlightStyle::default(); + if bold_depth > 0 { style.weight = Weight::BOLD; } + if italic_depth > 0 { style.italic = true; } - if let Some(link_url) = link_url.clone() { + + if let Some(link) = link_url.clone().and_then(|u| Link::identify(u)) { region_ranges.push(prev_len..text.len()); regions.push(ParsedRegion { - link_url: Some(link_url), code: false, + link: Some(link), }); style.underline = true; } @@ -151,7 +175,9 @@ pub async fn parse_markdown_block( Event::Code(t) => { text.push_str(t.as_ref()); region_ranges.push(prev_len..text.len()); - if link_url.is_some() { + + let link = link_url.clone().and_then(|u| Link::identify(u)); + if link.is_some() { highlights.push(( prev_len..text.len(), MarkdownHighlight::Style(MarkdownHighlightStyle { @@ -160,10 +186,7 @@ pub async fn parse_markdown_block( }), )); } - regions.push(ParsedRegion { - code: true, - link_url: link_url.clone(), - }); + regions.push(ParsedRegion { code: true, link }); } Event::Start(tag) => match tag { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index cd939b5604..5a13efd07a 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -150,11 +150,14 @@ impl TerminalView { cx.notify(); cx.emit(Event::Wakeup); } + Event::Bell => { this.has_bell = true; cx.emit(Event::Wakeup); } + Event::BlinkChanged => this.blinking_on = !this.blinking_on, + Event::TitleChanged => { if let Some(foreground_info) = &this.terminal().read(cx).foreground_process_info { let cwd = foreground_info.cwd.clone(); @@ -171,6 +174,7 @@ impl TerminalView { .detach(); } } + Event::NewNavigationTarget(maybe_navigation_target) => { this.can_navigate_to_selected_word = match maybe_navigation_target { Some(MaybeNavigationTarget::Url(_)) => true, @@ -180,8 +184,10 @@ impl TerminalView { None => false, } } + Event::Open(maybe_navigation_target) => match maybe_navigation_target { MaybeNavigationTarget::Url(url) => cx.platform().open_url(url), + MaybeNavigationTarget::PathLike(maybe_path) => { if !this.can_navigate_to_selected_word { return; @@ -246,6 +252,7 @@ impl TerminalView { } } }, + _ => cx.emit(event.clone()), }) .detach(); From 85332eacbd861847582526cec0642f2d76e88944 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 13:23:26 -0400 Subject: [PATCH 75/92] Race completion filter w/completion request & make not block UI --- crates/editor/src/editor.rs | 107 +++++++++++++++++++----------- crates/editor/src/editor_tests.rs | 14 ++-- crates/editor/src/element.rs | 2 +- 3 files changed, 76 insertions(+), 47 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0748a0fcf4..d7ef82da36 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -656,7 +656,7 @@ pub struct Editor { background_highlights: BTreeMap, inlay_background_highlights: TreeMap, InlayBackgroundHighlight>, nav_history: Option, - context_menu: Option, + context_menu: RwLock>, mouse_context_menu: ViewHandle, completion_tasks: Vec<(CompletionId, Task>)>, next_completion_id: CompletionId, @@ -934,12 +934,13 @@ impl ContextMenu { } } +#[derive(Clone)] struct CompletionsMenu { id: CompletionId, initial_position: Anchor, buffer: ModelHandle, completions: Arc>>, - match_candidates: Vec, + match_candidates: Arc<[StringMatchCandidate]>, matches: Arc<[StringMatch]>, selected_item: usize, list: UniformListState, @@ -1333,13 +1334,13 @@ impl CompletionsMenu { .collect() }; - //Remove all candidates where the query's start does not match the start of any word in the candidate + // Remove all candidates where the query's start does not match the start of any word in the candidate if let Some(query) = query { if let Some(query_start) = query.chars().next() { matches.retain(|string_match| { split_words(&string_match.string).any(|word| { - //Check that the first codepoint of the word as lowercase matches the first - //codepoint of the query as lowercase + // Check that the first codepoint of the word as lowercase matches the first + // codepoint of the query as lowercase word.chars() .flat_map(|codepoint| codepoint.to_lowercase()) .zip(query_start.to_lowercase()) @@ -1805,7 +1806,7 @@ impl Editor { background_highlights: Default::default(), inlay_background_highlights: Default::default(), nav_history: None, - context_menu: None, + context_menu: RwLock::new(None), mouse_context_menu: cx .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)), completion_tasks: Default::default(), @@ -2100,10 +2101,12 @@ impl Editor { if local { let new_cursor_position = self.selections.newest_anchor().head(); - let completion_menu = match self.context_menu.as_mut() { + let mut context_menu = self.context_menu.write(); + let completion_menu = match context_menu.as_ref() { Some(ContextMenu::Completions(menu)) => Some(menu), + _ => { - self.context_menu.take(); + *context_menu = None; None } }; @@ -2115,13 +2118,39 @@ impl Editor { if kind == Some(CharKind::Word) && word_range.to_inclusive().contains(&cursor_position) { + let mut completion_menu = completion_menu.clone(); + drop(context_menu); + let query = Self::completion_query(buffer, cursor_position); - cx.background() - .block(completion_menu.filter(query.as_deref(), cx.background().clone())); + cx.spawn(move |this, mut cx| async move { + completion_menu + .filter(query.as_deref(), cx.background().clone()) + .await; + + this.update(&mut cx, |this, cx| { + let mut context_menu = this.context_menu.write(); + let Some(ContextMenu::Completions(menu)) = context_menu.as_ref() else { + return; + }; + + if menu.id > completion_menu.id { + return; + } + + *context_menu = Some(ContextMenu::Completions(completion_menu)); + drop(context_menu); + cx.notify(); + }) + }) + .detach(); + self.show_completions(&ShowCompletions, cx); } else { + drop(context_menu); self.hide_context_menu(cx); } + } else { + drop(context_menu); } hide_hover(self, cx); @@ -3432,23 +3461,31 @@ impl Editor { this.update(&mut cx, |this, cx| { this.completion_tasks.retain(|(task_id, _)| *task_id > id); - match this.context_menu.as_ref() { + let mut context_menu = this.context_menu.write(); + match context_menu.as_ref() { None => {} + Some(ContextMenu::Completions(prev_menu)) => { if prev_menu.id > id { return; } } + _ => return, } if this.focused && menu.is_some() { let menu = menu.unwrap(); - this.show_context_menu(ContextMenu::Completions(menu), cx); + *context_menu = Some(ContextMenu::Completions(menu)); + drop(context_menu); + this.completion_tasks.clear(); + this.discard_copilot_suggestion(cx); + cx.notify(); } else if this.completion_tasks.is_empty() { // If there are no more completion tasks and the last menu was // empty, we should hide it. If it was already hidden, we should // also show the copilot suggestion when available. + drop(context_menu); if this.hide_context_menu(cx).is_none() { this.update_visible_copilot_suggestion(cx); } @@ -3593,14 +3630,13 @@ impl Editor { } pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext) { - if matches!( - self.context_menu.as_ref(), - Some(ContextMenu::CodeActions(_)) - ) { - self.context_menu.take(); + let mut context_menu = self.context_menu.write(); + if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) { + *context_menu = None; cx.notify(); return; } + drop(context_menu); let deployed_from_indicator = action.deployed_from_indicator; let mut task = self.code_actions_task.take(); @@ -3613,16 +3649,16 @@ impl Editor { this.update(&mut cx, |this, cx| { if this.focused { if let Some((buffer, actions)) = this.available_code_actions.clone() { - this.show_context_menu( - ContextMenu::CodeActions(CodeActionsMenu { + this.completion_tasks.clear(); + this.discard_copilot_suggestion(cx); + *this.context_menu.write() = + Some(ContextMenu::CodeActions(CodeActionsMenu { buffer, actions, selected_item: Default::default(), list: Default::default(), deployed_from_indicator, - }), - cx, - ); + })); } } })?; @@ -4086,7 +4122,7 @@ impl Editor { let selection = self.selections.newest_anchor(); let cursor = selection.head(); - if self.context_menu.is_some() + if self.context_menu.read().is_some() || !self.completion_tasks.is_empty() || selection.start != selection.end { @@ -4220,6 +4256,7 @@ impl Editor { pub fn context_menu_visible(&self) -> bool { self.context_menu + .read() .as_ref() .map_or(false, |menu| menu.visible()) } @@ -4230,7 +4267,7 @@ impl Editor { style: EditorStyle, cx: &mut ViewContext, ) -> Option<(DisplayPoint, AnyElement)> { - self.context_menu.as_ref().map(|menu| { + self.context_menu.read().as_ref().map(|menu| { menu.render( cursor_position, style, @@ -4240,19 +4277,10 @@ impl Editor { }) } - fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { - if !matches!(menu, ContextMenu::Completions(_)) { - self.completion_tasks.clear(); - } - self.context_menu = Some(menu); - self.discard_copilot_suggestion(cx); - cx.notify(); - } - fn hide_context_menu(&mut self, cx: &mut ViewContext) -> Option { cx.notify(); self.completion_tasks.clear(); - let context_menu = self.context_menu.take(); + let context_menu = self.context_menu.write().take(); if context_menu.is_some() { self.update_visible_copilot_suggestion(cx); } @@ -5604,6 +5632,7 @@ impl Editor { if self .context_menu + .write() .as_mut() .map(|menu| menu.select_last(self.project.as_ref(), cx)) .unwrap_or(false) @@ -5648,25 +5677,25 @@ impl Editor { } pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_first(self.project.as_ref(), cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_prev(self.project.as_ref(), cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_next(self.project.as_ref(), cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_last(self.project.as_ref(), cx); } } @@ -9164,7 +9193,7 @@ impl View for Editor { keymap.add_identifier("renaming"); } if self.context_menu_visible() { - match self.context_menu.as_ref() { + match self.context_menu.read().as_ref() { Some(ContextMenu::Completions(_)) => { keymap.add_identifier("menu"); keymap.add_identifier("showing_completions") diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index dee27e0121..4be29ea084 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5430,9 +5430,9 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { additional edit "}); cx.simulate_keystroke(" "); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.simulate_keystroke("s"); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.assert_editor_state(indoc! {" one.second_completion @@ -5494,12 +5494,12 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { }); cx.set_state("editorˇ"); cx.simulate_keystroke("."); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.simulate_keystroke("c"); cx.simulate_keystroke("l"); cx.simulate_keystroke("o"); cx.assert_editor_state("editor.cloˇ"); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.update_editor(|editor, cx| { editor.show_completions(&ShowCompletions, cx); }); @@ -7788,7 +7788,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: cx.simulate_keystroke("-"); cx.foreground().run_until_parked(); cx.update_editor(|editor, _| { - if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { assert_eq!( menu.matches.iter().map(|m| &m.string).collect::>(), &["bg-red", "bg-blue", "bg-yellow"] @@ -7801,7 +7801,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: cx.simulate_keystroke("l"); cx.foreground().run_until_parked(); cx.update_editor(|editor, _| { - if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { assert_eq!( menu.matches.iter().map(|m| &m.string).collect::>(), &["bg-blue", "bg-yellow"] @@ -7817,7 +7817,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: cx.simulate_keystroke("l"); cx.foreground().run_until_parked(); cx.update_editor(|editor, _| { - if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { assert_eq!( menu.matches.iter().map(|m| &m.string).collect::>(), &["bg-yellow"] diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 316e143413..00c8508b6c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2428,7 +2428,7 @@ impl Element for EditorElement { } let active = matches!( - editor.context_menu, + editor.context_menu.read().as_ref(), Some(crate::ContextMenu::CodeActions(_)) ); From 2e5461ee4de8f296c116e00a0c70efaacd376227 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Oct 2023 11:55:39 -0700 Subject: [PATCH 76/92] Exclude disconnected channel views from following messages --- crates/collab_ui/src/channel_view.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index b2e65eb2fa..817e9fcb4e 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -285,10 +285,14 @@ impl FollowableItem for ChannelView { } fn to_state_proto(&self, cx: &AppContext) -> Option { - let channel = self.channel_buffer.read(cx).channel(); + let channel_buffer = self.channel_buffer.read(cx); + if !channel_buffer.is_connected() { + return None; + } + Some(proto::view::Variant::ChannelView( proto::view::ChannelView { - channel_id: channel.id, + channel_id: channel_buffer.channel().id, editor: if let Some(proto::view::Variant::Editor(proto)) = self.editor.read(cx).to_state_proto(cx) { From 85fe11ff117f076ae5dc92a7a99607d14e0d6e7e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Oct 2023 12:38:23 -0700 Subject: [PATCH 77/92] Replace disconnected channel notes views when re-opening the notes --- crates/channel/src/channel_buffer.rs | 4 +++ crates/collab_ui/src/channel_view.rs | 41 +++++++++++++++++++++------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/channel/src/channel_buffer.rs b/crates/channel/src/channel_buffer.rs index 7de8b956f1..ab7ea78ac1 100644 --- a/crates/channel/src/channel_buffer.rs +++ b/crates/channel/src/channel_buffer.rs @@ -99,6 +99,10 @@ impl ChannelBuffer { })) } + pub fn remote_id(&self, cx: &AppContext) -> u64 { + self.buffer.read(cx).remote_id() + } + pub fn user_store(&self) -> &ModelHandle { &self.user_store } diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 817e9fcb4e..e62ee8ef4b 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -24,7 +24,7 @@ use workspace::{ item::{FollowableItem, Item, ItemHandle}, register_followable_item, searchable::SearchableItemHandle, - ItemNavHistory, Pane, ViewId, Workspace, WorkspaceId, + ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId, }; actions!(channel_view, [Deploy]); @@ -93,15 +93,36 @@ impl ChannelView { } pane.update(&mut cx, |pane, cx| { - pane.items_of_type::() - .find(|channel_view| channel_view.read(cx).channel_buffer == channel_buffer) - .unwrap_or_else(|| { - cx.add_view(|cx| { - let mut this = Self::new(project, channel_store, channel_buffer, cx); - this.acknowledge_buffer_version(cx); - this - }) - }) + let buffer_id = channel_buffer.read(cx).remote_id(cx); + + let existing_view = pane + .items_of_type::() + .find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id); + + // If this channel buffer is already open in this pane, just return it. + if let Some(existing_view) = existing_view.clone() { + if existing_view.read(cx).channel_buffer == channel_buffer { + return existing_view; + } + } + + let view = cx.add_view(|cx| { + let mut this = Self::new(project, channel_store, channel_buffer, cx); + this.acknowledge_buffer_version(cx); + this + }); + + // If the pane contained a disconnected view for this channel buffer, + // replace that. + if let Some(existing_item) = existing_view { + if let Some(ix) = pane.index_for_item(&existing_item) { + pane.close_item_by_id(existing_item.id(), SaveIntent::Skip, cx) + .detach(); + pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx); + } + } + + view }) .ok_or_else(|| anyhow!("pane was dropped")) }) From f5d6d7caca2ce1d9f2bf4db75bd8cc461c5567f2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Oct 2023 12:39:02 -0700 Subject: [PATCH 78/92] Mark channel notes as disconnected immediately upon explicitly signing out --- crates/channel/src/channel_store.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 2a2fa454f2..bceb2c094d 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -114,12 +114,21 @@ impl ChannelStore { let watch_connection_status = cx.spawn_weak(|this, mut cx| async move { while let Some(status) = connection_status.next().await { let this = this.upgrade(&cx)?; + match status { + client::Status::Connected { .. } => { + this.update(&mut cx, |this, cx| this.handle_connect(cx)) + .await + .log_err()?; + } + client::Status::SignedOut | client::Status::UpgradeRequired => { + this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx)); + } + _ => { + this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx)); + } + } if status.is_connected() { - this.update(&mut cx, |this, cx| this.handle_connect(cx)) - .await - .log_err()?; } else { - this.update(&mut cx, |this, cx| this.handle_disconnect(cx)); } } Some(()) @@ -823,7 +832,7 @@ impl ChannelStore { }) } - fn handle_disconnect(&mut self, cx: &mut ModelContext) { + fn handle_disconnect(&mut self, wait_for_reconnect: bool, cx: &mut ModelContext) { self.channel_index.clear(); self.channel_invitations.clear(); self.channel_participants.clear(); @@ -834,7 +843,10 @@ impl ChannelStore { self.disconnect_channel_buffers_task.get_or_insert_with(|| { cx.spawn_weak(|this, mut cx| async move { - cx.background().timer(RECONNECT_TIMEOUT).await; + if wait_for_reconnect { + cx.background().timer(RECONNECT_TIMEOUT).await; + } + if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { for (_, buffer) in this.opened_buffers.drain() { From d23bb3b05da84c29ce9626f4d7a68461f2e19c93 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 16:18:54 -0400 Subject: [PATCH 79/92] Unbork markdown parse test by making links match --- crates/editor/src/hover_popover.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 00a307df68..5b3985edf9 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -911,7 +911,7 @@ mod tests { // Links Row { blocks: vec![HoverBlock { - text: "one [two](the-url) three".to_string(), + text: "one [two](https://the-url) three".to_string(), kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), @@ -932,7 +932,7 @@ mod tests { - a - b * two - - [c](the-url) + - [c](https://the-url) - d" .unindent(), kind: HoverBlockKind::Markdown, From 45f3a9835972b42763bc95d3759703d2f4f5f379 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 12 Oct 2023 17:40:20 -0400 Subject: [PATCH 80/92] Remove old `ui` and `storybook` crates (#3125) This PR deletes the old `ui` and `storybook` crates in favor of their newer variants that we'll be landing to `main` in the near future. ### Motivation These crates are based off the old version of GPUI 2 (the `gpui2` crate). At this point we have since transitioned to the new version of GPUI 2 (the `gpui3` crate, currently still on the `gpui2` branch). Having both copies around is confusing, so the old ones are going the way of the dinosaurs. Release Notes: - N/A --- Cargo.lock | 59 - Cargo.toml | 2 - crates/storybook/Cargo.lock | 2919 ----------------- crates/storybook/Cargo.toml | 30 - crates/storybook/docs/thoughts.md | 72 - crates/storybook/src/stories.rs | 3 - crates/storybook/src/stories/components.rs | 22 - .../src/stories/components/assistant_panel.rs | 16 - .../src/stories/components/breadcrumb.rs | 45 - .../src/stories/components/buffer.rs | 36 - .../src/stories/components/chat_panel.rs | 46 - .../src/stories/components/collab_panel.rs | 16 - .../src/stories/components/context_menu.rs | 21 - .../src/stories/components/facepile.rs | 25 - .../src/stories/components/keybinding.rs | 64 - .../stories/components/language_selector.rs | 16 - .../src/stories/components/multi_buffer.rs | 24 - .../src/stories/components/palette.rs | 53 - .../storybook/src/stories/components/panel.rs | 25 - .../src/stories/components/project_panel.rs | 20 - .../src/stories/components/recent_projects.rs | 16 - .../src/stories/components/status_bar.rs | 16 - .../storybook/src/stories/components/tab.rs | 91 - .../src/stories/components/tab_bar.rs | 46 - .../src/stories/components/terminal.rs | 16 - .../src/stories/components/theme_selector.rs | 16 - .../src/stories/components/title_bar.rs | 16 - .../src/stories/components/toolbar.rs | 70 - .../src/stories/components/traffic_lights.rs | 18 - crates/storybook/src/stories/elements.rs | 5 - .../storybook/src/stories/elements/avatar.rs | 23 - .../storybook/src/stories/elements/button.rs | 192 -- crates/storybook/src/stories/elements/icon.rs | 19 - .../storybook/src/stories/elements/input.rs | 16 - .../storybook/src/stories/elements/label.rs | 18 - crates/storybook/src/stories/kitchen_sink.rs | 26 - crates/storybook/src/story.rs | 44 - crates/storybook/src/story_selector.rs | 178 - crates/storybook/src/storybook.rs | 198 -- crates/ui/Cargo.toml | 16 - crates/ui/docs/_project.md | 13 - crates/ui/docs/elevation.md | 57 - crates/ui/src/children.rs | 7 - crates/ui/src/components.rs | 163 - crates/ui/src/components/assistant_panel.rs | 91 - crates/ui/src/components/breadcrumb.rs | 71 - crates/ui/src/components/buffer.rs | 233 -- crates/ui/src/components/chat_panel.rs | 108 - crates/ui/src/components/collab_panel.rs | 161 - crates/ui/src/components/command_palette.rs | 29 - crates/ui/src/components/context_menu.rs | 65 - crates/ui/src/components/editor_pane.rs | 60 - crates/ui/src/components/facepile.rs | 28 - crates/ui/src/components/icon_button.rs | 67 - crates/ui/src/components/keybinding.rs | 158 - crates/ui/src/components/language_selector.rs | 36 - crates/ui/src/components/list.rs | 512 --- crates/ui/src/components/multi_buffer.rs | 42 - crates/ui/src/components/palette.rs | 152 - crates/ui/src/components/panel.rs | 142 - crates/ui/src/components/panes.rs | 132 - crates/ui/src/components/player_stack.rs | 65 - crates/ui/src/components/project_panel.rs | 58 - crates/ui/src/components/recent_projects.rs | 32 - crates/ui/src/components/status_bar.rs | 144 - crates/ui/src/components/tab.rs | 131 - crates/ui/src/components/tab_bar.rs | 85 - crates/ui/src/components/terminal.rs | 84 - crates/ui/src/components/theme_selector.rs | 37 - crates/ui/src/components/title_bar.rs | 117 - crates/ui/src/components/toast.rs | 66 - crates/ui/src/components/toolbar.rs | 49 - crates/ui/src/components/traffic_lights.rs | 78 - crates/ui/src/components/workspace.rs | 186 -- crates/ui/src/element_ext.rs | 24 - crates/ui/src/elements.rs | 19 - crates/ui/src/elements/avatar.rs | 41 - crates/ui/src/elements/button.rs | 203 -- crates/ui/src/elements/details.rs | 33 - crates/ui/src/elements/icon.rs | 185 -- crates/ui/src/elements/input.rs | 106 - crates/ui/src/elements/label.rs | 161 - crates/ui/src/elements/player.rs | 133 - crates/ui/src/elements/stack.rs | 31 - crates/ui/src/elements/tool_divider.rs | 17 - crates/ui/src/lib.rs | 20 - crates/ui/src/prelude.rs | 274 -- crates/ui/src/static_data.rs | 966 ------ crates/ui/src/theme.rs | 196 -- crates/ui/src/tokens.rs | 25 - crates/ui/tracker.md | 133 - 91 files changed, 10580 deletions(-) delete mode 100644 crates/storybook/Cargo.lock delete mode 100644 crates/storybook/Cargo.toml delete mode 100644 crates/storybook/docs/thoughts.md delete mode 100644 crates/storybook/src/stories.rs delete mode 100644 crates/storybook/src/stories/components.rs delete mode 100644 crates/storybook/src/stories/components/assistant_panel.rs delete mode 100644 crates/storybook/src/stories/components/breadcrumb.rs delete mode 100644 crates/storybook/src/stories/components/buffer.rs delete mode 100644 crates/storybook/src/stories/components/chat_panel.rs delete mode 100644 crates/storybook/src/stories/components/collab_panel.rs delete mode 100644 crates/storybook/src/stories/components/context_menu.rs delete mode 100644 crates/storybook/src/stories/components/facepile.rs delete mode 100644 crates/storybook/src/stories/components/keybinding.rs delete mode 100644 crates/storybook/src/stories/components/language_selector.rs delete mode 100644 crates/storybook/src/stories/components/multi_buffer.rs delete mode 100644 crates/storybook/src/stories/components/palette.rs delete mode 100644 crates/storybook/src/stories/components/panel.rs delete mode 100644 crates/storybook/src/stories/components/project_panel.rs delete mode 100644 crates/storybook/src/stories/components/recent_projects.rs delete mode 100644 crates/storybook/src/stories/components/status_bar.rs delete mode 100644 crates/storybook/src/stories/components/tab.rs delete mode 100644 crates/storybook/src/stories/components/tab_bar.rs delete mode 100644 crates/storybook/src/stories/components/terminal.rs delete mode 100644 crates/storybook/src/stories/components/theme_selector.rs delete mode 100644 crates/storybook/src/stories/components/title_bar.rs delete mode 100644 crates/storybook/src/stories/components/toolbar.rs delete mode 100644 crates/storybook/src/stories/components/traffic_lights.rs delete mode 100644 crates/storybook/src/stories/elements.rs delete mode 100644 crates/storybook/src/stories/elements/avatar.rs delete mode 100644 crates/storybook/src/stories/elements/button.rs delete mode 100644 crates/storybook/src/stories/elements/icon.rs delete mode 100644 crates/storybook/src/stories/elements/input.rs delete mode 100644 crates/storybook/src/stories/elements/label.rs delete mode 100644 crates/storybook/src/stories/kitchen_sink.rs delete mode 100644 crates/storybook/src/story.rs delete mode 100644 crates/storybook/src/story_selector.rs delete mode 100644 crates/storybook/src/storybook.rs delete mode 100644 crates/ui/Cargo.toml delete mode 100644 crates/ui/docs/_project.md delete mode 100644 crates/ui/docs/elevation.md delete mode 100644 crates/ui/src/children.rs delete mode 100644 crates/ui/src/components.rs delete mode 100644 crates/ui/src/components/assistant_panel.rs delete mode 100644 crates/ui/src/components/breadcrumb.rs delete mode 100644 crates/ui/src/components/buffer.rs delete mode 100644 crates/ui/src/components/chat_panel.rs delete mode 100644 crates/ui/src/components/collab_panel.rs delete mode 100644 crates/ui/src/components/command_palette.rs delete mode 100644 crates/ui/src/components/context_menu.rs delete mode 100644 crates/ui/src/components/editor_pane.rs delete mode 100644 crates/ui/src/components/facepile.rs delete mode 100644 crates/ui/src/components/icon_button.rs delete mode 100644 crates/ui/src/components/keybinding.rs delete mode 100644 crates/ui/src/components/language_selector.rs delete mode 100644 crates/ui/src/components/list.rs delete mode 100644 crates/ui/src/components/multi_buffer.rs delete mode 100644 crates/ui/src/components/palette.rs delete mode 100644 crates/ui/src/components/panel.rs delete mode 100644 crates/ui/src/components/panes.rs delete mode 100644 crates/ui/src/components/player_stack.rs delete mode 100644 crates/ui/src/components/project_panel.rs delete mode 100644 crates/ui/src/components/recent_projects.rs delete mode 100644 crates/ui/src/components/status_bar.rs delete mode 100644 crates/ui/src/components/tab.rs delete mode 100644 crates/ui/src/components/tab_bar.rs delete mode 100644 crates/ui/src/components/terminal.rs delete mode 100644 crates/ui/src/components/theme_selector.rs delete mode 100644 crates/ui/src/components/title_bar.rs delete mode 100644 crates/ui/src/components/toast.rs delete mode 100644 crates/ui/src/components/toolbar.rs delete mode 100644 crates/ui/src/components/traffic_lights.rs delete mode 100644 crates/ui/src/components/workspace.rs delete mode 100644 crates/ui/src/element_ext.rs delete mode 100644 crates/ui/src/elements.rs delete mode 100644 crates/ui/src/elements/avatar.rs delete mode 100644 crates/ui/src/elements/button.rs delete mode 100644 crates/ui/src/elements/details.rs delete mode 100644 crates/ui/src/elements/icon.rs delete mode 100644 crates/ui/src/elements/input.rs delete mode 100644 crates/ui/src/elements/label.rs delete mode 100644 crates/ui/src/elements/player.rs delete mode 100644 crates/ui/src/elements/stack.rs delete mode 100644 crates/ui/src/elements/tool_divider.rs delete mode 100644 crates/ui/src/lib.rs delete mode 100644 crates/ui/src/prelude.rs delete mode 100644 crates/ui/src/static_data.rs delete mode 100644 crates/ui/src/theme.rs delete mode 100644 crates/ui/src/tokens.rs delete mode 100644 crates/ui/tracker.md diff --git a/Cargo.lock b/Cargo.lock index f95db3d354..01153ca0f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6623,12 +6623,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - [[package]] name = "rustybuzz" version = "0.3.0" @@ -7682,28 +7676,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "storybook" -version = "0.1.0" -dependencies = [ - "anyhow", - "chrono", - "clap 4.4.4", - "fs", - "futures 0.3.28", - "gpui2", - "itertools 0.11.0", - "log", - "rust-embed", - "serde", - "settings", - "simplelog", - "strum", - "theme", - "ui", - "util", -] - [[package]] name = "stringprep" version = "0.1.4" @@ -7726,22 +7698,6 @@ name = "strum" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.37", -] [[package]] name = "subtle" @@ -8908,21 +8864,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ui" -version = "0.1.0" -dependencies = [ - "anyhow", - "chrono", - "gpui2", - "rand 0.8.5", - "serde", - "settings", - "smallvec", - "strum", - "theme", -] - [[package]] name = "unicase" version = "2.7.0" diff --git a/Cargo.toml b/Cargo.toml index 25aec39cdd..532610efd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,13 +66,11 @@ members = [ "crates/sqlez_macros", "crates/feature_flags", "crates/rich_text", - "crates/storybook", "crates/sum_tree", "crates/terminal", "crates/text", "crates/theme", "crates/theme_selector", - "crates/ui", "crates/util", "crates/semantic_index", "crates/vim", diff --git a/crates/storybook/Cargo.lock b/crates/storybook/Cargo.lock deleted file mode 100644 index b652dbbd13..0000000000 --- a/crates/storybook/Cargo.lock +++ /dev/null @@ -1,2919 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - -[[package]] -name = "aho-corasick" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" -dependencies = [ - "memchr", -] - -[[package]] -name = "anyhow" -version = "1.0.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" -dependencies = [ - "async-lock", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock", - "autocfg", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite", - "log", - "parking", - "polling", - "rustix", - "slab", - "socket2", - "waker-fn", -] - -[[package]] -name = "async-lock" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-net" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f" -dependencies = [ - "async-io", - "autocfg", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-process" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" -dependencies = [ - "async-io", - "async-lock", - "autocfg", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", - "signal-hook", - "windows-sys", -] - -[[package]] -name = "async-task" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" - -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic-waker" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide 0.7.1", - "object", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "bindgen" -version = "0.65.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.25", - "which", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blocking" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" -dependencies = [ - "async-channel", - "async-lock", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "log", -] - -[[package]] -name = "bstr" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" - -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clang-sys" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" -dependencies = [ - "glob", - "libc", - "libloading 0.7.4", -] - -[[package]] -name = "cmake" -version = "0.1.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" -dependencies = [ - "cc", -] - -[[package]] -name = "cocoa" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" -dependencies = [ - "bitflags", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" -dependencies = [ - "bitflags", - "block", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "collections" -version = "0.1.0" - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "concurrent-queue" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-cstr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" - -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", - "uuid 0.5.1", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" -dependencies = [ - "bitflags", - "core-foundation", - "libc", -] - -[[package]] -name = "core-text" -version = "19.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" -dependencies = [ - "core-foundation", - "core-graphics", - "foreign-types", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "curl" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi", -] - -[[package]] -name = "curl-sys" -version = "0.4.63+curl-8.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi", -] - -[[package]] -name = "data-url" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" -dependencies = [ - "matches", -] - -[[package]] -name = "deflate" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" -dependencies = [ - "adler32", - "byteorder", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading 0.8.0", -] - -[[package]] -name = "dwrote" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" -dependencies = [ - "lazy_static", - "libc", - "winapi", - "wio", -] - -[[package]] -name = "dyn-clone" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "erased-serde" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f94c0e13118e7d7533271f754a168ae8400e6a1cc043f2bfd53cc7290f1a1de3" -dependencies = [ - "serde", -] - -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "etagere" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf22f748754352918e082e0039335ee92454a5d62bcaf69b5e8daf5907d9644" -dependencies = [ - "euclid", - "svg_fmt", -] - -[[package]] -name = "euclid" -version = "0.22.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" -dependencies = [ - "num-traits", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "flate2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" -dependencies = [ - "crc32fast", - "miniz_oxide 0.7.1", -] - -[[package]] -name = "float-cmp" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" - -[[package]] -name = "float-ord" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "font-kit" -version = "0.11.0" -source = "git+https://github.com/zed-industries/font-kit?rev=b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18#b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" -dependencies = [ - "bitflags", - "byteorder", - "core-foundation", - "core-graphics", - "core-text", - "dirs-next", - "dwrote", - "float-ord", - "freetype", - "lazy_static", - "libc", - "log", - "pathfinder_geometry", - "pathfinder_simd", - "walkdir", - "winapi", - "yeslogic-fontconfig-sys", -] - -[[package]] -name = "fontdb" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58903f4f8d5b58c7d300908e4ebe5289c1bfdf5587964330f12023b8ff17fd1" -dependencies = [ - "log", - "memmap2", - "ttf-parser 0.12.3", -] - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "freetype" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6" -dependencies = [ - "freetype-sys", - "libc", -] - -[[package]] -name = "freetype-sys" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" -dependencies = [ - "cmake", - "libc", - "pkg-config", -] - -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-executor" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gif" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "gimli" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "globset" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - -[[package]] -name = "gpui" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-task", - "bindgen", - "block", - "cc", - "cocoa", - "collections", - "core-foundation", - "core-graphics", - "core-text", - "ctor", - "etagere", - "font-kit", - "foreign-types", - "futures", - "gpui_macros", - "image", - "itertools", - "lazy_static", - "log", - "media", - "metal", - "num_cpus", - "objc", - "ordered-float", - "parking", - "parking_lot 0.11.2", - "pathfinder_color", - "pathfinder_geometry", - "postage", - "rand", - "resvg", - "schemars", - "seahash", - "serde", - "serde_derive", - "serde_json", - "smallvec", - "smol", - "sqlez", - "sum_tree", - "time", - "tiny-skia", - "usvg", - "util", - "uuid 1.4.0", - "waker-fn", -] - -[[package]] -name = "gpui_macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - -[[package]] -name = "hermit-abi" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - -[[package]] -name = "http" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "image" -version = "0.23.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "gif", - "jpeg-decoder", - "num-iter", - "num-rational", - "num-traits", - "png", - "scoped_threadpool", - "tiff", -] - -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "indoc" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", -] - -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener", - "futures-lite", - "http", - "log", - "mime", - "once_cell", - "polling", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" - -[[package]] -name = "jpeg-decoder" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" -dependencies = [ - "rayon", -] - -[[package]] -name = "kurbo" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" -dependencies = [ - "arrayvec 0.7.4", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libloading" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" -dependencies = [ - "cfg-if", - "windows-sys", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "lock_api" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" -dependencies = [ - "serde", - "value-bag", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "media" -version = "0.1.0" -dependencies = [ - "anyhow", - "bindgen", - "block", - "bytes", - "core-foundation", - "foreign-types", - "metal", - "objc", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memmap2" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "metal" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4598d719460ade24c7d91f335daf055bf2a7eec030728ce751814c50cdd6a26c" -dependencies = [ - "bitflags", - "block", - "cocoa-foundation", - "foreign-types", - "log", - "objc", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", - "objc_exception", -] - -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - -[[package]] -name = "object" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-float" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" -dependencies = [ - "num-traits", -] - -[[package]] -name = "parking" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.8", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.3.5", - "smallvec", - "windows-targets", -] - -[[package]] -name = "pathfinder_color" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69bdc0d277d559e35e1b374de56df9262a6b71e091ca04a8831a239f8c7f0c62" -dependencies = [ - "pathfinder_simd", -] - -[[package]] -name = "pathfinder_geometry" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" -dependencies = [ - "log", - "pathfinder_simd", -] - -[[package]] -name = "pathfinder_simd" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - -[[package]] -name = "percent-encoding" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - -[[package]] -name = "pest" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9" -dependencies = [ - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pico-args" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" - -[[package]] -name = "pin-project" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] -name = "storybook" -version = "0.1.0" -dependencies = [ - "gpui", -] - -[[package]] -name = "png" -version = "0.16.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" -dependencies = [ - "bitflags", - "crc32fast", - "deflate", - "miniz_oxide 0.3.7", -] - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys", -] - -[[package]] -name = "pollster" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" - -[[package]] -name = "postage" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" -dependencies = [ - "atomic", - "crossbeam-queue", - "futures", - "log", - "parking_lot 0.12.1", - "pin-project", - "pollster", - "static_assertions", - "thiserror", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "prettyplease" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387" -dependencies = [ - "proc-macro2", - "syn 2.0.25", -] - -[[package]] -name = "proc-macro2" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "rctree" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9e29cb19c8fe84169fcb07f8f11e66bc9e6e0280efd4715c54818296f8a4a8" - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall 0.2.16", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" - -[[package]] -name = "resvg" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09697862c5c3f940cbaffef91969c62188b5c8ed385b0aef43a5ff01ddc8000f" -dependencies = [ - "jpeg-decoder", - "log", - "pico-args", - "png", - "rgb", - "svgfilters", - "tiny-skia", - "usvg", -] - -[[package]] -name = "rgb" -version = "0.8.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "roxmltree" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" -dependencies = [ - "xmlparser", -] - -[[package]] -name = "rust-embed" -version = "6.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "6.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 2.0.25", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "7.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" -dependencies = [ - "globset", - "sha2", - "walkdir", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc_version" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.37.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "rustybuzz" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab463a295d00f3692e0974a0bfd83c7a9bcd119e27e07c2beecdb1b44a09d10" -dependencies = [ - "bitflags", - "bytemuck", - "smallvec", - "ttf-parser 0.9.0", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-general-category", - "unicode-script", -] - -[[package]] -name = "ryu" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" - -[[package]] -name = "safe_arch" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "schemars" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 1.0.109", -] - -[[package]] -name = "scoped_threadpool" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - -[[package]] -name = "serde" -version = "1.0.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "serde_fmt" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_json" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - -[[package]] -name = "signal-hook" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "simplecss" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" -dependencies = [ - "log", -] - -[[package]] -name = "siphasher" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" - -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - -[[package]] -name = "smallvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" - -[[package]] -name = "smol" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" -dependencies = [ - "async-channel", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-net", - "async-process", - "blocking", - "futures-lite", -] - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "sqlez" -version = "0.1.0" -dependencies = [ - "anyhow", - "futures", - "indoc", - "lazy_static", - "libsqlite3-sys", - "parking_lot 0.11.2", - "smol", - "thread_local", - "uuid 1.4.0", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "sum_tree" -version = "0.1.0" -dependencies = [ - "arrayvec 0.7.4", - "log", -] - -[[package]] -name = "sval" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b031320a434d3e9477ccf9b5756d57d4272937b8d22cb88af80b7633a1b78b1" - -[[package]] -name = "sval_buffer" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf7e9412af26b342f3f2cc5cc4122b0105e9d16eb76046cd14ed10106cf6028" -dependencies = [ - "sval", - "sval_ref", -] - -[[package]] -name = "sval_dynamic" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ef628e8a77a46ed3338db8d1b08af77495123cc229453084e47cd716d403cf" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_fmt" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dc09e9364c2045ab5fa38f7b04d077b3359d30c4c2b3ec4bae67a358bd64326" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_json" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada6f627e38cbb8860283649509d87bc4a5771141daa41c78fd31f2b9485888d" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_ref" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703ca1942a984bd0d9b5a4c0a65ab8b4b794038d080af4eb303c71bc6bf22d7c" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_serde" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830926cd0581f7c3e5d51efae4d35c6b6fc4db583842652891ba2f1bed8db046" -dependencies = [ - "serde", - "sval", - "sval_buffer", - "sval_fmt", -] - -[[package]] -name = "svg_fmt" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2" - -[[package]] -name = "svgfilters" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0dce2fee79ac40c21dafba48565ff7a5fa275e23ffe9ce047a40c9574ba34e" -dependencies = [ - "float-cmp", - "rgb", -] - -[[package]] -name = "svgtypes" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff" -dependencies = [ - "float-cmp", - "siphasher", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "take-until" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" - -[[package]] -name = "thiserror" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "tiff" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" -dependencies = [ - "jpeg-decoder", - "miniz_oxide 0.4.4", - "weezl", -] - -[[package]] -name = "time" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" -dependencies = [ - "itoa", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" - -[[package]] -name = "time-macros" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" -dependencies = [ - "time-core", -] - -[[package]] -name = "tiny-skia" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf81f2900d2e235220e6f31ec9f63ade6a7f59090c556d74fe949bb3b15e9fe" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "bytemuck", - "cfg-if", - "png", - "safe_arch", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "tracing-core" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - -[[package]] -name = "ttf-parser" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ddb402ac6c2af6f7a2844243887631c4e94b51585b229fcfddb43958cd55ca" - -[[package]] -name = "ttf-parser" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - -[[package]] -name = "unicode-bidi-mirroring" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" - -[[package]] -name = "unicode-ccc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" - -[[package]] -name = "unicode-general-category" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9af028e052a610d99e066b33304625dea9613170a2563314490a4e6ec5cf7f" - -[[package]] -name = "unicode-ident" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-script" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" - -[[package]] -name = "unicode-vo" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" - -[[package]] -name = "url" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "usvg" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8352f317d8f9a918ba5154797fb2a93e2730244041cf7d5be35148266adfa5" -dependencies = [ - "base64", - "data-url", - "flate2", - "fontdb", - "kurbo", - "log", - "memmap2", - "pico-args", - "rctree", - "roxmltree", - "rustybuzz", - "simplecss", - "siphasher", - "svgtypes", - "ttf-parser 0.12.3", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", -] - -[[package]] -name = "util" -version = "0.1.0" -dependencies = [ - "anyhow", - "backtrace", - "dirs", - "futures", - "isahc", - "lazy_static", - "log", - "rand", - "rust-embed", - "serde", - "serde_json", - "smol", - "take-until", - "url", -] - -[[package]] -name = "uuid" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" - -[[package]] -name = "uuid" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" -dependencies = [ - "getrandom", -] - -[[package]] -name = "value-bag" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" -dependencies = [ - "value-bag-serde1", - "value-bag-sval2", -] - -[[package]] -name = "value-bag-serde1" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b9f3feef403a50d4d67e9741a6d8fc688bcbb4e4f31bd4aab72cc690284394" -dependencies = [ - "erased-serde", - "serde", - "serde_fmt", -] - -[[package]] -name = "value-bag-sval2" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b24f4146b6f3361e91cbf527d1fb35e9376c3c0cef72ca5ec5af6d640fad7d" -dependencies = [ - "sval", - "sval_buffer", - "sval_dynamic", - "sval_fmt", - "sval_json", - "sval_ref", - "sval_serde", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - -[[package]] -name = "walkdir" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "weezl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" - -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "wio" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" -dependencies = [ - "winapi", -] - -[[package]] -name = "xmlparser" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" - -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - -[[package]] -name = "yeslogic-fontconfig-sys" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386" -dependencies = [ - "const-cstr", - "dlib", - "once_cell", - "pkg-config", -] diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml deleted file mode 100644 index 43890dd01a..0000000000 --- a/crates/storybook/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "storybook" -version = "0.1.0" -edition = "2021" -publish = false - -[[bin]] -name = "storybook" -path = "src/storybook.rs" - -[dependencies] -anyhow.workspace = true -clap = { version = "4.4", features = ["derive", "string"] } -chrono = "0.4" -fs = { path = "../fs" } -futures.workspace = true -gpui2 = { path = "../gpui2" } -itertools = "0.11.0" -log.workspace = true -rust-embed.workspace = true -serde.workspace = true -settings = { path = "../settings" } -simplelog = "0.9" -strum = { version = "0.25.0", features = ["derive"] } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } - -[dev-dependencies] -gpui2 = { path = "../gpui2", features = ["test-support"] } diff --git a/crates/storybook/docs/thoughts.md b/crates/storybook/docs/thoughts.md deleted file mode 100644 index 9416f25933..0000000000 --- a/crates/storybook/docs/thoughts.md +++ /dev/null @@ -1,72 +0,0 @@ -Much of element styling is now handled by an external engine. - - -How do I make an element hover. - -There's a hover style. - -Hoverable needs to wrap another element. That element can be styled. - -```rs -struct Hoverable { - -} - -impl Element for Hoverable { - -} - -``` - - - -```rs -#[derive(Styled, Interactive)] -pub struct Div { - declared_style: StyleRefinement, - interactions: Interactions -} - -pub trait Styled { - fn declared_style(&mut self) -> &mut StyleRefinement; - fn compute_style(&mut self) -> Style { - Style::default().refine(self.declared_style()) - } - - // All the tailwind classes, modifying self.declared_style() -} - -impl Style { - pub fn paint_background(layout: Layout, cx: &mut PaintContext); - pub fn paint_foreground(layout: Layout, cx: &mut PaintContext); -} - -pub trait Interactive { - fn interactions(&mut self) -> &mut Interactions; - - fn on_click(self, ) -} - -struct Interactions { - click: SmallVec<[; 1]>, -} - - -``` - - -```rs - - -trait Stylable { - type Style; - - fn with_style(self, style: Self::Style) -> Self; -} - - - - - - -``` diff --git a/crates/storybook/src/stories.rs b/crates/storybook/src/stories.rs deleted file mode 100644 index 95b8844157..0000000000 --- a/crates/storybook/src/stories.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod components; -pub mod elements; -pub mod kitchen_sink; diff --git a/crates/storybook/src/stories/components.rs b/crates/storybook/src/stories/components.rs deleted file mode 100644 index 85d5ce088f..0000000000 --- a/crates/storybook/src/stories/components.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub mod assistant_panel; -pub mod breadcrumb; -pub mod buffer; -pub mod chat_panel; -pub mod collab_panel; -pub mod context_menu; -pub mod facepile; -pub mod keybinding; -pub mod language_selector; -pub mod multi_buffer; -pub mod palette; -pub mod panel; -pub mod project_panel; -pub mod recent_projects; -pub mod status_bar; -pub mod tab; -pub mod tab_bar; -pub mod terminal; -pub mod theme_selector; -pub mod title_bar; -pub mod toolbar; -pub mod traffic_lights; diff --git a/crates/storybook/src/stories/components/assistant_panel.rs b/crates/storybook/src/stories/components/assistant_panel.rs deleted file mode 100644 index 09f964756e..0000000000 --- a/crates/storybook/src/stories/components/assistant_panel.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::AssistantPanel; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct AssistantPanelStory {} - -impl AssistantPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, AssistantPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(AssistantPanel::new()) - } -} diff --git a/crates/storybook/src/stories/components/breadcrumb.rs b/crates/storybook/src/stories/components/breadcrumb.rs deleted file mode 100644 index 002b6140e1..0000000000 --- a/crates/storybook/src/stories/components/breadcrumb.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::path::PathBuf; -use std::str::FromStr; - -use ui::prelude::*; -use ui::{Breadcrumb, HighlightedText, Symbol}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct BreadcrumbStory {} - -impl BreadcrumbStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - Story::container(cx) - .child(Story::title_for::<_, Breadcrumb>(cx)) - .child(Story::label(cx, "Default")) - .child(Breadcrumb::new( - PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(), - vec![ - Symbol(vec![ - HighlightedText { - text: "impl ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "BreadcrumbStory".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "render".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - ], - )) - } -} diff --git a/crates/storybook/src/stories/components/buffer.rs b/crates/storybook/src/stories/components/buffer.rs deleted file mode 100644 index 0b3268421b..0000000000 --- a/crates/storybook/src/stories/components/buffer.rs +++ /dev/null @@ -1,36 +0,0 @@ -use gpui2::geometry::rems; -use ui::prelude::*; -use ui::{ - empty_buffer_example, hello_world_rust_buffer_example, - hello_world_rust_buffer_with_status_example, Buffer, -}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct BufferStory {} - -impl BufferStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - Story::container(cx) - .child(Story::title_for::<_, Buffer>(cx)) - .child(Story::label(cx, "Default")) - .child(div().w(rems(64.)).h_96().child(empty_buffer_example())) - .child(Story::label(cx, "Hello World (Rust)")) - .child( - div() - .w(rems(64.)) - .h_96() - .child(hello_world_rust_buffer_example(&theme)), - ) - .child(Story::label(cx, "Hello World (Rust) with Status")) - .child( - div() - .w(rems(64.)) - .h_96() - .child(hello_world_rust_buffer_with_status_example(&theme)), - ) - } -} diff --git a/crates/storybook/src/stories/components/chat_panel.rs b/crates/storybook/src/stories/components/chat_panel.rs deleted file mode 100644 index e87ac0afa2..0000000000 --- a/crates/storybook/src/stories/components/chat_panel.rs +++ /dev/null @@ -1,46 +0,0 @@ -use chrono::DateTime; -use ui::prelude::*; -use ui::{ChatMessage, ChatPanel, Panel}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ChatPanelStory {} - -impl ChatPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, ChatPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(Panel::new( - ScrollState::default(), - |_, _| vec![ChatPanel::new(ScrollState::default()).into_any()], - Box::new(()), - )) - .child(Story::label(cx, "With Mesages")) - .child(Panel::new( - ScrollState::default(), - |_, _| { - vec![ChatPanel::new(ScrollState::default()) - .with_messages(vec![ - ChatMessage::new( - "osiewicz".to_string(), - "is this thing on?".to_string(), - DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z") - .unwrap() - .naive_local(), - ), - ChatMessage::new( - "maxdeviant".to_string(), - "Reading you loud and clear!".to_string(), - DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z") - .unwrap() - .naive_local(), - ), - ]) - .into_any()] - }, - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/collab_panel.rs b/crates/storybook/src/stories/components/collab_panel.rs deleted file mode 100644 index 6a66f0d47f..0000000000 --- a/crates/storybook/src/stories/components/collab_panel.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::CollabPanel; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct CollabPanelStory {} - -impl CollabPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, CollabPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(CollabPanel::new(ScrollState::default())) - } -} diff --git a/crates/storybook/src/stories/components/context_menu.rs b/crates/storybook/src/stories/components/context_menu.rs deleted file mode 100644 index 71776ca66a..0000000000 --- a/crates/storybook/src/stories/components/context_menu.rs +++ /dev/null @@ -1,21 +0,0 @@ -use ui::prelude::*; -use ui::{ContextMenu, ContextMenuItem, Label}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ContextMenuStory {} - -impl ContextMenuStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - //.fill(theme.middle.base.default.background) - .child(Story::title_for::<_, ContextMenu>(cx)) - .child(Story::label(cx, "Default")) - .child(ContextMenu::new([ - ContextMenuItem::header("Section header"), - ContextMenuItem::Separator, - ContextMenuItem::entry(Label::new("Some entry")), - ])) - } -} diff --git a/crates/storybook/src/stories/components/facepile.rs b/crates/storybook/src/stories/components/facepile.rs deleted file mode 100644 index bbd08ae984..0000000000 --- a/crates/storybook/src/stories/components/facepile.rs +++ /dev/null @@ -1,25 +0,0 @@ -use ui::prelude::*; -use ui::{static_players, Facepile}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct FacepileStory {} - -impl FacepileStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let players = static_players(); - - Story::container(cx) - .child(Story::title_for::<_, Facepile>(cx)) - .child(Story::label(cx, "Default")) - .child( - div() - .flex() - .gap_3() - .child(Facepile::new(players.clone().into_iter().take(1))) - .child(Facepile::new(players.clone().into_iter().take(2))) - .child(Facepile::new(players.clone().into_iter().take(3))), - ) - } -} diff --git a/crates/storybook/src/stories/components/keybinding.rs b/crates/storybook/src/stories/components/keybinding.rs deleted file mode 100644 index 1acf59fe3b..0000000000 --- a/crates/storybook/src/stories/components/keybinding.rs +++ /dev/null @@ -1,64 +0,0 @@ -use itertools::Itertools; -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{Keybinding, ModifierKey, ModifierKeys}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct KeybindingStory {} - -impl KeybindingStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let all_modifier_permutations = ModifierKey::iter().permutations(2); - - Story::container(cx) - .child(Story::title_for::<_, Keybinding>(cx)) - .child(Story::label(cx, "Single Key")) - .child(Keybinding::new("Z".to_string(), ModifierKeys::new())) - .child(Story::label(cx, "Single Key with Modifier")) - .child( - div() - .flex() - .gap_3() - .children(ModifierKey::iter().map(|modifier| { - Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier)) - })), - ) - .child(Story::label(cx, "Single Key with Modifier (Permuted)")) - .child( - div().flex().flex_col().children( - all_modifier_permutations - .chunks(4) - .into_iter() - .map(|chunk| { - div() - .flex() - .gap_4() - .py_3() - .children(chunk.map(|permutation| { - let mut modifiers = ModifierKeys::new(); - - for modifier in permutation { - modifiers = modifiers.add(modifier); - } - - Keybinding::new("X".to_string(), modifiers) - })) - }), - ), - ) - .child(Story::label(cx, "Single Key with All Modifiers")) - .child(Keybinding::new("Z".to_string(), ModifierKeys::all())) - .child(Story::label(cx, "Chord")) - .child(Keybinding::new_chord( - ("A".to_string(), ModifierKeys::new()), - ("Z".to_string(), ModifierKeys::new()), - )) - .child(Story::label(cx, "Chord with Modifier")) - .child(Keybinding::new_chord( - ("A".to_string(), ModifierKeys::new().control(true)), - ("Z".to_string(), ModifierKeys::new().shift(true)), - )) - } -} diff --git a/crates/storybook/src/stories/components/language_selector.rs b/crates/storybook/src/stories/components/language_selector.rs deleted file mode 100644 index c6dbd13d3f..0000000000 --- a/crates/storybook/src/stories/components/language_selector.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::LanguageSelector; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct LanguageSelectorStory {} - -impl LanguageSelectorStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, LanguageSelector>(cx)) - .child(Story::label(cx, "Default")) - .child(LanguageSelector::new()) - } -} diff --git a/crates/storybook/src/stories/components/multi_buffer.rs b/crates/storybook/src/stories/components/multi_buffer.rs deleted file mode 100644 index cd760c54dc..0000000000 --- a/crates/storybook/src/stories/components/multi_buffer.rs +++ /dev/null @@ -1,24 +0,0 @@ -use ui::prelude::*; -use ui::{hello_world_rust_buffer_example, MultiBuffer}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct MultiBufferStory {} - -impl MultiBufferStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - Story::container(cx) - .child(Story::title_for::<_, MultiBuffer>(cx)) - .child(Story::label(cx, "Default")) - .child(MultiBuffer::new(vec![ - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - ])) - } -} diff --git a/crates/storybook/src/stories/components/palette.rs b/crates/storybook/src/stories/components/palette.rs deleted file mode 100644 index d14fac6697..0000000000 --- a/crates/storybook/src/stories/components/palette.rs +++ /dev/null @@ -1,53 +0,0 @@ -use ui::prelude::*; -use ui::{Keybinding, ModifierKeys, Palette, PaletteItem}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct PaletteStory {} - -impl PaletteStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Palette>(cx)) - .child(Story::label(cx, "Default")) - .child(Palette::new(ScrollState::default())) - .child(Story::label(cx, "With Items")) - .child( - Palette::new(ScrollState::default()) - .placeholder("Execute a command...") - .items(vec![ - PaletteItem::new("theme selector: toggle").keybinding( - Keybinding::new_chord( - ("k".to_string(), ModifierKeys::new().command(true)), - ("t".to_string(), ModifierKeys::new().command(true)), - ), - ), - PaletteItem::new("assistant: inline assist").keybinding(Keybinding::new( - "enter".to_string(), - ModifierKeys::new().command(true), - )), - PaletteItem::new("assistant: quote selection").keybinding(Keybinding::new( - ">".to_string(), - ModifierKeys::new().command(true), - )), - PaletteItem::new("assistant: toggle focus").keybinding(Keybinding::new( - "?".to_string(), - ModifierKeys::new().command(true), - )), - PaletteItem::new("auto update: check"), - PaletteItem::new("auto update: view release notes"), - PaletteItem::new("branches: open recent").keybinding(Keybinding::new( - "b".to_string(), - ModifierKeys::new().command(true).alt(true), - )), - PaletteItem::new("chat panel: toggle focus"), - PaletteItem::new("cli: install"), - PaletteItem::new("client: sign in"), - PaletteItem::new("client: sign out"), - PaletteItem::new("editor: cancel") - .keybinding(Keybinding::new("escape".to_string(), ModifierKeys::new())), - ]), - ) - } -} diff --git a/crates/storybook/src/stories/components/panel.rs b/crates/storybook/src/stories/components/panel.rs deleted file mode 100644 index 39a5ceafa2..0000000000 --- a/crates/storybook/src/stories/components/panel.rs +++ /dev/null @@ -1,25 +0,0 @@ -use ui::prelude::*; -use ui::{Label, Panel}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct PanelStory {} - -impl PanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Panel>(cx)) - .child(Story::label(cx, "Default")) - .child(Panel::new( - ScrollState::default(), - |_, _| { - vec![div() - .overflow_y_scroll(ScrollState::default()) - .children((0..100).map(|ix| Label::new(format!("Item {}", ix + 1)))) - .into_any()] - }, - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/project_panel.rs b/crates/storybook/src/stories/components/project_panel.rs deleted file mode 100644 index cba71cd21a..0000000000 --- a/crates/storybook/src/stories/components/project_panel.rs +++ /dev/null @@ -1,20 +0,0 @@ -use ui::prelude::*; -use ui::{Panel, ProjectPanel}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ProjectPanelStory {} - -impl ProjectPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, ProjectPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(Panel::new( - ScrollState::default(), - |_, _| vec![ProjectPanel::new(ScrollState::default()).into_any()], - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/recent_projects.rs b/crates/storybook/src/stories/components/recent_projects.rs deleted file mode 100644 index f924654695..0000000000 --- a/crates/storybook/src/stories/components/recent_projects.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::RecentProjects; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct RecentProjectsStory {} - -impl RecentProjectsStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, RecentProjects>(cx)) - .child(Story::label(cx, "Default")) - .child(RecentProjects::new()) - } -} diff --git a/crates/storybook/src/stories/components/status_bar.rs b/crates/storybook/src/stories/components/status_bar.rs deleted file mode 100644 index ed3d047c6d..0000000000 --- a/crates/storybook/src/stories/components/status_bar.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::StatusBar; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct StatusBarStory {} - -impl StatusBarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, StatusBar>(cx)) - .child(Story::label(cx, "Default")) - .child(StatusBar::new()) - } -} diff --git a/crates/storybook/src/stories/components/tab.rs b/crates/storybook/src/stories/components/tab.rs deleted file mode 100644 index 6a154ce644..0000000000 --- a/crates/storybook/src/stories/components/tab.rs +++ /dev/null @@ -1,91 +0,0 @@ -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{h_stack, v_stack, Tab}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TabStory {} - -impl TabStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let git_statuses = GitStatus::iter(); - let fs_statuses = FileSystemStatus::iter(); - - Story::container(cx) - .child(Story::title_for::<_, Tab>(cx)) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "Default")) - .child(Tab::new()), - ), - ) - .child( - h_stack().child( - v_stack().gap_2().child(Story::label(cx, "Current")).child( - h_stack() - .gap_4() - .child(Tab::new().title("Current".to_string()).current(true)) - .child(Tab::new().title("Not Current".to_string()).current(false)), - ), - ), - ) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "Titled")) - .child(Tab::new().title("label".to_string())), - ), - ) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "With Icon")) - .child( - Tab::new() - .title("label".to_string()) - .icon(Some(ui::Icon::Envelope)), - ), - ), - ) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "Close Side")) - .child( - h_stack() - .gap_4() - .child( - Tab::new() - .title("Left".to_string()) - .close_side(IconSide::Left), - ) - .child(Tab::new().title("Right".to_string())), - ), - ), - ) - .child( - v_stack() - .gap_2() - .child(Story::label(cx, "Git Status")) - .child(h_stack().gap_4().children(git_statuses.map(|git_status| { - Tab::new() - .title(git_status.to_string()) - .git_status(git_status) - }))), - ) - .child( - v_stack() - .gap_2() - .child(Story::label(cx, "File System Status")) - .child(h_stack().gap_4().children(fs_statuses.map(|fs_status| { - Tab::new().title(fs_status.to_string()).fs_status(fs_status) - }))), - ) - } -} diff --git a/crates/storybook/src/stories/components/tab_bar.rs b/crates/storybook/src/stories/components/tab_bar.rs deleted file mode 100644 index b5fa45dfd6..0000000000 --- a/crates/storybook/src/stories/components/tab_bar.rs +++ /dev/null @@ -1,46 +0,0 @@ -use ui::prelude::*; -use ui::{Tab, TabBar}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TabBarStory {} - -impl TabBarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, TabBar>(cx)) - .child(Story::label(cx, "Default")) - .child(TabBar::new(vec![ - Tab::new() - .title("Cargo.toml".to_string()) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("Channels Panel".to_string()) - .current(false), - Tab::new() - .title("channels_panel.rs".to_string()) - .current(true) - .git_status(GitStatus::Modified), - Tab::new() - .title("workspace.rs".to_string()) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("icon_button.rs".to_string()) - .current(false), - Tab::new() - .title("storybook.rs".to_string()) - .current(false) - .git_status(GitStatus::Created), - Tab::new().title("theme.rs".to_string()).current(false), - Tab::new() - .title("theme_registry.rs".to_string()) - .current(false), - Tab::new() - .title("styleable_helpers.rs".to_string()) - .current(false), - ])) - } -} diff --git a/crates/storybook/src/stories/components/terminal.rs b/crates/storybook/src/stories/components/terminal.rs deleted file mode 100644 index 2bce2e27e5..0000000000 --- a/crates/storybook/src/stories/components/terminal.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::Terminal; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TerminalStory {} - -impl TerminalStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Terminal>(cx)) - .child(Story::label(cx, "Default")) - .child(Terminal::new()) - } -} diff --git a/crates/storybook/src/stories/components/theme_selector.rs b/crates/storybook/src/stories/components/theme_selector.rs deleted file mode 100644 index 43e2a704e7..0000000000 --- a/crates/storybook/src/stories/components/theme_selector.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::ThemeSelector; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ThemeSelectorStory {} - -impl ThemeSelectorStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, ThemeSelector>(cx)) - .child(Story::label(cx, "Default")) - .child(ThemeSelector::new()) - } -} diff --git a/crates/storybook/src/stories/components/title_bar.rs b/crates/storybook/src/stories/components/title_bar.rs deleted file mode 100644 index 3c4682b3ba..0000000000 --- a/crates/storybook/src/stories/components/title_bar.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::TitleBar; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TitleBarStory {} - -impl TitleBarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, TitleBar>(cx)) - .child(Story::label(cx, "Default")) - .child(TitleBar::new(cx)) - } -} diff --git a/crates/storybook/src/stories/components/toolbar.rs b/crates/storybook/src/stories/components/toolbar.rs deleted file mode 100644 index 1413c463b4..0000000000 --- a/crates/storybook/src/stories/components/toolbar.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::path::PathBuf; -use std::str::FromStr; -use std::sync::Arc; - -use ui::prelude::*; -use ui::{theme, Breadcrumb, HighlightColor, HighlightedText, Icon, IconButton, Symbol, Toolbar}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ToolbarStory {} - -impl ToolbarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - struct LeftItemsPayload { - pub theme: Arc, - } - - Story::container(cx) - .child(Story::title_for::<_, Toolbar>(cx)) - .child(Story::label(cx, "Default")) - .child(Toolbar::new( - |_, payload| { - let payload = payload.downcast_ref::().unwrap(); - - let theme = payload.theme.clone(); - - vec![Breadcrumb::new( - PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(), - vec![ - Symbol(vec![ - HighlightedText { - text: "impl ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "ToolbarStory".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "render".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - ], - ) - .into_any()] - }, - Box::new(LeftItemsPayload { - theme: theme.clone(), - }), - |_, _| { - vec![ - IconButton::new(Icon::InlayHint).into_any(), - IconButton::new(Icon::MagnifyingGlass).into_any(), - IconButton::new(Icon::MagicWand).into_any(), - ] - }, - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/traffic_lights.rs b/crates/storybook/src/stories/components/traffic_lights.rs deleted file mode 100644 index 3b759a43a3..0000000000 --- a/crates/storybook/src/stories/components/traffic_lights.rs +++ /dev/null @@ -1,18 +0,0 @@ -use ui::prelude::*; -use ui::TrafficLights; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TrafficLightsStory {} - -impl TrafficLightsStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, TrafficLights>(cx)) - .child(Story::label(cx, "Default")) - .child(TrafficLights::new()) - .child(Story::label(cx, "Unfocused")) - .child(TrafficLights::new().window_has_focus(false)) - } -} diff --git a/crates/storybook/src/stories/elements.rs b/crates/storybook/src/stories/elements.rs deleted file mode 100644 index f7afec4d88..0000000000 --- a/crates/storybook/src/stories/elements.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod avatar; -pub mod button; -pub mod icon; -pub mod input; -pub mod label; diff --git a/crates/storybook/src/stories/elements/avatar.rs b/crates/storybook/src/stories/elements/avatar.rs deleted file mode 100644 index a277fa6a1e..0000000000 --- a/crates/storybook/src/stories/elements/avatar.rs +++ /dev/null @@ -1,23 +0,0 @@ -use ui::prelude::*; -use ui::Avatar; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct AvatarStory {} - -impl AvatarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Avatar>(cx)) - .child(Story::label(cx, "Default")) - .child(Avatar::new( - "https://avatars.githubusercontent.com/u/1714999?v=4", - )) - .child(Story::label(cx, "Rounded rectangle")) - .child( - Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4") - .shape(Shape::RoundedRectangle), - ) - } -} diff --git a/crates/storybook/src/stories/elements/button.rs b/crates/storybook/src/stories/elements/button.rs deleted file mode 100644 index 7ff3fdd30c..0000000000 --- a/crates/storybook/src/stories/elements/button.rs +++ /dev/null @@ -1,192 +0,0 @@ -use gpui2::elements::div; -use gpui2::geometry::rems; -use gpui2::{Element, IntoElement, ViewContext}; -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{h_stack, v_stack, Button, Icon, IconPosition, Label}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ButtonStory {} - -impl ButtonStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let states = InteractionState::iter(); - - Story::container(cx) - .child(Story::title_for::<_, Button>(cx)) - .child( - div() - .flex() - .gap_8() - .child( - div() - .child(Story::label(cx, "Ghost (Default)")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - .state(state), - ) - }))) - .child(Story::label(cx, "Ghost – Left Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - .icon(Icon::Plus) - .icon_position(IconPosition::Left) - .state(state), - ) - }))) - .child(Story::label(cx, "Ghost – Right Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - .icon(Icon::Plus) - .icon_position(IconPosition::Right) - .state(state), - ) - }))), - ) - .child( - div() - .child(Story::label(cx, "Filled")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state), - ) - }))) - .child(Story::label(cx, "Filled – Left Button")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .icon(Icon::Plus) - .icon_position(IconPosition::Left) - .state(state), - ) - }))) - .child(Story::label(cx, "Filled – Right Button")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .icon(Icon::Plus) - .icon_position(IconPosition::Right) - .state(state), - ) - }))), - ) - .child( - div() - .child(Story::label(cx, "Fixed With")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state) - .width(Some(rems(6.).into())), - ) - }))) - .child(Story::label(cx, "Fixed With – Left Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state) - .icon(Icon::Plus) - .icon_position(IconPosition::Left) - .width(Some(rems(6.).into())), - ) - }))) - .child(Story::label(cx, "Fixed With – Right Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state) - .icon(Icon::Plus) - .icon_position(IconPosition::Right) - .width(Some(rems(6.).into())), - ) - }))), - ), - ) - .child(Story::label(cx, "Button with `on_click`")) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - // NOTE: There currently appears to be a bug in GPUI2 where only the last event handler will fire. - // So adding additional buttons with `on_click`s after this one will cause this `on_click` to not fire. - .on_click(|_view, _cx| println!("Button clicked.")), - ) - } -} diff --git a/crates/storybook/src/stories/elements/icon.rs b/crates/storybook/src/stories/elements/icon.rs deleted file mode 100644 index 66d3abc0b3..0000000000 --- a/crates/storybook/src/stories/elements/icon.rs +++ /dev/null @@ -1,19 +0,0 @@ -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{Icon, IconElement}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct IconStory {} - -impl IconStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let icons = Icon::iter(); - - Story::container(cx) - .child(Story::title_for::<_, IconElement>(cx)) - .child(Story::label(cx, "All Icons")) - .child(div().flex().gap_3().children(icons.map(IconElement::new))) - } -} diff --git a/crates/storybook/src/stories/elements/input.rs b/crates/storybook/src/stories/elements/input.rs deleted file mode 100644 index 8617b7daaf..0000000000 --- a/crates/storybook/src/stories/elements/input.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::Input; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct InputStory {} - -impl InputStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Input>(cx)) - .child(Story::label(cx, "Default")) - .child(div().flex().child(Input::new("Search"))) - } -} diff --git a/crates/storybook/src/stories/elements/label.rs b/crates/storybook/src/stories/elements/label.rs deleted file mode 100644 index 1b63cb3a3a..0000000000 --- a/crates/storybook/src/stories/elements/label.rs +++ /dev/null @@ -1,18 +0,0 @@ -use ui::prelude::*; -use ui::Label; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct LabelStory {} - -impl LabelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Label>(cx)) - .child(Story::label(cx, "Default")) - .child(Label::new("Hello, world!")) - .child(Story::label(cx, "Highlighted")) - .child(Label::new("Hello, world!").with_highlights(vec![0, 1, 2, 7, 8, 12])) - } -} diff --git a/crates/storybook/src/stories/kitchen_sink.rs b/crates/storybook/src/stories/kitchen_sink.rs deleted file mode 100644 index ae826f934e..0000000000 --- a/crates/storybook/src/stories/kitchen_sink.rs +++ /dev/null @@ -1,26 +0,0 @@ -use strum::IntoEnumIterator; -use ui::prelude::*; - -use crate::story::Story; -use crate::story_selector::{ComponentStory, ElementStory}; - -#[derive(Element, Default)] -pub struct KitchenSinkStory {} - -impl KitchenSinkStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let element_stories = ElementStory::iter().map(|selector| selector.story()); - let component_stories = ComponentStory::iter().map(|selector| selector.story()); - - Story::container(cx) - .overflow_y_scroll(ScrollState::default()) - .child(Story::title(cx, "Kitchen Sink")) - .child(Story::label(cx, "Elements")) - .child(div().flex().flex_col().children_any(element_stories)) - .child(Story::label(cx, "Components")) - .child(div().flex().flex_col().children_any(component_stories)) - // Add a bit of space at the bottom of the kitchen sink so elements - // don't end up squished right up against the bottom of the screen. - .child(div().p_4()) - } -} diff --git a/crates/storybook/src/story.rs b/crates/storybook/src/story.rs deleted file mode 100644 index 16eae50f88..0000000000 --- a/crates/storybook/src/story.rs +++ /dev/null @@ -1,44 +0,0 @@ -use gpui2::elements::div::Div; -use ui::prelude::*; -use ui::theme; - -pub struct Story {} - -impl Story { - pub fn container(cx: &mut ViewContext) -> Div { - let theme = theme(cx); - - div() - .size_full() - .flex() - .flex_col() - .pt_2() - .px_4() - .font("Zed Mono Extended") - .fill(theme.lowest.base.default.background) - } - - pub fn title(cx: &mut ViewContext, title: &str) -> impl Element { - let theme = theme(cx); - - div() - .text_xl() - .text_color(theme.lowest.base.default.foreground) - .child(title.to_owned()) - } - - pub fn title_for(cx: &mut ViewContext) -> impl Element { - Self::title(cx, std::any::type_name::()) - } - - pub fn label(cx: &mut ViewContext, label: &str) -> impl Element { - let theme = theme(cx); - - div() - .mt_4() - .mb_2() - .text_xs() - .text_color(theme.lowest.base.default.foreground) - .child(label.to_owned()) - } -} diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs deleted file mode 100644 index 6b0a9ac78d..0000000000 --- a/crates/storybook/src/story_selector.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::str::FromStr; -use std::sync::OnceLock; - -use anyhow::{anyhow, Context}; -use clap::builder::PossibleValue; -use clap::ValueEnum; -use gpui2::{AnyElement, Element}; -use strum::{EnumIter, EnumString, IntoEnumIterator}; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)] -#[strum(serialize_all = "snake_case")] -pub enum ElementStory { - Avatar, - Button, - Icon, - Input, - Label, -} - -impl ElementStory { - pub fn story(&self) -> AnyElement { - use crate::stories::elements; - - match self { - Self::Avatar => elements::avatar::AvatarStory::default().into_any(), - Self::Button => elements::button::ButtonStory::default().into_any(), - Self::Icon => elements::icon::IconStory::default().into_any(), - Self::Input => elements::input::InputStory::default().into_any(), - Self::Label => elements::label::LabelStory::default().into_any(), - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)] -#[strum(serialize_all = "snake_case")] -pub enum ComponentStory { - AssistantPanel, - Breadcrumb, - Buffer, - ContextMenu, - ChatPanel, - CollabPanel, - Facepile, - Keybinding, - LanguageSelector, - MultiBuffer, - Palette, - Panel, - ProjectPanel, - RecentProjects, - StatusBar, - Tab, - TabBar, - Terminal, - ThemeSelector, - TitleBar, - Toolbar, - TrafficLights, -} - -impl ComponentStory { - pub fn story(&self) -> AnyElement { - use crate::stories::components; - - match self { - Self::AssistantPanel => { - components::assistant_panel::AssistantPanelStory::default().into_any() - } - Self::Breadcrumb => components::breadcrumb::BreadcrumbStory::default().into_any(), - Self::Buffer => components::buffer::BufferStory::default().into_any(), - Self::ContextMenu => components::context_menu::ContextMenuStory::default().into_any(), - Self::ChatPanel => components::chat_panel::ChatPanelStory::default().into_any(), - Self::CollabPanel => components::collab_panel::CollabPanelStory::default().into_any(), - Self::Facepile => components::facepile::FacepileStory::default().into_any(), - Self::Keybinding => components::keybinding::KeybindingStory::default().into_any(), - Self::LanguageSelector => { - components::language_selector::LanguageSelectorStory::default().into_any() - } - Self::MultiBuffer => components::multi_buffer::MultiBufferStory::default().into_any(), - Self::Palette => components::palette::PaletteStory::default().into_any(), - Self::Panel => components::panel::PanelStory::default().into_any(), - Self::ProjectPanel => { - components::project_panel::ProjectPanelStory::default().into_any() - } - Self::RecentProjects => { - components::recent_projects::RecentProjectsStory::default().into_any() - } - Self::StatusBar => components::status_bar::StatusBarStory::default().into_any(), - Self::Tab => components::tab::TabStory::default().into_any(), - Self::TabBar => components::tab_bar::TabBarStory::default().into_any(), - Self::Terminal => components::terminal::TerminalStory::default().into_any(), - Self::ThemeSelector => { - components::theme_selector::ThemeSelectorStory::default().into_any() - } - Self::TitleBar => components::title_bar::TitleBarStory::default().into_any(), - Self::Toolbar => components::toolbar::ToolbarStory::default().into_any(), - Self::TrafficLights => { - components::traffic_lights::TrafficLightsStory::default().into_any() - } - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum StorySelector { - Element(ElementStory), - Component(ComponentStory), - KitchenSink, -} - -impl FromStr for StorySelector { - type Err = anyhow::Error; - - fn from_str(raw_story_name: &str) -> std::result::Result { - let story = raw_story_name.to_ascii_lowercase(); - - if story == "kitchen_sink" { - return Ok(Self::KitchenSink); - } - - if let Some((_, story)) = story.split_once("elements/") { - let element_story = ElementStory::from_str(story) - .with_context(|| format!("story not found for element '{story}'"))?; - - return Ok(Self::Element(element_story)); - } - - if let Some((_, story)) = story.split_once("components/") { - let component_story = ComponentStory::from_str(story) - .with_context(|| format!("story not found for component '{story}'"))?; - - return Ok(Self::Component(component_story)); - } - - Err(anyhow!("story not found for '{raw_story_name}'")) - } -} - -impl StorySelector { - pub fn story(&self) -> AnyElement { - match self { - Self::Element(element_story) => element_story.story(), - Self::Component(component_story) => component_story.story(), - Self::KitchenSink => { - crate::stories::kitchen_sink::KitchenSinkStory::default().into_any() - } - } - } -} - -/// The list of all stories available in the storybook. -static ALL_STORY_SELECTORS: OnceLock> = OnceLock::new(); - -impl ValueEnum for StorySelector { - fn value_variants<'a>() -> &'a [Self] { - let stories = ALL_STORY_SELECTORS.get_or_init(|| { - let element_stories = ElementStory::iter().map(StorySelector::Element); - let component_stories = ComponentStory::iter().map(StorySelector::Component); - - element_stories - .chain(component_stories) - .chain(std::iter::once(StorySelector::KitchenSink)) - .collect::>() - }); - - stories - } - - fn to_possible_value(&self) -> Option { - let value = match self { - Self::Element(story) => format!("elements/{story}"), - Self::Component(story) => format!("components/{story}"), - Self::KitchenSink => "kitchen_sink".to_string(), - }; - - Some(PossibleValue::new(value)) - } -} diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs deleted file mode 100644 index afae0d5ebe..0000000000 --- a/crates/storybook/src/storybook.rs +++ /dev/null @@ -1,198 +0,0 @@ -#![allow(dead_code, unused_variables)] - -mod stories; -mod story; -mod story_selector; - -use std::{process::Command, sync::Arc}; - -use ::theme as legacy_theme; -use clap::Parser; -use gpui2::{ - serde_json, vec2f, view, Element, IntoElement, ParentElement, RectF, ViewContext, WindowBounds, -}; -use legacy_theme::{ThemeRegistry, ThemeSettings}; -use log::LevelFilter; -use settings::{default_settings, SettingsStore}; -use simplelog::SimpleLogger; -use ui::prelude::*; -use ui::{ElementExt, Theme, WorkspaceElement}; - -use crate::story_selector::StorySelector; - -gpui2::actions! { - storybook, - [ToggleInspector] -} - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Args { - #[arg(value_enum)] - story: Option, - - /// The name of the theme to use in the storybook. - /// - /// If not provided, the default theme will be used. - #[arg(long)] - theme: Option, -} - -async fn watch_zed_changes(fs: Arc) -> Option<()> { - if std::env::var("ZED_HOT_RELOAD").is_err() { - return None; - } - use futures::StreamExt; - let mut events = fs - .watch(".".as_ref(), std::time::Duration::from_millis(100)) - .await; - let mut current_child: Option = None; - while let Some(events) = events.next().await { - if !events.iter().any(|event| { - event - .path - .to_str() - .map(|path| path.contains("/crates/")) - .unwrap_or_default() - }) { - continue; - } - let child = current_child.take().map(|mut child| child.kill()); - log::info!("Storybook changed, rebuilding..."); - current_child = Some( - Command::new("cargo") - .args(["run", "-p", "storybook"]) - .spawn() - .ok()?, - ); - } - Some(()) -} - -fn main() { - SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); - - let args = Args::parse(); - - let fs = Arc::new(fs::RealFs); - - gpui2::App::new(Assets).unwrap().run(move |cx| { - let mut store = SettingsStore::default(); - store - .set_default_settings(default_settings().as_ref(), cx) - .unwrap(); - cx.set_global(store); - legacy_theme::init(Assets, cx); - // load_embedded_fonts(cx.platform().as_ref()); - - let theme_registry = cx.global::>(); - - let theme_override = args - .theme - .and_then(|theme| { - theme_registry - .list_names(true) - .find(|known_theme| theme == *known_theme) - }) - .and_then(|theme_name| theme_registry.get(&theme_name).ok()); - - cx.spawn(|_| async move { - watch_zed_changes(fs).await; - }) - .detach(); - cx.add_window( - gpui2::WindowOptions { - bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1700., 980.))), - center: true, - ..Default::default() - }, - |cx| match args.story { - Some(selector) => view(move |cx| { - render_story( - &mut ViewContext::new(cx), - theme_override.clone(), - div().flex().flex_col().h_full().child_any(selector.story()), - ) - }), - None => view(move |cx| { - render_story( - &mut ViewContext::new(cx), - theme_override.clone(), - WorkspaceElement::default(), - ) - }), - }, - ); - cx.platform().activate(true); - }); -} - -fn render_story>( - cx: &mut ViewContext, - theme_override: Option>, - story: S, -) -> impl Element { - let theme = current_theme(cx, theme_override); - - story.into_element().themed(theme) -} - -fn current_theme( - cx: &mut ViewContext, - theme_override: Option>, -) -> Theme { - let legacy_theme = - theme_override.unwrap_or_else(|| settings::get::(cx).theme.clone()); - - let new_theme: Theme = serde_json::from_value(legacy_theme.base_theme.clone()).unwrap(); - - add_base_theme_to_legacy_theme(&legacy_theme, new_theme) -} - -// Nathan: During the transition to gpui2, we will include the base theme on the legacy Theme struct. -fn add_base_theme_to_legacy_theme(legacy_theme: &legacy_theme::Theme, new_theme: Theme) -> Theme { - legacy_theme - .deserialized_base_theme - .lock() - .get_or_insert_with(|| Box::new(new_theme)) - .downcast_ref::() - .unwrap() - .clone() -} - -use anyhow::{anyhow, Result}; -use gpui2::AssetSource; -use rust_embed::RustEmbed; - -#[derive(RustEmbed)] -#[folder = "../../assets"] -#[include = "themes/**/*"] -#[include = "fonts/**/*"] -#[include = "icons/**/*"] -#[exclude = "*.DS_Store"] -pub struct Assets; - -impl AssetSource for Assets { - fn load(&self, path: &str) -> Result> { - Self::get(path) - .map(|f| f.data) - .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) - } - - fn list(&self, path: &str) -> Vec> { - Self::iter().filter(|p| p.starts_with(path)).collect() - } -} - -// fn load_embedded_fonts(platform: &dyn gpui2::Platform) { -// let font_paths = Assets.list("fonts"); -// let mut embedded_fonts = Vec::new(); -// for font_path in &font_paths { -// if font_path.ends_with(".ttf") { -// let font_path = &*font_path; -// let font_bytes = Assets.load(font_path).unwrap().to_vec(); -// embedded_fonts.push(Arc::from(font_bytes)); -// } -// } -// platform.fonts().add_fonts(&embedded_fonts).unwrap(); -// } diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml deleted file mode 100644 index 7bd9d912a0..0000000000 --- a/crates/ui/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "ui" -version = "0.1.0" -edition = "2021" -publish = false - -[dependencies] -anyhow.workspace = true -chrono = "0.4" -gpui2 = { path = "../gpui2" } -serde.workspace = true -settings = { path = "../settings" } -smallvec.workspace = true -strum = { version = "0.25.0", features = ["derive"] } -theme = { path = "../theme" } -rand = "0.8" diff --git a/crates/ui/docs/_project.md b/crates/ui/docs/_project.md deleted file mode 100644 index a3b72a9d61..0000000000 --- a/crates/ui/docs/_project.md +++ /dev/null @@ -1,13 +0,0 @@ -## Project Plan - -- Port existing UI to GPUI2 -- Update UI in places that GPUI1 was limiting us* -- Understand the needs &/|| struggles the engineers have been having with building UI in the past and address as many of those as possible as we go -- Ship a simple, straightforward system with documentation that is easy to use to build UI - -## Component Classification - -To simplify the understanding of components and minimize unnecessary cognitive load, let's categorize components into two types: - -- An element refers to a standalone component that doesn't import any other 'ui' components. -- A component indicates a component that utilizes or imports other 'ui' components. diff --git a/crates/ui/docs/elevation.md b/crates/ui/docs/elevation.md deleted file mode 100644 index bd34de3396..0000000000 --- a/crates/ui/docs/elevation.md +++ /dev/null @@ -1,57 +0,0 @@ -# Elevation - -Elevation in Zed applies to all surfaces and components. Elevation is categorized into levels. - -Elevation accomplishes the following: -- Allows surfaces to move in front of or behind others, such as content scrolling beneath app top bars. -- Reflects spatial relationships, for instance, how a floating action button’s shadow intimates its disconnection from a collection of cards. -- Directs attention to structures at the highest elevation, like a temporary dialog arising in front of other surfaces. - -Elevations are the initial elevation values assigned to components by default. - -Components may transition to a higher elevation in some cases, like user interations. - -On such occasions, components transition to predetermined dynamic elevation offsets. These are the typical elevations to which components move when they are not at rest. - -## Understanding Elevation - -Elevation can be thought of as the physical closeness of an element to the user. Elements with lower elevations are physically further away from the user on the z-axis and appear to be underneath elements with higher elevations. - -Material Design 3 has a some great visualizations of elevation that may be helpful to understanding the mental modal of elevation. [Material Design – Elevation](https://m3.material.io/styles/elevation/overview) - -## Elevation Levels - -Zed integrates six unique elevation levels in its design system. The elevation of a surface is expressed as a whole number ranging from 0 to 5, both numbers inclusive. A component’s elevation is ascertained by combining the component’s resting elevation with any dynamic elevation offsets. - -The levels are detailed as follows: - -0. App Background -1. UI Surface -2. Elevated Elements -3. Wash -4. Focused Element -5. Dragged Element - -### 0. App Background - -The app background constitutes the lowest elevation layer, appearing behind all other surfaces and components. It is predominantly used for the background color of the app. - -### 1. UI Surface - -The UI Surface is the standard elevation for components and is placed above the app background. It is generally used for the background color of the app bar, card, and sheet. - -### 2. Elevated Elements - -Elevated elements appear above the UI surface layer surfaces and components. Elevated elements are predominantly used for creating popovers, context menus, and tooltips. - -### 3. Wash - -Wash denotes a distinct elevation reserved to isolate app UI layers from high elevation components such as modals, notifications, and overlaid panels. The wash may not consistently be visible when these components are active. This layer is often referred to as a scrim or overlay and the background color of the wash is typically deployed in its design. - -### 4. Focused Element - -Focused elements obtain a higher elevation above surfaces and components at wash elevation. They are often used for modals, notifications, and overlaid panels and indicate that they are the sole element the user is interacting with at the moment. - -### 5. Dragged Element - -Dragged elements gain the highest elevation, thus appearing above surfaces and components at the elevation of focused elements. These are typically used for elements that are being dragged, following the cursor diff --git a/crates/ui/src/children.rs b/crates/ui/src/children.rs deleted file mode 100644 index 947f8d98cf..0000000000 --- a/crates/ui/src/children.rs +++ /dev/null @@ -1,7 +0,0 @@ -use std::any::Any; - -use gpui2::{AnyElement, ViewContext}; - -pub type HackyChildren = fn(&mut ViewContext, &dyn Any) -> Vec>; - -pub type HackyChildrenPayload = Box; diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs deleted file mode 100644 index 65b0218565..0000000000 --- a/crates/ui/src/components.rs +++ /dev/null @@ -1,163 +0,0 @@ -mod assistant_panel; -mod breadcrumb; -mod buffer; -mod chat_panel; -mod collab_panel; -mod command_palette; -mod context_menu; -mod editor_pane; -mod facepile; -mod icon_button; -mod keybinding; -mod language_selector; -mod list; -mod multi_buffer; -mod palette; -mod panel; -mod panes; -mod player_stack; -mod project_panel; -mod recent_projects; -mod status_bar; -mod tab; -mod tab_bar; -mod terminal; -mod theme_selector; -mod title_bar; -mod toast; -mod toolbar; -mod traffic_lights; -mod workspace; - -pub use assistant_panel::*; -pub use breadcrumb::*; -pub use buffer::*; -pub use chat_panel::*; -pub use collab_panel::*; -pub use command_palette::*; -pub use context_menu::*; -pub use editor_pane::*; -pub use facepile::*; -pub use icon_button::*; -pub use keybinding::*; -pub use language_selector::*; -pub use list::*; -pub use multi_buffer::*; -pub use palette::*; -pub use panel::*; -pub use panes::*; -pub use player_stack::*; -pub use project_panel::*; -pub use recent_projects::*; -pub use status_bar::*; -pub use tab::*; -pub use tab_bar::*; -pub use terminal::*; -pub use theme_selector::*; -pub use title_bar::*; -pub use toast::*; -pub use toolbar::*; -pub use traffic_lights::*; -pub use workspace::*; - -// Nate: Commenting this out for now, unsure if we need it. - -// use std::marker::PhantomData; -// use std::rc::Rc; - -// use gpui2::elements::div; -// use gpui2::interactive::Interactive; -// use gpui2::platform::MouseButton; -// use gpui2::{ArcCow, Element, EventContext, IntoElement, ParentElement, ViewContext}; - -// struct ButtonHandlers { -// click: Option)>>, -// } - -// impl Default for ButtonHandlers { -// fn default() -> Self { -// Self { click: None } -// } -// } - -// #[derive(Element)] -// pub struct Button { -// handlers: ButtonHandlers, -// label: Option>, -// icon: Option>, -// data: Rc, -// view_type: PhantomData, -// } - -// // Impl block for buttons without data. -// // See below for an impl block for any button. -// impl Button { -// fn new() -> Self { -// Self { -// handlers: ButtonHandlers::default(), -// label: None, -// icon: None, -// data: Rc::new(()), -// view_type: PhantomData, -// } -// } - -// pub fn data(self, data: D) -> Button { -// Button { -// handlers: ButtonHandlers::default(), -// label: self.label, -// icon: self.icon, -// data: Rc::new(data), -// view_type: PhantomData, -// } -// } -// } - -// // Impl block for button regardless of its data type. -// impl Button { -// pub fn label(mut self, label: impl Into>) -> Self { -// self.label = Some(label.into()); -// self -// } - -// pub fn icon(mut self, icon: impl Into>) -> Self { -// self.icon = Some(icon.into()); -// self -// } - -// pub fn on_click( -// mut self, -// handler: impl Fn(&mut V, &D, &mut EventContext) + 'static, -// ) -> Self { -// self.handlers.click = Some(Rc::new(handler)); -// self -// } -// } - -// pub fn button() -> Button { -// Button::new() -// } - -// impl Button { -// fn render( -// &mut self, -// view: &mut V, -// cx: &mut ViewContext, -// ) -> impl IntoElement + Interactive { -// // let colors = &cx.theme::().colors; - -// let button = div() -// // .fill(colors.error(0.5)) -// .h_4() -// .children(self.label.clone()); - -// if let Some(handler) = self.handlers.click.clone() { -// let data = self.data.clone(); -// button.on_mouse_down(MouseButton::Left, move |view, event, cx| { -// handler(view, data.as_ref(), cx) -// }) -// } else { -// button -// } -// } -// } diff --git a/crates/ui/src/components/assistant_panel.rs b/crates/ui/src/components/assistant_panel.rs deleted file mode 100644 index a0a0c52882..0000000000 --- a/crates/ui/src/components/assistant_panel.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::geometry::rems; - -use crate::prelude::*; -use crate::theme::theme; -use crate::{Icon, IconButton, Label, Panel, PanelSide}; - -#[derive(Element)] -pub struct AssistantPanel { - view_type: PhantomData, - scroll_state: ScrollState, - current_side: PanelSide, -} - -impl AssistantPanel { - pub fn new() -> Self { - Self { - view_type: PhantomData, - scroll_state: ScrollState::default(), - current_side: PanelSide::default(), - } - } - - pub fn side(mut self, side: PanelSide) -> Self { - self.current_side = side; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - struct PanelPayload { - pub scroll_state: ScrollState, - } - - Panel::new( - self.scroll_state.clone(), - |_, payload| { - let payload = payload.downcast_ref::().unwrap(); - - vec![div() - .flex() - .flex_col() - .h_full() - .px_2() - .gap_2() - // Header - .child( - div() - .flex() - .justify_between() - .gap_2() - .child( - div() - .flex() - .child(IconButton::new(Icon::Menu)) - .child(Label::new("New Conversation")), - ) - .child( - div() - .flex() - .items_center() - .gap_px() - .child(IconButton::new(Icon::SplitMessage)) - .child(IconButton::new(Icon::Quote)) - .child(IconButton::new(Icon::MagicWand)) - .child(IconButton::new(Icon::Plus)) - .child(IconButton::new(Icon::Maximize)), - ), - ) - // Chat Body - .child( - div() - .w_full() - .flex() - .flex_col() - .gap_3() - .overflow_y_scroll(payload.scroll_state.clone()) - .child(Label::new("Is this thing on?")), - ) - .into_any()] - }, - Box::new(PanelPayload { - scroll_state: self.scroll_state.clone(), - }), - ) - .side(self.current_side) - .width(rems(32.)) - } -} diff --git a/crates/ui/src/components/breadcrumb.rs b/crates/ui/src/components/breadcrumb.rs deleted file mode 100644 index c14e89ee7b..0000000000 --- a/crates/ui/src/components/breadcrumb.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::path::PathBuf; - -use gpui2::elements::div::Div; - -use crate::{h_stack, theme}; -use crate::{prelude::*, HighlightedText}; - -#[derive(Clone)] -pub struct Symbol(pub Vec); - -#[derive(Element)] -pub struct Breadcrumb { - path: PathBuf, - symbols: Vec, -} - -impl Breadcrumb { - pub fn new(path: PathBuf, symbols: Vec) -> Self { - Self { path, symbols } - } - - fn render_separator(&self, theme: &Theme) -> Div { - div() - .child(" › ") - .text_color(HighlightColor::Default.hsla(theme)) - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let symbols_len = self.symbols.len(); - - h_stack() - .px_1() - // TODO: Read font from theme (or settings?). - .font("Zed Mono Extended") - .text_sm() - .text_color(theme.middle.base.default.foreground) - .rounded_md() - .hover() - .fill(theme.highest.base.hovered.background) - .child(self.path.clone().to_str().unwrap().to_string()) - .child(if !self.symbols.is_empty() { - self.render_separator(&theme) - } else { - div() - }) - .child( - div().flex().children( - self.symbols - .iter() - .enumerate() - // TODO: Could use something like `intersperse` here instead. - .flat_map(|(ix, symbol)| { - let mut items = - vec![div().flex().children(symbol.0.iter().map(|segment| { - div().child(segment.text.clone()).text_color(segment.color) - }))]; - - let is_last_segment = ix == symbols_len - 1; - if !is_last_segment { - items.push(self.render_separator(&theme)); - } - - items - }) - .collect::>(), - ), - ) - } -} diff --git a/crates/ui/src/components/buffer.rs b/crates/ui/src/components/buffer.rs deleted file mode 100644 index 00e5daee55..0000000000 --- a/crates/ui/src/components/buffer.rs +++ /dev/null @@ -1,233 +0,0 @@ -use gpui2::{Hsla, WindowContext}; - -use crate::prelude::*; -use crate::{h_stack, theme, v_stack, Icon, IconElement}; - -#[derive(Default, PartialEq, Copy, Clone)] -pub struct PlayerCursor { - color: Hsla, - index: usize, -} - -#[derive(Default, PartialEq, Clone)] -pub struct HighlightedText { - pub text: String, - pub color: Hsla, -} - -#[derive(Default, PartialEq, Clone)] -pub struct HighlightedLine { - pub highlighted_texts: Vec, -} - -#[derive(Default, PartialEq, Clone)] -pub struct BufferRow { - pub line_number: usize, - pub code_action: bool, - pub current: bool, - pub line: Option, - pub cursors: Option>, - pub status: GitStatus, - pub show_line_number: bool, -} - -#[derive(Clone)] -pub struct BufferRows { - pub show_line_numbers: bool, - pub rows: Vec, -} - -impl Default for BufferRows { - fn default() -> Self { - Self { - show_line_numbers: true, - rows: vec![BufferRow { - line_number: 1, - code_action: false, - current: true, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number: true, - }], - } - } -} - -impl BufferRow { - pub fn new(line_number: usize) -> Self { - Self { - line_number, - code_action: false, - current: false, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number: true, - } - } - - pub fn set_line(mut self, line: Option) -> Self { - self.line = line; - self - } - - pub fn set_cursors(mut self, cursors: Option>) -> Self { - self.cursors = cursors; - self - } - - pub fn add_cursor(mut self, cursor: PlayerCursor) -> Self { - if let Some(cursors) = &mut self.cursors { - cursors.push(cursor); - } else { - self.cursors = Some(vec![cursor]); - } - self - } - - pub fn set_status(mut self, status: GitStatus) -> Self { - self.status = status; - self - } - - pub fn set_show_line_number(mut self, show_line_number: bool) -> Self { - self.show_line_number = show_line_number; - self - } - - pub fn set_code_action(mut self, code_action: bool) -> Self { - self.code_action = code_action; - self - } - - pub fn set_current(mut self, current: bool) -> Self { - self.current = current; - self - } -} - -#[derive(Element, Clone)] -pub struct Buffer { - scroll_state: ScrollState, - rows: Option, - readonly: bool, - language: Option, - title: Option, - path: Option, -} - -impl Buffer { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - rows: Some(BufferRows::default()), - readonly: false, - language: None, - title: Some("untitled".to_string()), - path: None, - } - } - - pub fn bind_scroll_state(&mut self, scroll_state: ScrollState) { - self.scroll_state = scroll_state; - } - - pub fn set_title>>(mut self, title: T) -> Self { - self.title = title.into(); - self - } - - pub fn set_path>>(mut self, path: P) -> Self { - self.path = path.into(); - self - } - - pub fn set_readonly(mut self, readonly: bool) -> Self { - self.readonly = readonly; - self - } - - pub fn set_rows>>(mut self, rows: R) -> Self { - self.rows = rows.into(); - self - } - - pub fn set_language>>(mut self, language: L) -> Self { - self.language = language.into(); - self - } - - fn render_row(row: BufferRow, cx: &WindowContext) -> impl IntoElement { - let theme = theme(cx); - let system_color = SystemColor::new(); - - let line_background = if row.current { - theme.middle.base.default.background - } else { - system_color.transparent - }; - - let line_number_color = if row.current { - HighlightColor::Default.hsla(&theme) - } else { - HighlightColor::Comment.hsla(&theme) - }; - - h_stack() - .fill(line_background) - .w_full() - .gap_2() - .px_1() - .child( - h_stack() - .w_4() - .h_full() - .px_0p5() - .when(row.code_action, |c| { - div().child(IconElement::new(Icon::Bolt)) - }), - ) - .when(row.show_line_number, |this| { - this.child( - h_stack().justify_end().px_0p5().w_3().child( - div() - .text_color(line_number_color) - .child(row.line_number.to_string()), - ), - ) - }) - .child(div().mx_0p5().w_1().h_full().fill(row.status.hsla(cx))) - .children(row.line.map(|line| { - div() - .flex() - .children(line.highlighted_texts.iter().map(|highlighted_text| { - div() - .text_color(highlighted_text.color) - .child(highlighted_text.text.clone()) - })) - })) - } - - fn render_rows(&self, cx: &WindowContext) -> Vec> { - match &self.rows { - Some(rows) => rows - .rows - .iter() - .map(|row| Self::render_row(row.clone(), cx)) - .collect(), - None => vec![], - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let rows = self.render_rows(cx); - v_stack() - .flex_1() - .w_full() - .h_full() - .fill(theme.highest.base.default.background) - .children(rows) - } -} diff --git a/crates/ui/src/components/chat_panel.rs b/crates/ui/src/components/chat_panel.rs deleted file mode 100644 index 5ae66967b6..0000000000 --- a/crates/ui/src/components/chat_panel.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::marker::PhantomData; - -use chrono::NaiveDateTime; - -use crate::prelude::*; -use crate::theme::theme; -use crate::{Icon, IconButton, Input, Label, LabelColor}; - -#[derive(Element)] -pub struct ChatPanel { - view_type: PhantomData, - scroll_state: ScrollState, - messages: Vec, -} - -impl ChatPanel { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - messages: Vec::new(), - } - } - - pub fn with_messages(mut self, messages: Vec) -> Self { - self.messages = messages; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_col() - .justify_between() - .h_full() - .px_2() - .gap_2() - // Header - .child( - div() - .flex() - .justify_between() - .py_2() - .child(div().flex().child(Label::new("#design"))) - .child( - div() - .flex() - .items_center() - .gap_px() - .child(IconButton::new(Icon::File)) - .child(IconButton::new(Icon::AudioOn)), - ), - ) - .child( - div() - .flex() - .flex_col() - // Chat Body - .child( - div() - .w_full() - .flex() - .flex_col() - .gap_3() - .overflow_y_scroll(self.scroll_state.clone()) - .children(self.messages.clone()), - ) - // Composer - .child(div().flex().my_2().child(Input::new("Message #design"))), - ) - } -} - -#[derive(Element, Clone)] -pub struct ChatMessage { - author: String, - text: String, - sent_at: NaiveDateTime, -} - -impl ChatMessage { - pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self { - Self { - author, - text, - sent_at, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div() - .flex() - .flex_col() - .child( - div() - .flex() - .gap_2() - .child(Label::new(self.author.clone())) - .child( - Label::new(self.sent_at.format("%m/%d/%Y").to_string()) - .color(LabelColor::Muted), - ), - ) - .child(div().child(Label::new(self.text.clone()))) - } -} diff --git a/crates/ui/src/components/collab_panel.rs b/crates/ui/src/components/collab_panel.rs deleted file mode 100644 index 14dd43294f..0000000000 --- a/crates/ui/src/components/collab_panel.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::elements::{img, svg}; -use gpui2::ArcCow; - -use crate::prelude::*; -use crate::theme::{theme, Theme}; -use crate::{ - static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List, - ListHeader, ToggleState, -}; - -#[derive(Element)] -pub struct CollabPanel { - view_type: PhantomData, - scroll_state: ScrollState, -} - -impl CollabPanel { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - v_stack() - .w_64() - .h_full() - .fill(theme.middle.base.default.background) - .child( - v_stack() - .w_full() - .overflow_y_scroll(self.scroll_state.clone()) - .child( - div() - .fill(theme.lowest.base.default.background) - .pb_1() - .border_color(theme.lowest.base.default.border) - .border_b() - .child( - List::new(static_collab_panel_current_call()) - .header( - ListHeader::new("CRDB") - .left_icon(Icon::Hash.into()) - .set_toggle(ToggleState::Toggled), - ) - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - v_stack().py_1().child( - List::new(static_collab_panel_channels()) - .header( - ListHeader::new("CHANNELS").set_toggle(ToggleState::Toggled), - ) - .empty_message("No channels yet. Add a channel to get started.") - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - v_stack().py_1().child( - List::new(static_collab_panel_current_call()) - .header( - ListHeader::new("CONTACTS – ONLINE") - .set_toggle(ToggleState::Toggled), - ) - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - v_stack().py_1().child( - List::new(static_collab_panel_current_call()) - .header( - ListHeader::new("CONTACTS – OFFLINE") - .set_toggle(ToggleState::NotToggled), - ) - .set_toggle(ToggleState::NotToggled), - ), - ), - ) - .child( - div() - .h_7() - .px_2() - .border_t() - .border_color(theme.middle.variant.default.border) - .flex() - .items_center() - .child( - div() - .text_sm() - .text_color(theme.middle.variant.default.foreground) - .child("Find..."), - ), - ) - } - - fn list_section_header( - &self, - label: impl Into>, - expanded: bool, - theme: &Theme, - ) -> impl Element { - div() - .h_7() - .px_2() - .flex() - .justify_between() - .items_center() - .child(div().flex().gap_1().text_sm().child(label)) - .child( - div().flex().h_full().gap_1().items_center().child( - svg() - .path(if expanded { - "icons/caret_down.svg" - } else { - "icons/caret_up.svg" - }) - .w_3p5() - .h_3p5() - .fill(theme.middle.variant.default.foreground), - ), - ) - } - - fn list_item( - &self, - avatar_uri: impl Into>, - label: impl Into>, - theme: &Theme, - ) -> impl Element { - div() - .h_7() - .px_2() - .flex() - .items_center() - .hover() - .fill(theme.lowest.variant.hovered.background) - .active() - .fill(theme.lowest.variant.pressed.background) - .child( - div() - .flex() - .items_center() - .gap_1() - .text_sm() - .child( - img() - .uri(avatar_uri) - .size_3p5() - .rounded_full() - .fill(theme.middle.positive.default.foreground), - ) - .child(label), - ) - } -} diff --git a/crates/ui/src/components/command_palette.rs b/crates/ui/src/components/command_palette.rs deleted file mode 100644 index 797876c05e..0000000000 --- a/crates/ui/src/components/command_palette.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{example_editor_actions, OrderMethod, Palette}; - -#[derive(Element)] -pub struct CommandPalette { - view_type: PhantomData, - scroll_state: ScrollState, -} - -impl CommandPalette { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(example_editor_actions()) - .placeholder("Execute a command...") - .empty_string("No items found.") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs deleted file mode 100644 index 0808bf6556..0000000000 --- a/crates/ui/src/components/context_menu.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::prelude::*; -use crate::theme::theme; -use crate::{ - v_stack, Label, List, ListEntry, ListItem, ListItemVariant, ListSeparator, ListSubHeader, -}; - -#[derive(Clone)] -pub enum ContextMenuItem { - Header(&'static str), - Entry(Label), - Separator, -} - -impl ContextMenuItem { - fn to_list_item(self) -> ListItem { - match self { - ContextMenuItem::Header(label) => ListSubHeader::new(label).into(), - ContextMenuItem::Entry(label) => { - ListEntry::new(label).variant(ListItemVariant::Inset).into() - } - ContextMenuItem::Separator => ListSeparator::new().into(), - } - } - pub fn header(label: &'static str) -> Self { - Self::Header(label) - } - pub fn separator() -> Self { - Self::Separator - } - pub fn entry(label: Label) -> Self { - Self::Entry(label) - } -} - -#[derive(Element)] -pub struct ContextMenu { - items: Vec, -} - -impl ContextMenu { - pub fn new(items: impl IntoIterator) -> Self { - Self { - items: items.into_iter().collect(), - } - } - fn render(&mut self, view: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - v_stack() - .flex() - .fill(theme.lowest.base.default.background) - .border() - .border_color(theme.lowest.base.default.border) - .child( - List::new( - self.items - .clone() - .into_iter() - .map(ContextMenuItem::to_list_item) - .collect(), - ) - .set_toggle(ToggleState::Toggled), - ) - //div().p_1().children(self.items.clone()) - } -} diff --git a/crates/ui/src/components/editor_pane.rs b/crates/ui/src/components/editor_pane.rs deleted file mode 100644 index 561081164c..0000000000 --- a/crates/ui/src/components/editor_pane.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::marker::PhantomData; -use std::path::PathBuf; - -use crate::prelude::*; -use crate::{v_stack, Breadcrumb, Buffer, Icon, IconButton, Symbol, Tab, TabBar, Toolbar}; - -pub struct Editor { - pub tabs: Vec, - pub path: PathBuf, - pub symbols: Vec, - pub buffer: Buffer, -} - -#[derive(Element)] -pub struct EditorPane { - view_type: PhantomData, - editor: Editor, -} - -impl EditorPane { - pub fn new(editor: Editor) -> Self { - Self { - view_type: PhantomData, - editor, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - struct LeftItemsPayload { - path: PathBuf, - symbols: Vec, - } - - v_stack() - .w_full() - .h_full() - .flex_1() - .child(TabBar::new(self.editor.tabs.clone())) - .child(Toolbar::new( - |_, payload| { - let payload = payload.downcast_ref::().unwrap(); - - vec![Breadcrumb::new(payload.path.clone(), payload.symbols.clone()).into_any()] - }, - Box::new(LeftItemsPayload { - path: self.editor.path.clone(), - symbols: self.editor.symbols.clone(), - }), - |_, _| { - vec![ - IconButton::new(Icon::InlayHint).into_any(), - IconButton::new(Icon::MagnifyingGlass).into_any(), - IconButton::new(Icon::MagicWand).into_any(), - ] - }, - Box::new(()), - )) - .child(self.editor.buffer.clone()) - } -} diff --git a/crates/ui/src/components/facepile.rs b/crates/ui/src/components/facepile.rs deleted file mode 100644 index 949e226c25..0000000000 --- a/crates/ui/src/components/facepile.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::prelude::*; -use crate::{theme, Avatar, Player}; - -#[derive(Element)] -pub struct Facepile { - players: Vec, -} - -impl Facepile { - pub fn new>(players: P) -> Self { - Self { - players: players.collect(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let player_count = self.players.len(); - let player_list = self.players.iter().enumerate().map(|(ix, player)| { - let isnt_last = ix < player_count - 1; - - div() - .when(isnt_last, |div| div.neg_mr_1()) - .child(Avatar::new(player.avatar_src().to_string())) - }); - div().p_1().flex().items_center().children(player_list) - } -} diff --git a/crates/ui/src/components/icon_button.rs b/crates/ui/src/components/icon_button.rs deleted file mode 100644 index 8ac122d3eb..0000000000 --- a/crates/ui/src/components/icon_button.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::prelude::*; -use crate::{theme, Icon, IconColor, IconElement}; - -#[derive(Element)] -pub struct IconButton { - icon: Icon, - color: IconColor, - variant: ButtonVariant, - state: InteractionState, -} - -impl IconButton { - pub fn new(icon: Icon) -> Self { - Self { - icon, - color: IconColor::default(), - variant: ButtonVariant::default(), - state: InteractionState::default(), - } - } - - pub fn icon(mut self, icon: Icon) -> Self { - self.icon = icon; - self - } - - pub fn color(mut self, color: IconColor) -> Self { - self.color = color; - self - } - - pub fn variant(mut self, variant: ButtonVariant) -> Self { - self.variant = variant; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let icon_color = match (self.state, self.color) { - (InteractionState::Disabled, _) => IconColor::Disabled, - _ => self.color, - }; - - let mut div = div(); - if self.variant == ButtonVariant::Filled { - div = div.fill(theme.highest.on.default.background); - } - - div.w_7() - .h_6() - .flex() - .items_center() - .justify_center() - .rounded_md() - .hover() - .fill(theme.highest.base.hovered.background) - .active() - .fill(theme.highest.base.pressed.background) - .child(IconElement::new(self.icon).color(icon_color)) - } -} diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs deleted file mode 100644 index 63c5f11039..0000000000 --- a/crates/ui/src/components/keybinding.rs +++ /dev/null @@ -1,158 +0,0 @@ -use std::collections::HashSet; - -use strum::{EnumIter, IntoEnumIterator}; - -use crate::prelude::*; -use crate::theme; - -#[derive(Element, Clone)] -pub struct Keybinding { - /// A keybinding consists of a key and a set of modifier keys. - /// More then one keybinding produces a chord. - /// - /// This should always contain at least one element. - keybinding: Vec<(String, ModifierKeys)>, -} - -impl Keybinding { - pub fn new(key: String, modifiers: ModifierKeys) -> Self { - Self { - keybinding: vec![(key, modifiers)], - } - } - - pub fn new_chord( - first_note: (String, ModifierKeys), - second_note: (String, ModifierKeys), - ) -> Self { - Self { - keybinding: vec![first_note, second_note], - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div() - .flex() - .gap_2() - .children(self.keybinding.iter().map(|(key, modifiers)| { - div() - .flex() - .gap_1() - .children(ModifierKey::iter().filter_map(|modifier| { - if modifiers.0.contains(&modifier) { - Some(Key::new(modifier.glyph())) - } else { - None - } - })) - .child(Key::new(key.clone())) - })) - } -} - -#[derive(Element)] -pub struct Key { - key: String, -} - -impl Key { - pub fn new(key: K) -> Self - where - K: Into, - { - Self { key: key.into() } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .px_2() - .py_0() - .rounded_md() - .text_sm() - .text_color(theme.lowest.on.default.foreground) - .fill(theme.lowest.on.default.background) - .child(self.key.clone()) - } -} - -// NOTE: The order the modifier keys appear in this enum impacts the order in -// which they are rendered in the UI. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum ModifierKey { - Control, - Alt, - Command, - Shift, -} - -impl ModifierKey { - /// Returns the glyph for the [`ModifierKey`]. - pub fn glyph(&self) -> char { - match self { - Self::Control => '^', - Self::Alt => '⌥', - Self::Command => '⌘', - Self::Shift => '⇧', - } - } -} - -#[derive(Clone)] -pub struct ModifierKeys(HashSet); - -impl ModifierKeys { - pub fn new() -> Self { - Self(HashSet::new()) - } - - pub fn all() -> Self { - Self(HashSet::from_iter(ModifierKey::iter())) - } - - pub fn add(mut self, modifier: ModifierKey) -> Self { - self.0.insert(modifier); - self - } - - pub fn control(mut self, control: bool) -> Self { - if control { - self.0.insert(ModifierKey::Control); - } else { - self.0.remove(&ModifierKey::Control); - } - - self - } - - pub fn alt(mut self, alt: bool) -> Self { - if alt { - self.0.insert(ModifierKey::Alt); - } else { - self.0.remove(&ModifierKey::Alt); - } - - self - } - - pub fn command(mut self, command: bool) -> Self { - if command { - self.0.insert(ModifierKey::Command); - } else { - self.0.remove(&ModifierKey::Command); - } - - self - } - - pub fn shift(mut self, shift: bool) -> Self { - if shift { - self.0.insert(ModifierKey::Shift); - } else { - self.0.remove(&ModifierKey::Shift); - } - - self - } -} diff --git a/crates/ui/src/components/language_selector.rs b/crates/ui/src/components/language_selector.rs deleted file mode 100644 index 124d7f13ee..0000000000 --- a/crates/ui/src/components/language_selector.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::prelude::*; -use crate::{OrderMethod, Palette, PaletteItem}; - -#[derive(Element)] -pub struct LanguageSelector { - scroll_state: ScrollState, -} - -impl LanguageSelector { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(vec![ - PaletteItem::new("C"), - PaletteItem::new("C++"), - PaletteItem::new("CSS"), - PaletteItem::new("Elixir"), - PaletteItem::new("Elm"), - PaletteItem::new("ERB"), - PaletteItem::new("Rust (current)"), - PaletteItem::new("Scheme"), - PaletteItem::new("TOML"), - PaletteItem::new("TypeScript"), - ]) - .placeholder("Select a language...") - .empty_string("No matches") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/list.rs b/crates/ui/src/components/list.rs deleted file mode 100644 index b7dff6b2c5..0000000000 --- a/crates/ui/src/components/list.rs +++ /dev/null @@ -1,512 +0,0 @@ -use gpui2::elements::div::Div; -use gpui2::{Hsla, WindowContext}; - -use crate::prelude::*; -use crate::{ - h_stack, theme, token, v_stack, Avatar, DisclosureControlVisibility, Icon, IconColor, - IconElement, IconSize, InteractionState, Label, LabelColor, LabelSize, SystemColor, - ToggleState, -}; - -#[derive(Clone, Copy, Default, Debug, PartialEq)] -pub enum ListItemVariant { - /// The list item extends to the far left and right of the list. - #[default] - FullWidth, - Inset, -} - -#[derive(Element, Clone, Copy)] -pub struct ListHeader { - label: &'static str, - left_icon: Option, - variant: ListItemVariant, - state: InteractionState, - toggleable: Toggleable, -} - -impl ListHeader { - pub fn new(label: &'static str) -> Self { - Self { - label, - left_icon: None, - variant: ListItemVariant::default(), - state: InteractionState::default(), - toggleable: Toggleable::default(), - } - } - - pub fn set_toggle(mut self, toggle: ToggleState) -> Self { - self.toggleable = toggle.into(); - self - } - - pub fn set_toggleable(mut self, toggleable: Toggleable) -> Self { - self.toggleable = toggleable; - self - } - - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - fn disclosure_control(&self) -> Div { - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = Toggleable::is_toggled(&self.toggleable); - - match (is_toggleable, is_toggled) { - (false, _) => div(), - (_, true) => div().child(IconElement::new(Icon::ChevronRight).color(IconColor::Muted)), - (_, false) => div().child(IconElement::new(Icon::ChevronDown).size(IconSize::Small)), - } - } - - fn background_color(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self.state { - InteractionState::Hovered => theme.lowest.base.hovered.background, - InteractionState::Active => theme.lowest.base.pressed.background, - InteractionState::Enabled => theme.lowest.on.default.background, - _ => system_color.transparent, - } - } - - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - let system_color = SystemColor::new(); - let background_color = self.background_color(cx); - - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = Toggleable::is_toggled(&self.toggleable); - - let disclosure_control = self.disclosure_control(); - - h_stack() - .flex_1() - .w_full() - .fill(background_color) - .when(self.state == InteractionState::Focused, |this| { - this.border() - .border_color(theme.lowest.accent.default.border) - }) - .relative() - .py_1() - .child( - div() - .h_6() - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) - .flex() - .flex_1() - .w_full() - .gap_1() - .items_center() - .justify_between() - .child( - div() - .flex() - .gap_1() - .items_center() - .children(self.left_icon.map(|i| { - IconElement::new(i) - .color(IconColor::Muted) - .size(IconSize::Small) - })) - .child( - Label::new(self.label) - .color(LabelColor::Muted) - .size(LabelSize::Small), - ), - ) - .child(disclosure_control), - ) - } -} - -#[derive(Element, Clone, Copy)] -pub struct ListSubHeader { - label: &'static str, - left_icon: Option, - variant: ListItemVariant, -} - -impl ListSubHeader { - pub fn new(label: &'static str) -> Self { - Self { - label, - left_icon: None, - variant: ListItemVariant::default(), - } - } - - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - - h_stack().flex_1().w_full().relative().py_1().child( - div() - .h_6() - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) - .flex() - .flex_1() - .w_full() - .gap_1() - .items_center() - .justify_between() - .child( - div() - .flex() - .gap_1() - .items_center() - .children(self.left_icon.map(|i| { - IconElement::new(i) - .color(IconColor::Muted) - .size(IconSize::Small) - })) - .child( - Label::new(self.label) - .color(LabelColor::Muted) - .size(LabelSize::Small), - ), - ), - ) - } -} - -#[derive(Clone)] -pub enum LeftContent { - Icon(Icon), - Avatar(&'static str), -} - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum ListEntrySize { - #[default] - Small, - Medium, -} - -#[derive(Clone, Element)] -pub enum ListItem { - Entry(ListEntry), - Separator(ListSeparator), - Header(ListSubHeader), -} - -impl From for ListItem { - fn from(entry: ListEntry) -> Self { - Self::Entry(entry) - } -} - -impl From for ListItem { - fn from(entry: ListSeparator) -> Self { - Self::Separator(entry) - } -} - -impl From for ListItem { - fn from(entry: ListSubHeader) -> Self { - Self::Header(entry) - } -} - -impl ListItem { - fn render(&mut self, v: &mut V, cx: &mut ViewContext) -> impl IntoElement { - match self { - ListItem::Entry(entry) => div().child(entry.render(v, cx)), - ListItem::Separator(separator) => div().child(separator.render(v, cx)), - ListItem::Header(header) => div().child(header.render(v, cx)), - } - } - pub fn new(label: Label) -> Self { - Self::Entry(ListEntry::new(label)) - } - pub fn as_entry(&mut self) -> Option<&mut ListEntry> { - if let Self::Entry(entry) = self { - Some(entry) - } else { - None - } - } -} - -#[derive(Element, Clone)] -pub struct ListEntry { - disclosure_control_style: DisclosureControlVisibility, - indent_level: u32, - label: Label, - left_content: Option, - variant: ListItemVariant, - size: ListEntrySize, - state: InteractionState, - toggle: Option, -} - -impl ListEntry { - pub fn new(label: Label) -> Self { - Self { - disclosure_control_style: DisclosureControlVisibility::default(), - indent_level: 0, - label, - variant: ListItemVariant::default(), - left_content: None, - size: ListEntrySize::default(), - state: InteractionState::default(), - toggle: None, - } - } - pub fn variant(mut self, variant: ListItemVariant) -> Self { - self.variant = variant; - self - } - pub fn indent_level(mut self, indent_level: u32) -> Self { - self.indent_level = indent_level; - self - } - - pub fn set_toggle(mut self, toggle: ToggleState) -> Self { - self.toggle = Some(toggle); - self - } - - pub fn left_content(mut self, left_content: LeftContent) -> Self { - self.left_content = Some(left_content); - self - } - - pub fn left_icon(mut self, left_icon: Icon) -> Self { - self.left_content = Some(LeftContent::Icon(left_icon)); - self - } - - pub fn left_avatar(mut self, left_avatar: &'static str) -> Self { - self.left_content = Some(LeftContent::Avatar(left_avatar)); - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - pub fn size(mut self, size: ListEntrySize) -> Self { - self.size = size; - self - } - - pub fn disclosure_control_style( - mut self, - disclosure_control_style: DisclosureControlVisibility, - ) -> Self { - self.disclosure_control_style = disclosure_control_style; - self - } - - fn background_color(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self.state { - InteractionState::Hovered => theme.lowest.base.hovered.background, - InteractionState::Active => theme.lowest.base.pressed.background, - InteractionState::Enabled => theme.lowest.on.default.background, - _ => system_color.transparent, - } - } - - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - - fn disclosure_control( - &mut self, - cx: &mut ViewContext, - ) -> Option> { - let theme = theme(cx); - let token = token(); - - let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle { - IconElement::new(Icon::ChevronDown) - } else { - IconElement::new(Icon::ChevronRight) - } - .color(IconColor::Muted) - .size(IconSize::Small); - - match (self.toggle, self.disclosure_control_style) { - (Some(_), DisclosureControlVisibility::OnHover) => { - Some(div().absolute().neg_left_5().child(disclosure_control_icon)) - } - (Some(_), DisclosureControlVisibility::Always) => { - Some(div().child(disclosure_control_icon)) - } - (None, _) => None, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - let system_color = SystemColor::new(); - let background_color = self.background_color(cx); - - let left_content = match self.left_content { - Some(LeftContent::Icon(i)) => { - Some(h_stack().child(IconElement::new(i).size(IconSize::Small))) - } - Some(LeftContent::Avatar(src)) => Some(h_stack().child(Avatar::new(src))), - None => None, - }; - - let sized_item = match self.size { - ListEntrySize::Small => div().h_6(), - ListEntrySize::Medium => div().h_7(), - }; - - div() - .fill(background_color) - .when(self.state == InteractionState::Focused, |this| { - this.border() - .border_color(theme.lowest.accent.default.border) - }) - .relative() - .py_1() - .child( - sized_item - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) - // .ml(rems(0.75 * self.indent_level as f32)) - .children((0..self.indent_level).map(|_| { - div() - .w(token.list_indent_depth) - .h_full() - .flex() - .justify_center() - .child(h_stack().child(div().w_px().h_full()).child( - div().w_px().h_full().fill(theme.middle.base.default.border), - )) - })) - .flex() - .gap_1() - .items_center() - .relative() - .children(self.disclosure_control(cx)) - .children(left_content) - .child(self.label.clone()), - ) - } -} - -#[derive(Clone, Default, Element)] -pub struct ListSeparator; - -impl ListSeparator { - pub fn new() -> Self { - Self::default() - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div().h_px().w_full().fill(theme.lowest.base.default.border) - } -} - -#[derive(Element)] -pub struct List { - items: Vec, - empty_message: &'static str, - header: Option, - toggleable: Toggleable, -} - -impl List { - pub fn new(items: Vec) -> Self { - Self { - items, - empty_message: "No items", - header: None, - toggleable: Toggleable::default(), - } - } - - pub fn empty_message(mut self, empty_message: &'static str) -> Self { - self.empty_message = empty_message; - self - } - - pub fn header(mut self, header: ListHeader) -> Self { - self.header = Some(header); - self - } - - pub fn set_toggle(mut self, toggle: ToggleState) -> Self { - self.toggleable = toggle.into(); - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = Toggleable::is_toggled(&self.toggleable); - - let disclosure_control = if is_toggleable { - IconElement::new(Icon::ChevronRight) - } else { - IconElement::new(Icon::ChevronDown) - }; - - let list_content = match (self.items.is_empty(), is_toggled) { - (_, false) => div(), - (false, _) => div().children(self.items.iter().cloned()), - (true, _) => div().child(Label::new(self.empty_message).color(LabelColor::Muted)), - }; - - v_stack() - .py_1() - .children( - self.header - .clone() - .map(|header| header.set_toggleable(self.toggleable)), - ) - .child(list_content) - } -} diff --git a/crates/ui/src/components/multi_buffer.rs b/crates/ui/src/components/multi_buffer.rs deleted file mode 100644 index d38603457a..0000000000 --- a/crates/ui/src/components/multi_buffer.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{v_stack, Buffer, Icon, IconButton, Label, LabelSize}; - -#[derive(Element)] -pub struct MultiBuffer { - view_type: PhantomData, - buffers: Vec, -} - -impl MultiBuffer { - pub fn new(buffers: Vec) -> Self { - Self { - view_type: PhantomData, - buffers, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - v_stack() - .w_full() - .h_full() - .flex_1() - .children(self.buffers.clone().into_iter().map(|buffer| { - v_stack() - .child( - div() - .flex() - .items_center() - .justify_between() - .p_4() - .fill(theme.lowest.base.default.background) - .child(Label::new("main.rs").size(LabelSize::Small)) - .child(IconButton::new(Icon::ArrowUpRight)), - ) - .child(buffer) - })) - } -} diff --git a/crates/ui/src/components/palette.rs b/crates/ui/src/components/palette.rs deleted file mode 100644 index 16001e50c1..0000000000 --- a/crates/ui/src/components/palette.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::theme::theme; -use crate::{h_stack, v_stack, Keybinding, Label, LabelColor}; - -#[derive(Element)] -pub struct Palette { - view_type: PhantomData, - scroll_state: ScrollState, - input_placeholder: &'static str, - empty_string: &'static str, - items: Vec, - default_order: OrderMethod, -} - -impl Palette { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - input_placeholder: "Find something...", - empty_string: "No items found.", - items: vec![], - default_order: OrderMethod::default(), - } - } - - pub fn items(mut self, items: Vec) -> Self { - self.items = items; - self - } - - pub fn placeholder(mut self, input_placeholder: &'static str) -> Self { - self.input_placeholder = input_placeholder; - self - } - - pub fn empty_string(mut self, empty_string: &'static str) -> Self { - self.empty_string = empty_string; - self - } - - // TODO: Hook up sort order - pub fn default_order(mut self, default_order: OrderMethod) -> Self { - self.default_order = default_order; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - v_stack() - .w_96() - .rounded_lg() - .fill(theme.lowest.base.default.background) - .border() - .border_color(theme.lowest.base.default.border) - .child( - v_stack() - .gap_px() - .child(v_stack().py_0p5().px_1().child( - div().px_2().py_0p5().child( - Label::new(self.input_placeholder).color(LabelColor::Placeholder), - ), - )) - .child(div().h_px().w_full().fill(theme.lowest.base.default.border)) - .child( - v_stack() - .py_0p5() - .px_1() - .grow() - .max_h_96() - .overflow_y_scroll(self.scroll_state.clone()) - .children( - vec![if self.items.is_empty() { - Some(h_stack().justify_between().px_2().py_1().child( - Label::new(self.empty_string).color(LabelColor::Muted), - )) - } else { - None - }] - .into_iter() - .flatten(), - ) - .children(self.items.iter().map(|item| { - h_stack() - .justify_between() - .px_2() - .py_0p5() - .rounded_lg() - .hover() - .fill(theme.lowest.base.hovered.background) - .active() - .fill(theme.lowest.base.pressed.background) - .child(item.clone()) - })), - ), - ) - } -} - -#[derive(Element, Clone)] -pub struct PaletteItem { - pub label: &'static str, - pub sublabel: Option<&'static str>, - pub keybinding: Option, -} - -impl PaletteItem { - pub fn new(label: &'static str) -> Self { - Self { - label, - sublabel: None, - keybinding: None, - } - } - - pub fn label(mut self, label: &'static str) -> Self { - self.label = label; - self - } - - pub fn sublabel>>(mut self, sublabel: L) -> Self { - self.sublabel = sublabel.into(); - self - } - - pub fn keybinding(mut self, keybinding: K) -> Self - where - K: Into>, - { - self.keybinding = keybinding.into(); - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_row() - .grow() - .justify_between() - .child( - v_stack() - .child(Label::new(self.label)) - .children(self.sublabel.map(|sublabel| Label::new(sublabel))), - ) - .children(self.keybinding.clone()) - } -} diff --git a/crates/ui/src/components/panel.rs b/crates/ui/src/components/panel.rs deleted file mode 100644 index cbcf502670..0000000000 --- a/crates/ui/src/components/panel.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::geometry::AbsoluteLength; - -use crate::prelude::*; -use crate::{theme, token, v_stack}; - -#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub enum PanelAllowedSides { - LeftOnly, - RightOnly, - BottomOnly, - #[default] - LeftAndRight, - All, -} - -impl PanelAllowedSides { - /// Return a `HashSet` that contains the allowable `PanelSide`s. - pub fn allowed_sides(&self) -> HashSet { - match self { - Self::LeftOnly => HashSet::from_iter([PanelSide::Left]), - Self::RightOnly => HashSet::from_iter([PanelSide::Right]), - Self::BottomOnly => HashSet::from_iter([PanelSide::Bottom]), - Self::LeftAndRight => HashSet::from_iter([PanelSide::Left, PanelSide::Right]), - Self::All => HashSet::from_iter([PanelSide::Left, PanelSide::Right, PanelSide::Bottom]), - } - } -} - -#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub enum PanelSide { - #[default] - Left, - Right, - Bottom, -} - -use std::collections::HashSet; - -#[derive(Element)] -pub struct Panel { - view_type: PhantomData, - scroll_state: ScrollState, - current_side: PanelSide, - /// Defaults to PanelAllowedSides::LeftAndRight - allowed_sides: PanelAllowedSides, - initial_width: AbsoluteLength, - width: Option, - children: HackyChildren, - payload: HackyChildrenPayload, -} - -impl Panel { - pub fn new( - scroll_state: ScrollState, - children: HackyChildren, - payload: HackyChildrenPayload, - ) -> Self { - let token = token(); - - Self { - view_type: PhantomData, - scroll_state, - current_side: PanelSide::default(), - allowed_sides: PanelAllowedSides::default(), - initial_width: token.default_panel_size, - width: None, - children, - payload, - } - } - - pub fn initial_width(mut self, initial_width: AbsoluteLength) -> Self { - self.initial_width = initial_width; - self - } - - pub fn width(mut self, width: AbsoluteLength) -> Self { - self.width = Some(width); - self - } - - pub fn allowed_sides(mut self, allowed_sides: PanelAllowedSides) -> Self { - self.allowed_sides = allowed_sides; - self - } - - pub fn side(mut self, side: PanelSide) -> Self { - let allowed_sides = self.allowed_sides.allowed_sides(); - - if allowed_sides.contains(&side) { - self.current_side = side; - } else { - panic!( - "The panel side {:?} was not added as allowed before it was set.", - side - ); - } - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let token = token(); - let theme = theme(cx); - - let panel_base; - let current_width = self.width.unwrap_or(self.initial_width); - - match self.current_side { - PanelSide::Left => { - panel_base = v_stack() - .flex_initial() - .h_full() - .w(current_width) - .fill(theme.middle.base.default.background) - .border_r() - .border_color(theme.middle.base.default.border); - } - PanelSide::Right => { - panel_base = v_stack() - .flex_initial() - .h_full() - .w(current_width) - .fill(theme.middle.base.default.background) - .border_l() - .border_color(theme.middle.base.default.border); - } - PanelSide::Bottom => { - panel_base = v_stack() - .flex_initial() - .w_full() - .h(current_width) - .fill(theme.middle.base.default.background) - .border_t() - .border_color(theme.middle.base.default.border); - } - } - - panel_base.children_any((self.children)(cx, self.payload.as_ref())) - } -} diff --git a/crates/ui/src/components/panes.rs b/crates/ui/src/components/panes.rs deleted file mode 100644 index 2518eea8ee..0000000000 --- a/crates/ui/src/components/panes.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::geometry::{Length, Size}; -use gpui2::{hsla, Hsla}; - -use crate::prelude::*; -use crate::theme; - -#[derive(Default, PartialEq)] -pub enum SplitDirection { - #[default] - Horizontal, - Vertical, -} - -#[derive(Element)] -pub struct Pane { - view_type: PhantomData, - scroll_state: ScrollState, - size: Size, - fill: Hsla, - children: HackyChildren, - payload: HackyChildrenPayload, -} - -impl Pane { - pub fn new( - scroll_state: ScrollState, - size: Size, - children: HackyChildren, - payload: HackyChildrenPayload, - ) -> Self { - // Fill is only here for debugging purposes, remove before release - let system_color = SystemColor::new(); - - Self { - view_type: PhantomData, - scroll_state, - size, - fill: hsla(0.3, 0.3, 0.3, 1.), - // fill: system_color.transparent, - children, - payload, - } - } - - pub fn fill(mut self, fill: Hsla) -> Self { - self.fill = fill; - self - } - - fn render(&mut self, view: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_initial() - .fill(self.fill) - .w(self.size.width) - .h(self.size.height) - .overflow_y_scroll(self.scroll_state.clone()) - .children_any((self.children)(cx, self.payload.as_ref())) - } -} - -#[derive(Element)] -pub struct PaneGroup { - view_type: PhantomData, - groups: Vec>, - panes: Vec>, - split_direction: SplitDirection, -} - -impl PaneGroup { - pub fn new_groups(groups: Vec>, split_direction: SplitDirection) -> Self { - Self { - view_type: PhantomData, - groups, - panes: Vec::new(), - split_direction, - } - } - - pub fn new_panes(panes: Vec>, split_direction: SplitDirection) -> Self { - Self { - view_type: PhantomData, - groups: Vec::new(), - panes, - split_direction, - } - } - - fn render(&mut self, view: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - if !self.panes.is_empty() { - let el = div() - .flex() - .flex_1() - .gap_px() - .w_full() - .h_full() - .fill(theme.lowest.base.default.background) - .children(self.panes.iter_mut().map(|pane| pane.render(view, cx))); - - if self.split_direction == SplitDirection::Horizontal { - return el; - } else { - return el.flex_col(); - } - } - - if !self.groups.is_empty() { - let el = div() - .flex() - .flex_1() - .gap_px() - .w_full() - .h_full() - .fill(theme.lowest.base.default.background) - .children(self.groups.iter_mut().map(|group| group.render(view, cx))); - - if self.split_direction == SplitDirection::Horizontal { - return el; - } else { - return el.flex_col(); - } - } - - unreachable!() - } -} diff --git a/crates/ui/src/components/player_stack.rs b/crates/ui/src/components/player_stack.rs deleted file mode 100644 index 7df6f065fb..0000000000 --- a/crates/ui/src/components/player_stack.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::prelude::*; -use crate::{Avatar, Facepile, PlayerWithCallStatus}; - -#[derive(Element)] -pub struct PlayerStack { - player_with_call_status: PlayerWithCallStatus, -} - -impl PlayerStack { - pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self { - Self { - player_with_call_status, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let system_color = SystemColor::new(); - let player = self.player_with_call_status.get_player(); - self.player_with_call_status.get_call_status(); - - let followers = self - .player_with_call_status - .get_call_status() - .followers - .as_ref() - .map(|followers| followers.clone()); - - // if we have no followers return a slightly different element - // if mic_status == muted add a red ring to avatar - - div() - .h_full() - .flex() - .flex_col() - .gap_px() - .justify_center() - .child( - div().flex().justify_center().w_full().child( - div() - .w_4() - .h_0p5() - .rounded_sm() - .fill(player.cursor_color(cx)), - ), - ) - .child( - div() - .flex() - .items_center() - .justify_center() - .h_6() - .pl_1() - .rounded_lg() - .fill(if followers.is_none() { - system_color.transparent - } else { - player.selection_color(cx) - }) - .child(Avatar::new(player.avatar_src().to_string())) - .children(followers.map(|followers| { - div().neg_ml_2().child(Facepile::new(followers.into_iter())) - })), - ) - } -} diff --git a/crates/ui/src/components/project_panel.rs b/crates/ui/src/components/project_panel.rs deleted file mode 100644 index 1f32c698e5..0000000000 --- a/crates/ui/src/components/project_panel.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{ - static_project_panel_project_items, static_project_panel_single_items, theme, Input, List, - ListHeader, -}; - -#[derive(Element)] -pub struct ProjectPanel { - view_type: PhantomData, - scroll_state: ScrollState, -} - -impl ProjectPanel { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_col() - .w_full() - .h_full() - .px_2() - .fill(theme.middle.base.default.background) - .child( - div() - .w_56() - .flex() - .flex_col() - .overflow_y_scroll(ScrollState::default()) - .child( - List::new(static_project_panel_single_items()) - .header(ListHeader::new("FILES").set_toggle(ToggleState::Toggled)) - .empty_message("No files in directory") - .set_toggle(ToggleState::Toggled), - ) - .child( - List::new(static_project_panel_project_items()) - .header(ListHeader::new("PROJECT").set_toggle(ToggleState::Toggled)) - .empty_message("No folders in directory") - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - Input::new("Find something...") - .value("buffe".to_string()) - .state(InteractionState::Focused), - ) - } -} diff --git a/crates/ui/src/components/recent_projects.rs b/crates/ui/src/components/recent_projects.rs deleted file mode 100644 index 6aca6631b9..0000000000 --- a/crates/ui/src/components/recent_projects.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::prelude::*; -use crate::{OrderMethod, Palette, PaletteItem}; - -#[derive(Element)] -pub struct RecentProjects { - scroll_state: ScrollState, -} - -impl RecentProjects { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(vec![ - PaletteItem::new("zed").sublabel("~/projects/zed"), - PaletteItem::new("saga").sublabel("~/projects/saga"), - PaletteItem::new("journal").sublabel("~/journal"), - PaletteItem::new("dotfiles").sublabel("~/dotfiles"), - PaletteItem::new("zed.dev").sublabel("~/projects/zed.dev"), - PaletteItem::new("laminar").sublabel("~/projects/laminar"), - ]) - .placeholder("Recent Projects...") - .empty_string("No matches") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/status_bar.rs b/crates/ui/src/components/status_bar.rs deleted file mode 100644 index 1970b0bff9..0000000000 --- a/crates/ui/src/components/status_bar.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::theme::{theme, Theme}; -use crate::{Button, Icon, IconButton, IconColor, ToolDivider}; - -#[derive(Default, PartialEq)] -pub enum Tool { - #[default] - ProjectPanel, - CollaborationPanel, - Terminal, - Assistant, - Feedback, - Diagnostics, -} - -struct ToolGroup { - active_index: Option, - tools: Vec, -} - -impl Default for ToolGroup { - fn default() -> Self { - ToolGroup { - active_index: None, - tools: vec![], - } - } -} - -#[derive(Element)] -pub struct StatusBar { - view_type: PhantomData, - left_tools: Option, - right_tools: Option, - bottom_tools: Option, -} - -impl StatusBar { - pub fn new() -> Self { - Self { - view_type: PhantomData, - left_tools: None, - right_tools: None, - bottom_tools: None, - } - } - - pub fn left_tool(mut self, tool: Tool, active_index: Option) -> Self { - self.left_tools = { - let mut tools = vec![tool]; - tools.extend(self.left_tools.take().unwrap_or_default().tools); - Some(ToolGroup { - active_index, - tools, - }) - }; - self - } - - pub fn right_tool(mut self, tool: Tool, active_index: Option) -> Self { - self.right_tools = { - let mut tools = vec![tool]; - tools.extend(self.left_tools.take().unwrap_or_default().tools); - Some(ToolGroup { - active_index, - tools, - }) - }; - self - } - - pub fn bottom_tool(mut self, tool: Tool, active_index: Option) -> Self { - self.bottom_tools = { - let mut tools = vec![tool]; - tools.extend(self.left_tools.take().unwrap_or_default().tools); - Some(ToolGroup { - active_index, - tools, - }) - }; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .py_0p5() - .px_1() - .flex() - .items_center() - .justify_between() - .w_full() - .fill(theme.lowest.base.default.background) - .child(self.left_tools(&theme)) - .child(self.right_tools(&theme)) - } - - fn left_tools(&self, theme: &Theme) -> impl Element { - div() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::FileTree).color(IconColor::Accent)) - .child(IconButton::new(Icon::Hash)) - .child(ToolDivider::new()) - .child(IconButton::new(Icon::XCircle)) - } - fn right_tools(&self, theme: &Theme) -> impl Element { - div() - .flex() - .items_center() - .gap_2() - .child( - div() - .flex() - .items_center() - .gap_1() - .child(Button::new("116:25")) - .child(Button::new("Rust")), - ) - .child(ToolDivider::new()) - .child( - div() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::Copilot)) - .child(IconButton::new(Icon::Envelope)), - ) - .child(ToolDivider::new()) - .child( - div() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::Terminal)) - .child(IconButton::new(Icon::MessageBubbles)) - .child(IconButton::new(Icon::Ai)), - ) - } -} diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs deleted file mode 100644 index 9eb1122775..0000000000 --- a/crates/ui/src/components/tab.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::prelude::*; -use crate::{theme, Icon, IconColor, IconElement, Label, LabelColor}; - -#[derive(Element, Clone)] -pub struct Tab { - title: String, - icon: Option, - current: bool, - dirty: bool, - fs_status: FileSystemStatus, - git_status: GitStatus, - diagnostic_status: DiagnosticStatus, - close_side: IconSide, -} - -impl Tab { - pub fn new() -> Self { - Self { - title: "untitled".to_string(), - icon: None, - current: false, - dirty: false, - fs_status: FileSystemStatus::None, - git_status: GitStatus::None, - diagnostic_status: DiagnosticStatus::None, - close_side: IconSide::Right, - } - } - - pub fn current(mut self, current: bool) -> Self { - self.current = current; - self - } - - pub fn title(mut self, title: String) -> Self { - self.title = title; - self - } - - pub fn icon(mut self, icon: I) -> Self - where - I: Into>, - { - self.icon = icon.into(); - self - } - - pub fn dirty(mut self, dirty: bool) -> Self { - self.dirty = dirty; - self - } - - pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self { - self.fs_status = fs_status; - self - } - - pub fn git_status(mut self, git_status: GitStatus) -> Self { - self.git_status = git_status; - self - } - - pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self { - self.diagnostic_status = diagnostic_status; - self - } - - pub fn close_side(mut self, close_side: IconSide) -> Self { - self.close_side = close_side; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict; - let is_deleted = self.fs_status == FileSystemStatus::Deleted; - - let label = match (self.git_status, is_deleted) { - (_, true) | (GitStatus::Deleted, false) => Label::new(self.title.clone()) - .color(LabelColor::Hidden) - .set_strikethrough(true), - (GitStatus::None, false) => Label::new(self.title.clone()), - (GitStatus::Created, false) => { - Label::new(self.title.clone()).color(LabelColor::Created) - } - (GitStatus::Modified, false) => { - Label::new(self.title.clone()).color(LabelColor::Modified) - } - (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(LabelColor::Accent), - (GitStatus::Conflict, false) => Label::new(self.title.clone()), - }; - - let close_icon = IconElement::new(Icon::Close).color(IconColor::Muted); - - div() - .px_2() - .py_0p5() - .flex() - .items_center() - .justify_center() - .fill(if self.current { - theme.highest.base.default.background - } else { - theme.middle.base.default.background - }) - .child( - div() - .px_1() - .flex() - .items_center() - .gap_1() - .children(has_fs_conflict.then(|| { - IconElement::new(Icon::ExclamationTriangle) - .size(crate::IconSize::Small) - .color(IconColor::Warning) - })) - .children(self.icon.map(IconElement::new)) - .children(if self.close_side == IconSide::Left { - Some(close_icon.clone()) - } else { - None - }) - .child(label) - .children(if self.close_side == IconSide::Right { - Some(close_icon) - } else { - None - }), - ) - } -} diff --git a/crates/ui/src/components/tab_bar.rs b/crates/ui/src/components/tab_bar.rs deleted file mode 100644 index 8addcb87b1..0000000000 --- a/crates/ui/src/components/tab_bar.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{theme, Icon, IconButton, Tab}; - -#[derive(Element)] -pub struct TabBar { - view_type: PhantomData, - scroll_state: ScrollState, - tabs: Vec, -} - -impl TabBar { - pub fn new(tabs: Vec) -> Self { - Self { - view_type: PhantomData, - scroll_state: ScrollState::default(), - tabs, - } - } - - pub fn bind_scroll_state(&mut self, scroll_state: ScrollState) { - self.scroll_state = scroll_state; - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let can_navigate_back = true; - let can_navigate_forward = false; - - div() - .w_full() - .flex() - .fill(theme.middle.base.default.background) - // Left Side - .child( - div() - .px_1() - .flex() - .flex_none() - .gap_2() - // Nav Buttons - .child( - div() - .flex() - .items_center() - .gap_px() - .child( - IconButton::new(Icon::ArrowLeft) - .state(InteractionState::Enabled.if_enabled(can_navigate_back)), - ) - .child( - IconButton::new(Icon::ArrowRight).state( - InteractionState::Enabled.if_enabled(can_navigate_forward), - ), - ), - ), - ) - .child( - div().w_0().flex_1().h_full().child( - div() - .flex() - .overflow_x_scroll(self.scroll_state.clone()) - .children(self.tabs.clone()), - ), - ) - // Right Side - .child( - div() - .px_1() - .flex() - .flex_none() - .gap_2() - // Nav Buttons - .child( - div() - .flex() - .items_center() - .gap_px() - .child(IconButton::new(Icon::Plus)) - .child(IconButton::new(Icon::Split)), - ), - ) - } -} diff --git a/crates/ui/src/components/terminal.rs b/crates/ui/src/components/terminal.rs deleted file mode 100644 index 909cb886ce..0000000000 --- a/crates/ui/src/components/terminal.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::sync::Arc; - -use gpui2::geometry::{relative, rems, Size}; - -use crate::prelude::*; -use crate::{theme, Icon, IconButton, Pane, Tab}; - -#[derive(Element)] -pub struct Terminal {} - -impl Terminal { - pub fn new() -> Self { - Self {} - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let can_navigate_back = true; - let can_navigate_forward = false; - - div() - .flex() - .flex_col() - .w_full() - .child( - // Terminal Tabs. - div() - .w_full() - .flex() - .fill(theme.middle.base.default.background) - .child( - div().px_1().flex().flex_none().gap_2().child( - div() - .flex() - .items_center() - .gap_px() - .child( - IconButton::new(Icon::ArrowLeft).state( - InteractionState::Enabled.if_enabled(can_navigate_back), - ), - ) - .child(IconButton::new(Icon::ArrowRight).state( - InteractionState::Enabled.if_enabled(can_navigate_forward), - )), - ), - ) - .child( - div().w_0().flex_1().h_full().child( - div() - .flex() - .child( - Tab::new() - .title("zed — fish".to_string()) - .icon(Icon::Terminal) - .close_side(IconSide::Right) - .current(true), - ) - .child( - Tab::new() - .title("zed — fish".to_string()) - .icon(Icon::Terminal) - .close_side(IconSide::Right) - .current(false), - ), - ), - ), - ) - // Terminal Pane. - .child(Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: rems(36.).into(), - }, - |_, payload| { - let theme = payload.downcast_ref::>().unwrap(); - - vec![crate::static_data::terminal_buffer(&theme).into_any()] - }, - Box::new(theme), - )) - } -} diff --git a/crates/ui/src/components/theme_selector.rs b/crates/ui/src/components/theme_selector.rs deleted file mode 100644 index e6f5237afe..0000000000 --- a/crates/ui/src/components/theme_selector.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::prelude::*; -use crate::{OrderMethod, Palette, PaletteItem}; - -#[derive(Element)] -pub struct ThemeSelector { - scroll_state: ScrollState, -} - -impl ThemeSelector { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(vec![ - PaletteItem::new("One Dark"), - PaletteItem::new("Rosé Pine"), - PaletteItem::new("Rosé Pine Moon"), - PaletteItem::new("Sandcastle"), - PaletteItem::new("Solarized Dark"), - PaletteItem::new("Summercamp"), - PaletteItem::new("Atelier Cave Light"), - PaletteItem::new("Atelier Dune Light"), - PaletteItem::new("Atelier Estuary Light"), - PaletteItem::new("Atelier Forest Light"), - PaletteItem::new("Atelier Heath Light"), - ]) - .placeholder("Select Theme...") - .empty_string("No matches") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/title_bar.rs b/crates/ui/src/components/title_bar.rs deleted file mode 100644 index dd3d6c1fba..0000000000 --- a/crates/ui/src/components/title_bar.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::marker::PhantomData; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; - -use crate::{prelude::*, PlayerWithCallStatus}; -use crate::{ - theme, Avatar, Button, Icon, IconButton, IconColor, PlayerStack, ToolDivider, TrafficLights, -}; - -#[derive(Clone)] -pub struct Livestream { - pub players: Vec, - pub channel: Option, // projects - // windows -} - -#[derive(Element)] -pub struct TitleBar { - view_type: PhantomData, - /// If the window is active from the OS's perspective. - is_active: Arc, - livestream: Option, -} - -impl TitleBar { - pub fn new(cx: &mut ViewContext) -> Self { - let is_active = Arc::new(AtomicBool::new(true)); - let active = is_active.clone(); - - cx.observe_window_activation(move |_, is_active, cx| { - active.store(is_active, std::sync::atomic::Ordering::SeqCst); - cx.notify(); - }) - .detach(); - - Self { - view_type: PhantomData, - is_active, - livestream: None, - } - } - - pub fn set_livestream(mut self, livestream: Option) -> Self { - self.livestream = livestream; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let has_focus = cx.window_is_active(); - - let player_list = if let Some(livestream) = &self.livestream { - livestream.players.clone().into_iter() - } else { - vec![].into_iter() - }; - - div() - .flex() - .items_center() - .justify_between() - .w_full() - .h_8() - .fill(theme.lowest.base.default.background) - .child( - div() - .flex() - .items_center() - .h_full() - .gap_4() - .px_2() - .child(TrafficLights::new().window_has_focus(has_focus)) - // === Project Info === // - .child( - div() - .flex() - .items_center() - .gap_1() - .child(Button::new("zed")) - .child(Button::new("nate/gpui2-ui-components")), - ) - .children(player_list.map(|p| PlayerStack::new(p))) - .child(IconButton::new(Icon::Plus)), - ) - .child( - div() - .flex() - .items_center() - .child( - div() - .px_2() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::FolderX)) - .child(IconButton::new(Icon::Close)), - ) - .child(ToolDivider::new()) - .child( - div() - .px_2() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::Mic)) - .child(IconButton::new(Icon::AudioOn)) - .child(IconButton::new(Icon::Screen).color(IconColor::Accent)), - ) - .child( - div().px_2().flex().items_center().child( - Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4") - .shape(Shape::RoundedRectangle), - ), - ), - ) - } -} diff --git a/crates/ui/src/components/toast.rs b/crates/ui/src/components/toast.rs deleted file mode 100644 index c299cdd6bc..0000000000 --- a/crates/ui/src/components/toast.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::prelude::*; - -#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] -pub enum ToastOrigin { - #[default] - Bottom, - BottomRight, -} - -#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] -pub enum ToastVariant { - #[default] - Toast, - Status, -} - -/// A toast is a small, temporary window that appears to show a message to the user -/// or indicate a required action. -/// -/// Toasts should not persist on the screen for more than a few seconds unless -/// they are actively showing the a process in progress. -/// -/// Only one toast may be visible at a time. -#[derive(Element)] -pub struct Toast { - origin: ToastOrigin, - children: HackyChildren, - payload: HackyChildrenPayload, -} - -impl Toast { - pub fn new( - origin: ToastOrigin, - children: HackyChildren, - payload: HackyChildrenPayload, - ) -> Self { - Self { - origin, - children, - payload, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let color = ThemeColor::new(cx); - - let mut div = div(); - - if self.origin == ToastOrigin::Bottom { - div = div.right_1_2(); - } else { - div = div.right_4(); - } - - div.absolute() - .bottom_4() - .flex() - .py_2() - .px_1p5() - .min_w_40() - .rounded_md() - .fill(color.elevated_surface) - .max_w_64() - .children_any((self.children)(cx, self.payload.as_ref())) - } -} diff --git a/crates/ui/src/components/toolbar.rs b/crates/ui/src/components/toolbar.rs deleted file mode 100644 index e0953bf3b2..0000000000 --- a/crates/ui/src/components/toolbar.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Clone)] -pub struct ToolbarItem {} - -#[derive(Element)] -pub struct Toolbar { - left_items: HackyChildren, - left_items_payload: HackyChildrenPayload, - right_items: HackyChildren, - right_items_payload: HackyChildrenPayload, -} - -impl Toolbar { - pub fn new( - left_items: HackyChildren, - left_items_payload: HackyChildrenPayload, - right_items: HackyChildren, - right_items_payload: HackyChildrenPayload, - ) -> Self { - Self { - left_items, - left_items_payload, - right_items, - right_items_payload, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .fill(theme.highest.base.default.background) - .p_2() - .flex() - .justify_between() - .child( - div() - .flex() - .children_any((self.left_items)(cx, self.left_items_payload.as_ref())), - ) - .child( - div() - .flex() - .children_any((self.right_items)(cx, self.right_items_payload.as_ref())), - ) - } -} diff --git a/crates/ui/src/components/traffic_lights.rs b/crates/ui/src/components/traffic_lights.rs deleted file mode 100644 index 0d644c49ca..0000000000 --- a/crates/ui/src/components/traffic_lights.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::prelude::*; -use crate::{theme, token, SystemColor}; - -#[derive(Clone, Copy)] -enum TrafficLightColor { - Red, - Yellow, - Green, -} - -#[derive(Element)] -struct TrafficLight { - color: TrafficLightColor, - window_has_focus: bool, -} - -impl TrafficLight { - fn new(color: TrafficLightColor, window_has_focus: bool) -> Self { - Self { - color, - window_has_focus, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let system_color = SystemColor::new(); - - let fill = match (self.window_has_focus, self.color) { - (true, TrafficLightColor::Red) => system_color.mac_os_traffic_light_red, - (true, TrafficLightColor::Yellow) => system_color.mac_os_traffic_light_yellow, - (true, TrafficLightColor::Green) => system_color.mac_os_traffic_light_green, - (false, _) => theme.lowest.base.active.background, - }; - - div().w_3().h_3().rounded_full().fill(fill) - } -} - -#[derive(Element)] -pub struct TrafficLights { - window_has_focus: bool, -} - -impl TrafficLights { - pub fn new() -> Self { - Self { - window_has_focus: true, - } - } - - pub fn window_has_focus(mut self, window_has_focus: bool) -> Self { - self.window_has_focus = window_has_focus; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - - div() - .flex() - .items_center() - .gap_2() - .child(TrafficLight::new( - TrafficLightColor::Red, - self.window_has_focus, - )) - .child(TrafficLight::new( - TrafficLightColor::Yellow, - self.window_has_focus, - )) - .child(TrafficLight::new( - TrafficLightColor::Green, - self.window_has_focus, - )) - } -} diff --git a/crates/ui/src/components/workspace.rs b/crates/ui/src/components/workspace.rs deleted file mode 100644 index b3d375bd64..0000000000 --- a/crates/ui/src/components/workspace.rs +++ /dev/null @@ -1,186 +0,0 @@ -use std::sync::Arc; - -use chrono::DateTime; -use gpui2::geometry::{relative, rems, Size}; - -use crate::{ - hello_world_rust_editor_with_status_example, prelude::*, random_players_with_call_status, - Livestream, -}; -use crate::{ - theme, v_stack, ChatMessage, ChatPanel, EditorPane, Pane, PaneGroup, Panel, PanelAllowedSides, - PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar, -}; - -#[derive(Element, Default)] -pub struct WorkspaceElement { - left_panel_scroll_state: ScrollState, - right_panel_scroll_state: ScrollState, - tab_bar_scroll_state: ScrollState, - bottom_panel_scroll_state: ScrollState, -} - -impl WorkspaceElement { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx).clone(); - - let temp_size = rems(36.).into(); - - let root_group = PaneGroup::new_groups( - vec![ - PaneGroup::new_panes( - vec![ - Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: temp_size, - }, - |_, payload| { - let theme = payload.downcast_ref::>().unwrap(); - - vec![EditorPane::new(hello_world_rust_editor_with_status_example( - &theme, - )) - .into_any()] - }, - Box::new(theme.clone()), - ), - Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: temp_size, - }, - |_, _| vec![Terminal::new().into_any()], - Box::new(()), - ), - ], - SplitDirection::Vertical, - ), - PaneGroup::new_panes( - vec![Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: relative(1.).into(), - }, - |_, payload| { - let theme = payload.downcast_ref::>().unwrap(); - - vec![EditorPane::new(hello_world_rust_editor_with_status_example( - &theme, - )) - .into_any()] - }, - Box::new(theme.clone()), - )], - SplitDirection::Vertical, - ), - ], - SplitDirection::Horizontal, - ); - - div() - .relative() - .size_full() - .flex() - .flex_col() - .font("Zed Sans Extended") - .gap_0() - .justify_start() - .items_start() - .text_color(theme.lowest.base.default.foreground) - .fill(theme.lowest.base.default.background) - .child(TitleBar::new(cx).set_livestream(Some(Livestream { - players: random_players_with_call_status(7), - channel: Some("gpui2-ui".to_string()), - }))) - .child( - div() - .flex_1() - .w_full() - .flex() - .flex_row() - .overflow_hidden() - .border_t() - .border_b() - .border_color(theme.lowest.base.default.border) - .child( - Panel::new( - self.left_panel_scroll_state.clone(), - |_, payload| vec![ProjectPanel::new(ScrollState::default()).into_any()], - Box::new(()), - ) - .side(PanelSide::Left), - ) - .child( - v_stack() - .flex_1() - .h_full() - .child( - div() - .flex() - .flex_1() - // CSS Hack: Flex 1 has to have a set height to properly fill the space - // Or it will give you a height of 0 - .h_px() - .child(root_group), - ) - .child( - Panel::new( - self.bottom_panel_scroll_state.clone(), - |_, _| vec![Terminal::new().into_any()], - Box::new(()), - ) - .allowed_sides(PanelAllowedSides::BottomOnly) - .side(PanelSide::Bottom), - ), - ) - .child( - Panel::new( - self.right_panel_scroll_state.clone(), - |_, payload| { - vec![ChatPanel::new(ScrollState::default()) - .with_messages(vec![ - ChatMessage::new( - "osiewicz".to_string(), - "is this thing on?".to_string(), - DateTime::parse_from_rfc3339( - "2023-09-27T15:40:52.707Z", - ) - .unwrap() - .naive_local(), - ), - ChatMessage::new( - "maxdeviant".to_string(), - "Reading you loud and clear!".to_string(), - DateTime::parse_from_rfc3339( - "2023-09-28T15:40:52.707Z", - ) - .unwrap() - .naive_local(), - ), - ]) - .into_any()] - }, - Box::new(()), - ) - .side(PanelSide::Right), - ), - ) - .child(StatusBar::new()) - // An example of a toast is below - // Currently because of stacking order this gets obscured by other elements - - // .child(Toast::new( - // ToastOrigin::Bottom, - // |_, payload| { - // let theme = payload.downcast_ref::>().unwrap(); - - // vec![Label::new("label").into_any()] - // }, - // Box::new(theme.clone()), - // )) - } -} diff --git a/crates/ui/src/element_ext.rs b/crates/ui/src/element_ext.rs deleted file mode 100644 index 67352f0779..0000000000 --- a/crates/ui/src/element_ext.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::Element; - -use crate::theme::{Theme, Themed}; - -pub trait ElementExt: Element { - fn themed(self, theme: Theme) -> Themed - where - Self: Sized; -} - -impl> ElementExt for E { - fn themed(self, theme: Theme) -> Themed - where - Self: Sized, - { - Themed { - child: self, - theme, - view_type: PhantomData, - } - } -} diff --git a/crates/ui/src/elements.rs b/crates/ui/src/elements.rs deleted file mode 100644 index c60902ae98..0000000000 --- a/crates/ui/src/elements.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod avatar; -mod button; -mod details; -mod icon; -mod input; -mod label; -mod player; -mod stack; -mod tool_divider; - -pub use avatar::*; -pub use button::*; -pub use details::*; -pub use icon::*; -pub use input::*; -pub use label::*; -pub use player::*; -pub use stack::*; -pub use tool_divider::*; diff --git a/crates/ui/src/elements/avatar.rs b/crates/ui/src/elements/avatar.rs deleted file mode 100644 index 2072b0e501..0000000000 --- a/crates/ui/src/elements/avatar.rs +++ /dev/null @@ -1,41 +0,0 @@ -use gpui2::elements::img; -use gpui2::ArcCow; - -use crate::prelude::*; -use crate::theme; - -#[derive(Element, Clone)] -pub struct Avatar { - src: ArcCow<'static, str>, - shape: Shape, -} - -impl Avatar { - pub fn new(src: impl Into>) -> Self { - Self { - src: src.into(), - shape: Shape::Circle, - } - } - - pub fn shape(mut self, shape: Shape) -> Self { - self.shape = shape; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let mut img = img(); - - if self.shape == Shape::Circle { - img = img.rounded_full(); - } else { - img = img.rounded_md(); - } - - img.uri(self.src.clone()) - .size_4() - .fill(theme.middle.warning.default.foreground) - } -} diff --git a/crates/ui/src/elements/button.rs b/crates/ui/src/elements/button.rs deleted file mode 100644 index c516b27908..0000000000 --- a/crates/ui/src/elements/button.rs +++ /dev/null @@ -1,203 +0,0 @@ -use std::rc::Rc; - -use gpui2::geometry::DefiniteLength; -use gpui2::platform::MouseButton; -use gpui2::{EventContext, Hsla, Interactive, WindowContext}; - -use crate::prelude::*; -use crate::{h_stack, theme, Icon, IconColor, IconElement, Label, LabelColor, LabelSize}; - -#[derive(Default, PartialEq, Clone, Copy)] -pub enum IconPosition { - #[default] - Left, - Right, -} - -#[derive(Default, Copy, Clone, PartialEq)] -pub enum ButtonVariant { - #[default] - Ghost, - Filled, -} - -struct ButtonHandlers { - click: Option)>>, -} - -impl Default for ButtonHandlers { - fn default() -> Self { - Self { click: None } - } -} - -#[derive(Element)] -pub struct Button { - label: String, - variant: ButtonVariant, - state: InteractionState, - icon: Option, - icon_position: Option, - width: Option, - handlers: ButtonHandlers, -} - -impl Button { - pub fn new(label: L) -> Self - where - L: Into, - { - Self { - label: label.into(), - variant: Default::default(), - state: Default::default(), - icon: None, - icon_position: None, - width: Default::default(), - handlers: ButtonHandlers::default(), - } - } - - pub fn ghost(label: L) -> Self - where - L: Into, - { - Self::new(label).variant(ButtonVariant::Ghost) - } - - pub fn variant(mut self, variant: ButtonVariant) -> Self { - self.variant = variant; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - pub fn icon(mut self, icon: Icon) -> Self { - self.icon = Some(icon); - self - } - - pub fn icon_position(mut self, icon_position: IconPosition) -> Self { - if self.icon.is_none() { - panic!("An icon must be present if an icon_position is provided."); - } - self.icon_position = Some(icon_position); - self - } - - pub fn width(mut self, width: Option) -> Self { - self.width = width; - self - } - - pub fn on_click(mut self, handler: impl Fn(&mut V, &mut EventContext) + 'static) -> Self { - self.handlers.click = Some(Rc::new(handler)); - self - } - - fn background_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match (self.variant, self.state) { - (ButtonVariant::Ghost, InteractionState::Hovered) => { - theme.lowest.base.hovered.background - } - (ButtonVariant::Ghost, InteractionState::Active) => { - theme.lowest.base.pressed.background - } - (ButtonVariant::Filled, InteractionState::Enabled) => { - theme.lowest.on.default.background - } - (ButtonVariant::Filled, InteractionState::Hovered) => { - theme.lowest.on.hovered.background - } - (ButtonVariant::Filled, InteractionState::Active) => theme.lowest.on.pressed.background, - (ButtonVariant::Filled, InteractionState::Disabled) => { - theme.lowest.on.disabled.background - } - _ => system_color.transparent, - } - } - - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - - fn border_color(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self.state { - InteractionState::Focused => theme.lowest.accent.default.border, - _ => system_color.transparent, - } - } - - fn render_label(&self) -> Label { - Label::new(self.label.clone()) - .size(LabelSize::Small) - .color(self.label_color()) - } - - fn render_icon(&self, icon_color: IconColor) -> Option { - self.icon.map(|i| IconElement::new(i).color(icon_color)) - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let icon_color = self.icon_color(); - let system_color = SystemColor::new(); - let border_color = self.border_color(cx); - - let mut el = h_stack() - .h_6() - .px_1() - .items_center() - .rounded_md() - .border() - .border_color(border_color) - .fill(self.background_color(cx)); - - match (self.icon, self.icon_position) { - (Some(_), Some(IconPosition::Left)) => { - el = el - .gap_1() - .child(self.render_label()) - .children(self.render_icon(icon_color)) - } - (Some(_), Some(IconPosition::Right)) => { - el = el - .gap_1() - .children(self.render_icon(icon_color)) - .child(self.render_label()) - } - (_, _) => el = el.child(self.render_label()), - } - - if let Some(width) = self.width { - el = el.w(width).justify_center(); - } - - if let Some(click_handler) = self.handlers.click.clone() { - el = el.on_mouse_down(MouseButton::Left, move |view, event, cx| { - click_handler(view, cx); - }); - } - - el - } -} diff --git a/crates/ui/src/elements/details.rs b/crates/ui/src/elements/details.rs deleted file mode 100644 index 9c829bcd41..0000000000 --- a/crates/ui/src/elements/details.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Element, Clone)] -pub struct Details { - text: &'static str, - meta: Option<&'static str>, -} - -impl Details { - pub fn new(text: &'static str) -> Self { - Self { text, meta: None } - } - - pub fn meta_text(mut self, meta: &'static str) -> Self { - self.meta = Some(meta); - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - // .flex() - // .w_full() - .p_1() - .gap_0p5() - .text_xs() - .text_color(theme.lowest.base.default.foreground) - .child(self.text) - .children(self.meta.map(|m| m)) - } -} diff --git a/crates/ui/src/elements/icon.rs b/crates/ui/src/elements/icon.rs deleted file mode 100644 index 26bf7dab22..0000000000 --- a/crates/ui/src/elements/icon.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::sync::Arc; - -use gpui2::elements::svg; -use gpui2::Hsla; -use strum::EnumIter; - -use crate::prelude::*; -use crate::theme::theme; -use crate::Theme; - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum IconSize { - Small, - #[default] - Large, -} - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum IconColor { - #[default] - Default, - Muted, - Disabled, - Placeholder, - Accent, - Error, - Warning, - Success, - Info, -} - -impl IconColor { - pub fn color(self, theme: Arc) -> Hsla { - match self { - IconColor::Default => theme.lowest.base.default.foreground, - IconColor::Muted => theme.lowest.variant.default.foreground, - IconColor::Disabled => theme.lowest.base.disabled.foreground, - IconColor::Placeholder => theme.lowest.base.disabled.foreground, - IconColor::Accent => theme.lowest.accent.default.foreground, - IconColor::Error => theme.lowest.negative.default.foreground, - IconColor::Warning => theme.lowest.warning.default.foreground, - IconColor::Success => theme.lowest.positive.default.foreground, - IconColor::Info => theme.lowest.accent.default.foreground, - } - } -} - -#[derive(Default, PartialEq, Copy, Clone, EnumIter)] -pub enum Icon { - Ai, - ArrowLeft, - ArrowRight, - ArrowUpRight, - AudioOff, - AudioOn, - Bolt, - ChevronDown, - ChevronLeft, - ChevronRight, - ChevronUp, - Close, - ExclamationTriangle, - ExternalLink, - File, - FileGeneric, - FileDoc, - FileGit, - FileLock, - FileRust, - FileToml, - FileTree, - Folder, - FolderOpen, - FolderX, - #[default] - Hash, - InlayHint, - MagicWand, - MagnifyingGlass, - Maximize, - Menu, - MessageBubbles, - Mic, - MicMute, - Plus, - Quote, - Screen, - SelectAll, - Split, - SplitMessage, - Terminal, - XCircle, - Copilot, - Envelope, -} - -impl Icon { - pub fn path(self) -> &'static str { - match self { - Icon::Ai => "icons/ai.svg", - Icon::ArrowLeft => "icons/arrow_left.svg", - Icon::ArrowRight => "icons/arrow_right.svg", - Icon::ArrowUpRight => "icons/arrow_up_right.svg", - Icon::AudioOff => "icons/speaker-off.svg", - Icon::AudioOn => "icons/speaker-loud.svg", - Icon::Bolt => "icons/bolt.svg", - Icon::ChevronDown => "icons/chevron_down.svg", - Icon::ChevronLeft => "icons/chevron_left.svg", - Icon::ChevronRight => "icons/chevron_right.svg", - Icon::ChevronUp => "icons/chevron_up.svg", - Icon::Close => "icons/x.svg", - Icon::ExclamationTriangle => "icons/warning.svg", - Icon::ExternalLink => "icons/external_link.svg", - Icon::File => "icons/file.svg", - Icon::FileGeneric => "icons/file_icons/file.svg", - Icon::FileDoc => "icons/file_icons/book.svg", - Icon::FileGit => "icons/file_icons/git.svg", - Icon::FileLock => "icons/file_icons/lock.svg", - Icon::FileRust => "icons/file_icons/rust.svg", - Icon::FileToml => "icons/file_icons/toml.svg", - Icon::FileTree => "icons/project.svg", - Icon::Folder => "icons/file_icons/folder.svg", - Icon::FolderOpen => "icons/file_icons/folder_open.svg", - Icon::FolderX => "icons/stop_sharing.svg", - Icon::Hash => "icons/hash.svg", - Icon::InlayHint => "icons/inlay_hint.svg", - Icon::MagicWand => "icons/magic-wand.svg", - Icon::MagnifyingGlass => "icons/magnifying_glass.svg", - Icon::Maximize => "icons/maximize.svg", - Icon::Menu => "icons/menu.svg", - Icon::MessageBubbles => "icons/conversations.svg", - Icon::Mic => "icons/mic.svg", - Icon::MicMute => "icons/mic-mute.svg", - Icon::Plus => "icons/plus.svg", - Icon::Quote => "icons/quote.svg", - Icon::Screen => "icons/desktop.svg", - Icon::SelectAll => "icons/select-all.svg", - Icon::Split => "icons/split.svg", - Icon::SplitMessage => "icons/split_message.svg", - Icon::Terminal => "icons/terminal.svg", - Icon::XCircle => "icons/error.svg", - Icon::Copilot => "icons/copilot.svg", - Icon::Envelope => "icons/feedback.svg", - } - } -} - -#[derive(Element, Clone)] -pub struct IconElement { - icon: Icon, - color: IconColor, - size: IconSize, -} - -impl IconElement { - pub fn new(icon: Icon) -> Self { - Self { - icon, - color: IconColor::default(), - size: IconSize::default(), - } - } - - pub fn color(mut self, color: IconColor) -> Self { - self.color = color; - self - } - - pub fn size(mut self, size: IconSize) -> Self { - self.size = size; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let fill = self.color.color(theme); - - let sized_svg = match self.size { - IconSize::Small => svg().size_3p5(), - IconSize::Large => svg().size_4(), - }; - - sized_svg.flex_none().path(self.icon.path()).fill(fill) - } -} diff --git a/crates/ui/src/elements/input.rs b/crates/ui/src/elements/input.rs deleted file mode 100644 index fd860f30c2..0000000000 --- a/crates/ui/src/elements/input.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Default, PartialEq)] -pub enum InputVariant { - #[default] - Ghost, - Filled, -} - -#[derive(Element)] -pub struct Input { - placeholder: &'static str, - value: String, - state: InteractionState, - variant: InputVariant, -} - -impl Input { - pub fn new(placeholder: &'static str) -> Self { - Self { - placeholder, - value: "".to_string(), - state: InteractionState::default(), - variant: InputVariant::default(), - } - } - - pub fn value(mut self, value: String) -> Self { - self.value = value; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - pub fn variant(mut self, variant: InputVariant) -> Self { - self.variant = variant; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let text_el; - let text_color; - let background_color_default; - let background_color_active; - - let mut border_color_default = theme.middle.base.default.border; - let mut border_color_hover = theme.middle.base.hovered.border; - let mut border_color_active = theme.middle.base.pressed.border; - let border_color_focus = theme.middle.base.pressed.background; - - match self.variant { - InputVariant::Ghost => { - background_color_default = theme.middle.base.default.background; - background_color_active = theme.middle.base.active.background; - } - InputVariant::Filled => { - background_color_default = theme.middle.on.default.background; - background_color_active = theme.middle.on.active.background; - } - }; - - if self.state == InteractionState::Focused { - border_color_default = theme.players[0].cursor; - border_color_hover = theme.players[0].cursor; - border_color_active = theme.players[0].cursor; - } - - if self.state == InteractionState::Focused || self.state == InteractionState::Active { - text_el = self.value.clone(); - text_color = theme.lowest.base.default.foreground; - } else { - text_el = self.placeholder.to_string().clone(); - text_color = theme.lowest.base.disabled.foreground; - } - - div() - .h_7() - .w_full() - .px_2() - .border() - .border_color(border_color_default) - .fill(background_color_default) - .hover() - .border_color(border_color_hover) - .active() - .border_color(border_color_active) - .fill(background_color_active) - .flex() - .items_center() - .child( - div() - .flex() - .items_center() - .text_sm() - .text_color(text_color) - .child(text_el) - .child(div().text_color(theme.players[0].cursor).child("|")), - ) - } -} diff --git a/crates/ui/src/elements/label.rs b/crates/ui/src/elements/label.rs deleted file mode 100644 index e7b15aaf02..0000000000 --- a/crates/ui/src/elements/label.rs +++ /dev/null @@ -1,161 +0,0 @@ -use gpui2::{Hsla, WindowContext}; -use smallvec::SmallVec; - -use crate::prelude::*; -use crate::theme::theme; - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum LabelColor { - #[default] - Default, - Muted, - Created, - Modified, - Deleted, - Disabled, - Hidden, - Placeholder, - Accent, -} - -impl LabelColor { - pub fn hsla(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - - match self { - Self::Default => theme.middle.base.default.foreground, - Self::Muted => theme.middle.variant.default.foreground, - Self::Created => theme.middle.positive.default.foreground, - Self::Modified => theme.middle.warning.default.foreground, - Self::Deleted => theme.middle.negative.default.foreground, - Self::Disabled => theme.middle.base.disabled.foreground, - Self::Hidden => theme.middle.variant.default.foreground, - Self::Placeholder => theme.middle.base.disabled.foreground, - Self::Accent => theme.middle.accent.default.foreground, - } - } -} - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum LabelSize { - #[default] - Default, - Small, -} - -#[derive(Element, Clone)] -pub struct Label { - label: String, - color: LabelColor, - size: LabelSize, - highlight_indices: Vec, - strikethrough: bool, -} - -impl Label { - pub fn new(label: L) -> Self - where - L: Into, - { - Self { - label: label.into(), - color: LabelColor::Default, - size: LabelSize::Default, - highlight_indices: Vec::new(), - strikethrough: false, - } - } - - pub fn color(mut self, color: LabelColor) -> Self { - self.color = color; - self - } - - pub fn size(mut self, size: LabelSize) -> Self { - self.size = size; - self - } - - pub fn with_highlights(mut self, indices: Vec) -> Self { - self.highlight_indices = indices; - self - } - - pub fn set_strikethrough(mut self, strikethrough: bool) -> Self { - self.strikethrough = strikethrough; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let highlight_color = theme.lowest.accent.default.foreground; - - let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); - - let mut runs: SmallVec<[Run; 8]> = SmallVec::new(); - - for (char_ix, char) in self.label.char_indices() { - let mut color = self.color.hsla(cx); - - if let Some(highlight_ix) = highlight_indices.peek() { - if char_ix == *highlight_ix { - color = highlight_color; - - highlight_indices.next(); - } - } - - let last_run = runs.last_mut(); - - let start_new_run = if let Some(last_run) = last_run { - if color == last_run.color { - last_run.text.push(char); - false - } else { - true - } - } else { - true - }; - - if start_new_run { - runs.push(Run { - text: char.to_string(), - color, - }); - } - } - - div() - .flex() - .when(self.strikethrough, |this| { - this.relative().child( - div() - .absolute() - .top_px() - .my_auto() - .w_full() - .h_px() - .fill(LabelColor::Hidden.hsla(cx)), - ) - }) - .children(runs.into_iter().map(|run| { - let mut div = div(); - - if self.size == LabelSize::Small { - div = div.text_xs(); - } else { - div = div.text_sm(); - } - - div.text_color(run.color).child(run.text) - })) - } -} - -/// A run of text that receives the same style. -struct Run { - pub text: String, - pub color: Hsla, -} diff --git a/crates/ui/src/elements/player.rs b/crates/ui/src/elements/player.rs deleted file mode 100644 index 465542dc7f..0000000000 --- a/crates/ui/src/elements/player.rs +++ /dev/null @@ -1,133 +0,0 @@ -use gpui2::{Hsla, ViewContext}; - -use crate::theme; - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum PlayerStatus { - #[default] - Offline, - Online, - InCall, - Away, - DoNotDisturb, - Invisible, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum MicStatus { - Muted, - #[default] - Unmuted, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum VideoStatus { - On, - #[default] - Off, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum ScreenShareStatus { - Shared, - #[default] - NotShared, -} - -#[derive(Clone)] -pub struct PlayerCallStatus { - pub mic_status: MicStatus, - /// Indicates if the player is currently speaking - /// And the intensity of the volume coming through - /// - /// 0.0 - 1.0 - pub voice_activity: f32, - pub video_status: VideoStatus, - pub screen_share_status: ScreenShareStatus, - pub in_current_project: bool, - pub disconnected: bool, - pub following: Option>, - pub followers: Option>, -} - -impl PlayerCallStatus { - pub fn new() -> Self { - Self { - mic_status: MicStatus::default(), - voice_activity: 0., - video_status: VideoStatus::default(), - screen_share_status: ScreenShareStatus::default(), - in_current_project: true, - disconnected: false, - following: None, - followers: None, - } - } -} - -#[derive(PartialEq, Clone)] -pub struct Player { - index: usize, - avatar_src: String, - username: String, - status: PlayerStatus, -} - -#[derive(Clone)] -pub struct PlayerWithCallStatus { - player: Player, - call_status: PlayerCallStatus, -} - -impl PlayerWithCallStatus { - pub fn new(player: Player, call_status: PlayerCallStatus) -> Self { - Self { - player, - call_status, - } - } - - pub fn get_player(&self) -> &Player { - &self.player - } - - pub fn get_call_status(&self) -> &PlayerCallStatus { - &self.call_status - } -} - -impl Player { - pub fn new(index: usize, avatar_src: String, username: String) -> Self { - Self { - index, - avatar_src, - username, - status: Default::default(), - } - } - - pub fn set_status(mut self, status: PlayerStatus) -> Self { - self.status = status; - self - } - - pub fn cursor_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - let index = self.index % 8; - theme.players[self.index].cursor - } - - pub fn selection_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - let index = self.index % 8; - theme.players[self.index].selection - } - - pub fn avatar_src(&self) -> &str { - &self.avatar_src - } - - pub fn index(&self) -> usize { - self.index - } -} diff --git a/crates/ui/src/elements/stack.rs b/crates/ui/src/elements/stack.rs deleted file mode 100644 index ef186f5ebe..0000000000 --- a/crates/ui/src/elements/stack.rs +++ /dev/null @@ -1,31 +0,0 @@ -use gpui2::elements::div::Div; - -use crate::prelude::*; - -pub trait Stack: StyleHelpers { - /// Horizontally stacks elements. - fn h_stack(self) -> Self { - self.flex().flex_row().items_center() - } - - /// Vertically stacks elements. - fn v_stack(self) -> Self { - self.flex().flex_col() - } -} - -impl Stack for Div {} - -/// Horizontally stacks elements. -/// -/// Sets `flex()`, `flex_row()`, `items_center()` -pub fn h_stack() -> Div { - div().h_stack() -} - -/// Vertically stacks elements. -/// -/// Sets `flex()`, `flex_col()` -pub fn v_stack() -> Div { - div().v_stack() -} diff --git a/crates/ui/src/elements/tool_divider.rs b/crates/ui/src/elements/tool_divider.rs deleted file mode 100644 index 8b5a191445..0000000000 --- a/crates/ui/src/elements/tool_divider.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Element)] -pub struct ToolDivider {} - -impl ToolDivider { - pub fn new() -> Self { - Self {} - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div().w_px().h_3().fill(theme.lowest.base.default.border) - } -} diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs deleted file mode 100644 index 39156d3ab4..0000000000 --- a/crates/ui/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![allow(dead_code, unused_variables)] - -mod children; -mod components; -mod element_ext; -mod elements; -pub mod prelude; -mod static_data; -mod theme; -mod tokens; - -pub use children::*; -pub use components::*; -pub use element_ext::*; -pub use elements::*; -pub use prelude::*; -pub use static_data::*; -pub use tokens::*; - -pub use crate::theme::*; diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs deleted file mode 100644 index e0da3579e2..0000000000 --- a/crates/ui/src/prelude.rs +++ /dev/null @@ -1,274 +0,0 @@ -pub use gpui2::elements::div::{div, ScrollState}; -pub use gpui2::style::{StyleHelpers, Styleable}; -pub use gpui2::{Element, IntoElement, ParentElement, ViewContext}; - -pub use crate::{theme, ButtonVariant, HackyChildren, HackyChildrenPayload, InputVariant, Theme}; - -use gpui2::{hsla, rgb, Hsla, WindowContext}; -use strum::EnumIter; - -#[derive(Default)] -pub struct SystemColor { - pub transparent: Hsla, - pub mac_os_traffic_light_red: Hsla, - pub mac_os_traffic_light_yellow: Hsla, - pub mac_os_traffic_light_green: Hsla, -} - -impl SystemColor { - pub fn new() -> SystemColor { - SystemColor { - transparent: hsla(0.0, 0.0, 0.0, 0.0), - mac_os_traffic_light_red: rgb::(0xEC695E), - mac_os_traffic_light_yellow: rgb::(0xF4BF4F), - mac_os_traffic_light_green: rgb::(0x62C554), - } - } - pub fn color(&self) -> Hsla { - self.transparent - } -} - -#[derive(Clone, Copy)] -pub struct ThemeColor { - pub border: Hsla, - pub border_variant: Hsla, - /// The background color of an elevated surface, like a modal, tooltip or toast. - pub elevated_surface: Hsla, -} - -impl ThemeColor { - pub fn new(cx: &WindowContext) -> Self { - let theme = theme(cx); - - Self { - border: theme.lowest.base.default.border, - border_variant: theme.lowest.variant.default.border, - elevated_surface: theme.middle.base.default.background, - } - } -} - -#[derive(Default, PartialEq, EnumIter, Clone, Copy)] -pub enum HighlightColor { - #[default] - Default, - Comment, - String, - Function, - Keyword, -} - -impl HighlightColor { - pub fn hsla(&self, theme: &Theme) -> Hsla { - let system_color = SystemColor::new(); - - match self { - Self::Default => theme - .syntax - .get("primary") - .expect("no theme.syntax.primary") - .clone(), - Self::Comment => theme - .syntax - .get("comment") - .expect("no theme.syntax.comment") - .clone(), - Self::String => theme - .syntax - .get("string") - .expect("no theme.syntax.string") - .clone(), - Self::Function => theme - .syntax - .get("function") - .expect("no theme.syntax.function") - .clone(), - Self::Keyword => theme - .syntax - .get("keyword") - .expect("no theme.syntax.keyword") - .clone(), - } - } -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum FileSystemStatus { - #[default] - None, - Conflict, - Deleted, -} - -impl FileSystemStatus { - pub fn to_string(&self) -> String { - match self { - Self::None => "None".to_string(), - Self::Conflict => "Conflict".to_string(), - Self::Deleted => "Deleted".to_string(), - } - } -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum GitStatus { - #[default] - None, - Created, - Modified, - Deleted, - Conflict, - Renamed, -} - -impl GitStatus { - pub fn to_string(&self) -> String { - match self { - Self::None => "None".to_string(), - Self::Created => "Created".to_string(), - Self::Modified => "Modified".to_string(), - Self::Deleted => "Deleted".to_string(), - Self::Conflict => "Conflict".to_string(), - Self::Renamed => "Renamed".to_string(), - } - } - - pub fn hsla(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self { - Self::None => system_color.transparent, - Self::Created => theme.lowest.positive.default.foreground, - Self::Modified => theme.lowest.warning.default.foreground, - Self::Deleted => theme.lowest.negative.default.foreground, - Self::Conflict => theme.lowest.warning.default.foreground, - Self::Renamed => theme.lowest.accent.default.foreground, - } - } -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum DiagnosticStatus { - #[default] - None, - Error, - Warning, - Info, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum IconSide { - #[default] - Left, - Right, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum OrderMethod { - #[default] - Ascending, - Descending, - MostRecent, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum Shape { - #[default] - Circle, - RoundedRectangle, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum DisclosureControlVisibility { - #[default] - OnHover, - Always, -} - -#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)] -pub enum InteractionState { - #[default] - Enabled, - Hovered, - Active, - Focused, - Disabled, -} - -impl InteractionState { - pub fn if_enabled(&self, enabled: bool) -> Self { - if enabled { - *self - } else { - InteractionState::Disabled - } - } -} - -#[derive(Default, PartialEq)] -pub enum SelectedState { - #[default] - Unselected, - PartiallySelected, - Selected, -} - -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] -pub enum Toggleable { - Toggleable(ToggleState), - #[default] - NotToggleable, -} - -impl Toggleable { - pub fn is_toggled(&self) -> bool { - match self { - Self::Toggleable(ToggleState::Toggled) => true, - _ => false, - } - } -} - -impl From for Toggleable { - fn from(state: ToggleState) -> Self { - Self::Toggleable(state) - } -} - -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] -pub enum ToggleState { - /// The "on" state of a toggleable element. - /// - /// Example: - /// - A collasable list that is currently expanded - /// - A toggle button that is currently on. - Toggled, - /// The "off" state of a toggleable element. - /// - /// Example: - /// - A collasable list that is currently collapsed - /// - A toggle button that is currently off. - #[default] - NotToggled, -} - -impl From for ToggleState { - fn from(toggleable: Toggleable) -> Self { - match toggleable { - Toggleable::Toggleable(state) => state, - Toggleable::NotToggleable => ToggleState::NotToggled, - } - } -} - -impl From for ToggleState { - fn from(toggled: bool) -> Self { - if toggled { - ToggleState::Toggled - } else { - ToggleState::NotToggled - } - } -} diff --git a/crates/ui/src/static_data.rs b/crates/ui/src/static_data.rs deleted file mode 100644 index b8c4e18f14..0000000000 --- a/crates/ui/src/static_data.rs +++ /dev/null @@ -1,966 +0,0 @@ -use std::path::PathBuf; -use std::str::FromStr; - -use rand::Rng; - -use crate::{ - Buffer, BufferRow, BufferRows, Editor, FileSystemStatus, GitStatus, HighlightColor, - HighlightedLine, HighlightedText, Icon, Keybinding, Label, LabelColor, ListEntry, - ListEntrySize, ListItem, Livestream, MicStatus, ModifierKeys, PaletteItem, Player, - PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, Theme, ToggleState, - VideoStatus, -}; - -pub fn static_tabs_example() -> Vec { - vec![ - Tab::new() - .title("wip.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .fs_status(FileSystemStatus::Deleted), - Tab::new() - .title("Cargo.toml".to_string()) - .icon(Icon::FileToml) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("Channels Panel".to_string()) - .icon(Icon::Hash) - .current(false), - Tab::new() - .title("channels_panel.rs".to_string()) - .icon(Icon::FileRust) - .current(true) - .git_status(GitStatus::Modified), - Tab::new() - .title("workspace.rs".to_string()) - .current(false) - .icon(Icon::FileRust) - .git_status(GitStatus::Modified), - Tab::new() - .title("icon_button.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("storybook.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .git_status(GitStatus::Created), - Tab::new() - .title("theme.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("theme_registry.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("styleable_helpers.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - ] -} - -pub fn static_tabs_1() -> Vec { - vec![ - Tab::new() - .title("project_panel.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .fs_status(FileSystemStatus::Deleted), - Tab::new() - .title("tab_bar.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("workspace.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("tab.rs".to_string()) - .icon(Icon::FileRust) - .current(true) - .git_status(GitStatus::Modified), - ] -} - -pub fn static_tabs_2() -> Vec { - vec![ - Tab::new() - .title("tab_bar.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .fs_status(FileSystemStatus::Deleted), - Tab::new() - .title("static_data.rs".to_string()) - .icon(Icon::FileRust) - .current(true) - .git_status(GitStatus::Modified), - ] -} - -pub fn static_tabs_3() -> Vec { - vec![Tab::new().git_status(GitStatus::Created).current(true)] -} - -pub fn static_players() -> Vec { - vec![ - Player::new( - 0, - "https://avatars.githubusercontent.com/u/1714999?v=4".into(), - "nathansobo".into(), - ), - Player::new( - 1, - "https://avatars.githubusercontent.com/u/326587?v=4".into(), - "maxbrunsfeld".into(), - ), - Player::new( - 2, - "https://avatars.githubusercontent.com/u/482957?v=4".into(), - "as-cii".into(), - ), - Player::new( - 3, - "https://avatars.githubusercontent.com/u/1714999?v=4".into(), - "iamnbutler".into(), - ), - Player::new( - 4, - "https://avatars.githubusercontent.com/u/1486634?v=4".into(), - "maxdeviant".into(), - ), - ] -} - -#[derive(Debug)] -pub struct PlayerData { - pub url: String, - pub name: String, -} -pub fn static_player_data() -> Vec { - vec![ - PlayerData { - url: "https://avatars.githubusercontent.com/u/1714999?v=4".into(), - name: "iamnbutler".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/326587?v=4".into(), - name: "maxbrunsfeld".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/482957?v=4".into(), - name: "as-cii".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/1789?v=4".into(), - name: "nathansobo".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(), - name: "ForLoveOfCats".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/2690773?v=4".into(), - name: "SomeoneToIgnore".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/19867440?v=4".into(), - name: "JosephTLyons".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/24362066?v=4".into(), - name: "osiewicz".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/22121886?v=4".into(), - name: "KCaverly".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(), - name: "maxdeviant".into(), - }, - ] -} -pub fn create_static_players(player_data: Vec) -> Vec { - let mut players = Vec::new(); - for data in player_data { - players.push(Player::new(players.len(), data.url, data.name)); - } - players -} -pub fn static_player_1(data: &Vec) -> Player { - Player::new(1, data[0].url.clone(), data[0].name.clone()) -} -pub fn static_player_2(data: &Vec) -> Player { - Player::new(2, data[1].url.clone(), data[1].name.clone()) -} -pub fn static_player_3(data: &Vec) -> Player { - Player::new(3, data[2].url.clone(), data[2].name.clone()) -} -pub fn static_player_4(data: &Vec) -> Player { - Player::new(4, data[3].url.clone(), data[3].name.clone()) -} -pub fn static_player_5(data: &Vec) -> Player { - Player::new(5, data[4].url.clone(), data[4].name.clone()) -} -pub fn static_player_6(data: &Vec) -> Player { - Player::new(6, data[5].url.clone(), data[5].name.clone()) -} -pub fn static_player_7(data: &Vec) -> Player { - Player::new(7, data[6].url.clone(), data[6].name.clone()) -} -pub fn static_player_8(data: &Vec) -> Player { - Player::new(8, data[7].url.clone(), data[7].name.clone()) -} -pub fn static_player_9(data: &Vec) -> Player { - Player::new(9, data[8].url.clone(), data[8].name.clone()) -} -pub fn static_player_10(data: &Vec) -> Player { - Player::new(10, data[9].url.clone(), data[9].name.clone()) -} -pub fn static_livestream() -> Livestream { - Livestream { - players: random_players_with_call_status(7), - channel: Some("gpui2-ui".to_string()), - } -} -pub fn populate_player_call_status( - player: Player, - followers: Option>, -) -> PlayerCallStatus { - let mut rng = rand::thread_rng(); - let in_current_project: bool = rng.gen(); - let disconnected: bool = rng.gen(); - let voice_activity: f32 = rng.gen(); - let mic_status = if rng.gen_bool(0.5) { - MicStatus::Muted - } else { - MicStatus::Unmuted - }; - let video_status = if rng.gen_bool(0.5) { - VideoStatus::On - } else { - VideoStatus::Off - }; - let screen_share_status = if rng.gen_bool(0.5) { - ScreenShareStatus::Shared - } else { - ScreenShareStatus::NotShared - }; - PlayerCallStatus { - mic_status, - voice_activity, - video_status, - screen_share_status, - in_current_project, - disconnected, - following: None, - followers, - } -} -pub fn random_players_with_call_status(number_of_players: usize) -> Vec { - let players = create_static_players(static_player_data()); - let mut player_status = vec![]; - for i in 0..number_of_players { - let followers = if i == 0 { - Some(vec![ - players[1].clone(), - players[3].clone(), - players[5].clone(), - players[6].clone(), - ]) - } else if i == 1 { - Some(vec![players[2].clone(), players[6].clone()]) - } else { - None - }; - let call_status = populate_player_call_status(players[i].clone(), followers); - player_status.push(PlayerWithCallStatus::new(players[i].clone(), call_status)); - } - player_status -} - -pub fn static_players_with_call_status() -> Vec { - let players = static_players(); - let mut player_0_status = PlayerCallStatus::new(); - let player_1_status = PlayerCallStatus::new(); - let player_2_status = PlayerCallStatus::new(); - let mut player_3_status = PlayerCallStatus::new(); - let mut player_4_status = PlayerCallStatus::new(); - - player_0_status.screen_share_status = ScreenShareStatus::Shared; - player_0_status.followers = Some(vec![players[1].clone(), players[3].clone()]); - - player_3_status.voice_activity = 0.5; - player_4_status.mic_status = MicStatus::Muted; - player_4_status.in_current_project = false; - - vec![ - PlayerWithCallStatus::new(players[0].clone(), player_0_status), - PlayerWithCallStatus::new(players[1].clone(), player_1_status), - PlayerWithCallStatus::new(players[2].clone(), player_2_status), - PlayerWithCallStatus::new(players[3].clone(), player_3_status), - PlayerWithCallStatus::new(players[4].clone(), player_4_status), - ] -} - -pub fn static_project_panel_project_items() -> Vec { - vec![ - ListEntry::new(Label::new("zed")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(0) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new(".cargo")) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".config")) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".git").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".cargo")) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".idea").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new("assets")) - .left_icon(Icon::Folder.into()) - .indent_level(1) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("cargo-target").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new("crates")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(1) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("activity_indicator")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("ai")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("audio")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("auto_update")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("breadcrumbs")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("call")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("sqlez").color(LabelColor::Modified)) - .left_icon(Icon::Folder.into()) - .indent_level(2) - .set_toggle(ToggleState::NotToggled), - ListEntry::new(Label::new("gpui2")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(2) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("src")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(3) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("derive_element.rs")) - .left_icon(Icon::FileRust.into()) - .indent_level(4), - ListEntry::new(Label::new("storybook").color(LabelColor::Modified)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(1) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("docs").color(LabelColor::Default)) - .left_icon(Icon::Folder.into()) - .indent_level(2) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("src").color(LabelColor::Modified)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(3) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("ui").color(LabelColor::Modified)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(4) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("component").color(LabelColor::Created)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(5) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("facepile.rs").color(LabelColor::Default)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("follow_group.rs").color(LabelColor::Default)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("list_item.rs").color(LabelColor::Created)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("tab.rs").color(LabelColor::Default)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("target").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".dockerignore")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new(".DS_Store").color(LabelColor::Hidden)) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new("Cargo.lock")) - .left_icon(Icon::FileLock.into()) - .indent_level(1), - ListEntry::new(Label::new("Cargo.toml")) - .left_icon(Icon::FileToml.into()) - .indent_level(1), - ListEntry::new(Label::new("Dockerfile")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new("Procfile")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new("README.md")) - .left_icon(Icon::FileDoc.into()) - .indent_level(1), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_project_panel_single_items() -> Vec { - vec![ - ListEntry::new(Label::new("todo.md")) - .left_icon(Icon::FileDoc.into()) - .indent_level(0), - ListEntry::new(Label::new("README.md")) - .left_icon(Icon::FileDoc.into()) - .indent_level(0), - ListEntry::new(Label::new("config.json")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(0), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_collab_panel_current_call() -> Vec { - vec![ - ListEntry::new(Label::new("as-cii")).left_avatar("http://github.com/as-cii.png?s=50"), - ListEntry::new(Label::new("nathansobo")) - .left_avatar("http://github.com/nathansobo.png?s=50"), - ListEntry::new(Label::new("maxbrunsfeld")) - .left_avatar("http://github.com/maxbrunsfeld.png?s=50"), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_collab_panel_channels() -> Vec { - vec![ - ListEntry::new(Label::new("zed")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(0), - ListEntry::new(Label::new("community")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(1), - ListEntry::new(Label::new("dashboards")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("feedback")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("teams-in-channels-alpha")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("current-projects")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(1), - ListEntry::new(Label::new("codegen")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("gpui2")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("livestreaming")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("open-source")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("replace")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("semantic-index")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("vim")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("web-tech")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn example_editor_actions() -> Vec { - vec![ - PaletteItem::new("New File").keybinding(Keybinding::new( - "N".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Open File").keybinding(Keybinding::new( - "O".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Save File").keybinding(Keybinding::new( - "S".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Cut").keybinding(Keybinding::new( - "X".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Copy").keybinding(Keybinding::new( - "C".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Paste").keybinding(Keybinding::new( - "V".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Undo").keybinding(Keybinding::new( - "Z".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Redo").keybinding(Keybinding::new( - "Z".to_string(), - ModifierKeys::new().control(true).shift(true), - )), - PaletteItem::new("Find").keybinding(Keybinding::new( - "F".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Replace").keybinding(Keybinding::new( - "R".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Jump to Line"), - PaletteItem::new("Select All"), - PaletteItem::new("Deselect All"), - PaletteItem::new("Switch Document"), - PaletteItem::new("Insert Line Below"), - PaletteItem::new("Insert Line Above"), - PaletteItem::new("Move Line Up"), - PaletteItem::new("Move Line Down"), - PaletteItem::new("Toggle Comment"), - PaletteItem::new("Delete Line"), - ] -} - -pub fn empty_editor_example() -> Editor { - Editor { - tabs: static_tabs_example(), - path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), - symbols: vec![], - buffer: empty_buffer_example(), - } -} - -pub fn empty_buffer_example() -> Buffer { - Buffer::new().set_rows(Some(BufferRows::default())) -} - -pub fn hello_world_rust_editor_example(theme: &Theme) -> Editor { - Editor { - tabs: static_tabs_example(), - path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), - symbols: vec![Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ])], - buffer: hello_world_rust_buffer_example(theme), - } -} - -pub fn hello_world_rust_buffer_example(theme: &Theme) -> Buffer { - Buffer::new() - .set_title("hello_world.rs".to_string()) - .set_path("src/hello_world.rs".to_string()) - .set_language("rust".to_string()) - .set_rows(Some(BufferRows { - show_line_numbers: true, - rows: hello_world_rust_buffer_rows(theme), - })) -} - -pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { - let show_line_number = true; - - vec![ - BufferRow { - line_number: 1, - code_action: false, - current: true, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "() {".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 2, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: " // Statements here are executed when the compiled binary is called." - .to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 3, - code_action: false, - current: false, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 4, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: " // Print text to the console.".to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 5, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: " println!(".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "\"Hello, world!\"".to_string(), - color: HighlightColor::String.hsla(&theme), - }, - HighlightedText { - text: ");".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 6, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "}".to_string(), - color: HighlightColor::Default.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - ] -} - -pub fn hello_world_rust_editor_with_status_example(theme: &Theme) -> Editor { - Editor { - tabs: static_tabs_example(), - path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), - symbols: vec![Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ])], - buffer: hello_world_rust_buffer_with_status_example(theme), - } -} - -pub fn hello_world_rust_buffer_with_status_example(theme: &Theme) -> Buffer { - Buffer::new() - .set_title("hello_world.rs".to_string()) - .set_path("src/hello_world.rs".to_string()) - .set_language("rust".to_string()) - .set_rows(Some(BufferRows { - show_line_numbers: true, - rows: hello_world_rust_with_status_buffer_rows(theme), - })) -} - -pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec { - let show_line_number = true; - - vec![ - BufferRow { - line_number: 1, - code_action: false, - current: true, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "() {".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 2, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "// Statements here are executed when the compiled binary is called." - .to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::Modified, - show_line_number, - }, - BufferRow { - line_number: 3, - code_action: false, - current: false, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 4, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: " // Print text to the console.".to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 5, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: " println!(".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "\"Hello, world!\"".to_string(), - color: HighlightColor::String.hsla(&theme), - }, - HighlightedText { - text: ");".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 6, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "}".to_string(), - color: HighlightColor::Default.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 7, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "".to_string(), - color: HighlightColor::Default.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::Created, - show_line_number, - }, - BufferRow { - line_number: 8, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "// Marshall and Nate were here".to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::Created, - show_line_number, - }, - ] -} - -pub fn terminal_buffer(theme: &Theme) -> Buffer { - Buffer::new() - .set_title("zed — fish".to_string()) - .set_rows(Some(BufferRows { - show_line_numbers: false, - rows: terminal_buffer_rows(theme), - })) -} - -pub fn terminal_buffer_rows(theme: &Theme) -> Vec { - let show_line_number = false; - - vec![ - BufferRow { - line_number: 1, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: "maxdeviant ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "in ".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "profaned-capital ".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "in ".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "~/p/zed ".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "on ".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: " gpui2-ui ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 2, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "λ ".to_string(), - color: HighlightColor::String.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - ] -} diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs deleted file mode 100644 index d4c8c26273..0000000000 --- a/crates/ui/src/theme.rs +++ /dev/null @@ -1,196 +0,0 @@ -use std::collections::HashMap; -use std::fmt; -use std::marker::PhantomData; -use std::sync::Arc; - -use gpui2::color::Hsla; -use gpui2::element::Element; -use gpui2::{serde_json, AppContext, IntoElement, Vector2F, ViewContext, WindowContext}; -use serde::de::Visitor; -use serde::{Deserialize, Deserializer}; -use theme::ThemeSettings; - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Theme { - pub name: String, - pub is_light: bool, - pub lowest: Layer, - pub middle: Layer, - pub highest: Layer, - pub popover_shadow: Shadow, - pub modal_shadow: Shadow, - #[serde(deserialize_with = "deserialize_player_colors")] - pub players: Vec, - #[serde(deserialize_with = "deserialize_syntax_colors")] - pub syntax: HashMap, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Layer { - pub base: StyleSet, - pub variant: StyleSet, - pub on: StyleSet, - pub accent: StyleSet, - pub positive: StyleSet, - pub warning: StyleSet, - pub negative: StyleSet, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct StyleSet { - #[serde(rename = "default")] - pub default: ContainerColors, - pub hovered: ContainerColors, - pub pressed: ContainerColors, - pub active: ContainerColors, - pub disabled: ContainerColors, - pub inverted: ContainerColors, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct ContainerColors { - pub background: Hsla, - pub foreground: Hsla, - pub border: Hsla, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct PlayerColors { - pub selection: Hsla, - pub cursor: Hsla, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Shadow { - pub blur: u8, - pub color: Hsla, - pub offset: Vec, -} - -fn deserialize_player_colors<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct PlayerArrayVisitor; - - impl<'de> Visitor<'de> for PlayerArrayVisitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("an object with integer keys") - } - - fn visit_map>( - self, - mut map: A, - ) -> Result { - let mut players = Vec::with_capacity(8); - while let Some((key, value)) = map.next_entry::()? { - if key < 8 { - players.push(value); - } else { - return Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Unsigned(key as u64), - &"a key in range 0..7", - )); - } - } - Ok(players) - } - } - - deserializer.deserialize_map(PlayerArrayVisitor) -} - -fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - #[derive(Deserialize)] - struct ColorWrapper { - color: Hsla, - } - - struct SyntaxVisitor; - - impl<'de> Visitor<'de> for SyntaxVisitor { - type Value = HashMap; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a map with keys and objects with a single color field as values") - } - - fn visit_map(self, mut map: M) -> Result, M::Error> - where - M: serde::de::MapAccess<'de>, - { - let mut result = HashMap::new(); - while let Some(key) = map.next_key()? { - let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla - result.insert(key, wrapper.color); - } - Ok(result) - } - } - deserializer.deserialize_map(SyntaxVisitor) -} - -#[derive(IntoElement)] -pub struct Themed> { - pub(crate) theme: Theme, - pub(crate) child: E, - pub(crate) view_type: PhantomData, -} - -impl> Element for Themed { - type PaintState = E::PaintState; - - fn layout( - &mut self, - view: &mut V, - cx: &mut ViewContext, - ) -> anyhow::Result<(gpui2::LayoutId, Self::PaintState)> - where - Self: Sized, - { - cx.push_theme(self.theme.clone()); - let result = self.child.layout(view, cx); - cx.pop_theme(); - result - } - - fn paint( - &mut self, - view: &mut V, - parent_origin: Vector2F, - layout: &gpui2::Layout, - state: &mut Self::PaintState, - cx: &mut ViewContext, - ) where - Self: Sized, - { - cx.push_theme(self.theme.clone()); - self.child.paint(view, parent_origin, layout, state, cx); - cx.pop_theme(); - } -} - -fn preferred_theme(cx: &AppContext) -> Theme { - settings::get::(cx) - .theme - .deserialized_base_theme - .lock() - .get_or_insert_with(|| { - let theme: Theme = - serde_json::from_value(settings::get::(cx).theme.base_theme.clone()) - .unwrap(); - Box::new(theme) - }) - .downcast_ref::() - .unwrap() - .clone() -} - -pub fn theme(cx: &WindowContext) -> Arc { - cx.theme::() -} diff --git a/crates/ui/src/tokens.rs b/crates/ui/src/tokens.rs deleted file mode 100644 index 5fd5b69a2a..0000000000 --- a/crates/ui/src/tokens.rs +++ /dev/null @@ -1,25 +0,0 @@ -use gpui2::geometry::AbsoluteLength; -use gpui2::{hsla, Hsla}; - -#[derive(Clone, Copy)] -pub struct Token { - pub list_indent_depth: AbsoluteLength, - pub default_panel_size: AbsoluteLength, - pub state_hover_background: Hsla, - pub state_active_background: Hsla, -} - -impl Default for Token { - fn default() -> Self { - Self { - list_indent_depth: AbsoluteLength::Rems(0.5), - default_panel_size: AbsoluteLength::Rems(16.), - state_hover_background: hsla(0.0, 0.0, 0.0, 0.08), - state_active_background: hsla(0.0, 0.0, 0.0, 0.16), - } - } -} - -pub fn token() -> Token { - Token::default() -} diff --git a/crates/ui/tracker.md b/crates/ui/tracker.md deleted file mode 100644 index 11b5804db2..0000000000 --- a/crates/ui/tracker.md +++ /dev/null @@ -1,133 +0,0 @@ -* = Not in the app today - -## Template -- [ ] Workspace -- [ ] Title Bar -- [ ] Project Panel -- [ ] Collab Panel -- [ ] Project Diagnosics -- [ ] Project Search -- [ ] Feedback Editor -- [ ] Terminal -- [ ] Assistant -- [ ] Chat* -- [ ] Notifications* -- [ ] Status Bar -- [ ] Panes -- [ ] Pane -- [ ] Editor -- [ ] Tab Bar -- [ ] Tool Bar -- [ ] Buffer -- [ ] Zoomed Editor (Modal) - -### Palettes -- [ ] Project Files Palette (⌘-P) -- [ ] Command Palette (⌘-SHIFT-P) -- [ ] Recent Projects Palette (⌘-OPT-O) -- [ ] Recent Branches Palette (⌘-OPT-B) -- [ ] Project Symbols (⌘-T) -- [ ] Theme Palette (⌘-K, ⌘-T) -- [ ] Outline View (⌘-SHIFT-O) - -### Debug Views -- [ ] LSP Tool -- [ ] Syntax Tree - -## Modules - -### Title Bar -- [ ] Traffic Lights -- [ ] Host Menu -- [ ] Project Menu -- [ ] Branch Menu -- [ ] Collaborators -- [ ] Add Collaborator* -- [ ] Project Controls -- [ ] Call Controls -- [ ] User Menu - -### Project Panel -- [ ] Open Editors* -- [ ] Open Files (Non-project files) -- [ ] Project Files -- [ ] Root Folder - Context Menu -- [ ] Folder - Context Menu -- [ ] File - Context Menu -- [ ] Project Filter* - -### Collab Panel -- [ ] Current Call -- [ ] Channels -- [ ] Channel - Context Menu -- [ ] Contacts -- [ ] Collab Filter - -### Project Diagnosics -WIP - -### Feedback Editor -- [ ] Feedback Header -- [ ] Editor -- [ ] Feedback Actions - -### Terminal -- [ ] Terminal Toolbar* -- [ ] Terminal Line -- [ ] Terminal Input - -### Assistant -- [ ] Toolbar -- [ ] History / Past Conversations -- [ ] Model Controls / Token Counter -- [ ] Chat Editor - -### Chat -WIP - -### Notifications -WIP - -### Status Bar -- [ ] Status Bar Tool (Icon) -- [ ] Status Bar Tool (Text) -- [ ] Status Bar Tool - Context Menu -- [ ] Status Bar Tool - Popover Palette -- [ ] Status Bar Tool - Popover Menu -- [ ] Diagnostic Message -- [ ] LSP Message -- [ ] Update message (New version available, downloading, etc) - -### Panes/Pane - -- [ ] Editor -- [ ] Split Divider/Control - -### Editor -- [ ] Editor -- [ ] Read-only Editor -- [ ] Rendered Markdown View* - -### Tab Bar -- [ ] Navigation History / Control -- [ ] Tabs -- [ ] Editor Controls (New, Split, Zoom) - -### Tool Bar -- [ ] Breadcrumb -- [ ] Editor Tool (Togglable) -- [ ] Buffer Search - -### Buffer - -### Zoomed Editor (Modal) -- [ ] Modal View - -### Palette -- [ ] Input -- [ ] Section Title -- [ ] List - -## Components - -- [ ] Context Menu From c4fc9f7ed81e6f0199c24ed99a60a64dd1eb98cb Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 19:28:17 -0400 Subject: [PATCH 81/92] Eagerly attempt to resolve missing completion documentation --- crates/editor/src/editor.rs | 264 ++++++++++++++++++++++++++---------- 1 file changed, 191 insertions(+), 73 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d7ef82da36..bdacf0be38 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -25,7 +25,7 @@ use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Context, Result}; use blink_manager::BlinkManager; -use client::{ClickhouseEvent, Collaborator, ParticipantIndex, TelemetrySettings}; +use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings}; use clock::{Global, ReplicaId}; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use convert_case::{Case, Casing}; @@ -62,8 +62,8 @@ use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, - IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, - SelectionGoal, TransactionId, + IndentSize, Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, + Selection, SelectionGoal, TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -954,7 +954,7 @@ impl CompletionsMenu { ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -967,7 +967,7 @@ impl CompletionsMenu { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -980,7 +980,7 @@ impl CompletionsMenu { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -991,16 +991,99 @@ impl CompletionsMenu { ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } - fn attempt_resolve_selected_completion( + fn pre_resolve_completion_documentation( + &self, + project: Option>, + cx: &mut ViewContext, + ) { + let Some(project) = project else { + return; + }; + let client = project.read(cx).client(); + let language_registry = project.read(cx).languages().clone(); + + let is_remote = project.read(cx).is_remote(); + let project_id = project.read(cx).remote_id(); + + let completions = self.completions.clone(); + let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect(); + + cx.spawn(move |this, mut cx| async move { + if is_remote { + let Some(project_id) = project_id else { + log::error!("Remote project without remote_id"); + return; + }; + + for completion_index in completion_indices { + let completions_guard = completions.read(); + let completion = &completions_guard[completion_index]; + if completion.documentation.is_some() { + continue; + } + + let server_id = completion.server_id; + let completion = completion.lsp_completion.clone(); + drop(completions_guard); + + Self::resolve_completion_documentation_remote( + project_id, + server_id, + completions.clone(), + completion_index, + completion, + client.clone(), + language_registry.clone(), + ) + .await; + + _ = this.update(&mut cx, |_, cx| cx.notify()); + } + } else { + for completion_index in completion_indices { + let completions_guard = completions.read(); + let completion = &completions_guard[completion_index]; + if completion.documentation.is_some() { + continue; + } + + let server_id = completion.server_id; + let completion = completion.lsp_completion.clone(); + drop(completions_guard); + + let server = project.read_with(&mut cx, |project, _| { + project.language_server_for_id(server_id) + }); + let Some(server) = server else { + return; + }; + + Self::resolve_completion_documentation_local( + server, + completions.clone(), + completion_index, + completion, + language_registry.clone(), + ) + .await; + + _ = this.update(&mut cx, |_, cx| cx.notify()); + } + } + }) + .detach(); + } + + fn attempt_resolve_selected_completion_documentation( &mut self, project: Option<&ModelHandle>, cx: &mut ViewContext, ) { - let index = self.matches[self.selected_item].candidate_id; + let completion_index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { return; }; @@ -1008,7 +1091,7 @@ impl CompletionsMenu { let completions = self.completions.clone(); let completions_guard = completions.read(); - let completion = &completions_guard[index]; + let completion = &completions_guard[completion_index]; if completion.documentation.is_some() { return; } @@ -1024,54 +1107,95 @@ impl CompletionsMenu { }; let client = project.read(cx).client(); - let request = proto::ResolveCompletionDocumentation { - project_id, - language_server_id: server_id.0 as u64, - lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), - }; - cx.spawn(|this, mut cx| async move { - let Some(response) = client - .request(request) - .await - .context("completion documentation resolve proto request") - .log_err() - else { - return; - }; - - if response.text.is_empty() { - let mut completions = completions.write(); - let completion = &mut completions[index]; - completion.documentation = Some(Documentation::Undocumented); - } - - let documentation = if response.is_markdown { - Documentation::MultiLineMarkdown( - markdown::parse_markdown(&response.text, &language_registry, None).await, - ) - } else if response.text.lines().count() <= 1 { - Documentation::SingleLine(response.text) - } else { - Documentation::MultiLinePlainText(response.text) - }; - - let mut completions = completions.write(); - let completion = &mut completions[index]; - completion.documentation = Some(documentation); - drop(completions); + cx.spawn(move |this, mut cx| async move { + Self::resolve_completion_documentation_remote( + project_id, + server_id, + completions.clone(), + completion_index, + completion, + client, + language_registry.clone(), + ) + .await; _ = this.update(&mut cx, |_, cx| cx.notify()); }) .detach(); + } else { + let Some(server) = project.read(cx).language_server_for_id(server_id) else { + return; + }; - return; + cx.spawn(move |this, mut cx| async move { + Self::resolve_completion_documentation_local( + server, + completions, + completion_index, + completion, + language_registry, + ) + .await; + + _ = this.update(&mut cx, |_, cx| cx.notify()); + }) + .detach(); } + } - let Some(server) = project.read(cx).language_server_for_id(server_id) else { + async fn resolve_completion_documentation_remote( + project_id: u64, + server_id: LanguageServerId, + completions: Arc>>, + completion_index: usize, + completion: lsp::CompletionItem, + client: Arc, + language_registry: Arc, + ) { + let request = proto::ResolveCompletionDocumentation { + project_id, + language_server_id: server_id.0 as u64, + lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), + }; + + let Some(response) = client + .request(request) + .await + .context("completion documentation resolve proto request") + .log_err() + else { return; }; + if response.text.is_empty() { + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(Documentation::Undocumented); + } + + let documentation = if response.is_markdown { + Documentation::MultiLineMarkdown( + markdown::parse_markdown(&response.text, &language_registry, None).await, + ) + } else if response.text.lines().count() <= 1 { + Documentation::SingleLine(response.text) + } else { + Documentation::MultiLinePlainText(response.text) + }; + + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(documentation); + } + + async fn resolve_completion_documentation_local( + server: Arc, + completions: Arc>>, + completion_index: usize, + completion: lsp::CompletionItem, + language_registry: Arc, + ) { let can_resolve = server .capabilities() .completion_provider @@ -1082,33 +1206,27 @@ impl CompletionsMenu { return; } - cx.spawn(|this, mut cx| async move { - let request = server.request::(completion); - let Some(completion_item) = request.await.log_err() else { - return; - }; + let request = server.request::(completion); + let Some(completion_item) = request.await.log_err() else { + return; + }; - if let Some(lsp_documentation) = completion_item.documentation { - let documentation = language::prepare_completion_documentation( - &lsp_documentation, - &language_registry, - None, // TODO: Try to reasonably work out which language the completion is for - ) - .await; + if let Some(lsp_documentation) = completion_item.documentation { + let documentation = language::prepare_completion_documentation( + &lsp_documentation, + &language_registry, + None, // TODO: Try to reasonably work out which language the completion is for + ) + .await; - let mut completions = completions.write(); - let completion = &mut completions[index]; - completion.documentation = Some(documentation); - drop(completions); - - _ = this.update(&mut cx, |_, cx| cx.notify()); - } else { - let mut completions = completions.write(); - let completion = &mut completions[index]; - completion.documentation = Some(Documentation::Undocumented); - } - }) - .detach(); + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(documentation); + } else { + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(Documentation::Undocumented); + } } fn visible(&self) -> bool { @@ -3450,7 +3568,7 @@ impl Editor { None } else { _ = this.update(&mut cx, |editor, cx| { - menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); + menu.pre_resolve_completion_documentation(editor.project.clone(), cx); }); Some(menu) } From 1c3ecc4ad242047a700a602ceda0b3630b7118d0 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 21:00:31 -0400 Subject: [PATCH 82/92] Whooooops --- crates/editor/src/editor.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bdacf0be38..1a17f38f92 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3596,7 +3596,6 @@ impl Editor { let menu = menu.unwrap(); *context_menu = Some(ContextMenu::Completions(menu)); drop(context_menu); - this.completion_tasks.clear(); this.discard_copilot_suggestion(cx); cx.notify(); } else if this.completion_tasks.is_empty() { From ec4391b88e9a41e47de295896cb20764f007e053 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 22:08:47 -0400 Subject: [PATCH 83/92] Add setting to disable completion docs --- assets/settings/default.json | 3 +++ crates/editor/src/editor.rs | 24 ++++++++++++++++++++++-- crates/editor/src/editor_settings.rs | 2 ++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 8fb73a2ecb..8a3598eed1 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -50,6 +50,9 @@ // Whether to pop the completions menu while typing in an editor without // explicitly requesting it. "show_completions_on_input": true, + // Whether to display inline and alongside documentation for items in the + // completions menu + "show_completion_documentation": true, // Whether to show wrap guides in the editor. Setting this to true will // show a guide at the 'preferred_line_length' value if softwrap is set to // 'preferred_line_length', and will show any additional guides as specified diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1a17f38f92..bb6d693d82 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1000,6 +1000,11 @@ impl CompletionsMenu { project: Option>, cx: &mut ViewContext, ) { + let settings = settings::get::(cx); + if !settings.show_completion_documentation { + return; + } + let Some(project) = project else { return; }; @@ -1083,6 +1088,11 @@ impl CompletionsMenu { project: Option<&ModelHandle>, cx: &mut ViewContext, ) { + let settings = settings::get::(cx); + if !settings.show_completion_documentation { + return; + } + let completion_index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { return; @@ -1241,6 +1251,9 @@ impl CompletionsMenu { ) -> AnyElement { enum CompletionTag {} + let settings = settings::get::(cx); + let show_completion_documentation = settings.show_completion_documentation; + let widest_completion_ix = self .matches .iter() @@ -1252,7 +1265,9 @@ impl CompletionsMenu { let mut len = completion.label.text.chars().count(); if let Some(Documentation::SingleLine(text)) = documentation { - len += text.chars().count(); + if show_completion_documentation { + len += text.chars().count(); + } } len @@ -1273,7 +1288,12 @@ impl CompletionsMenu { let item_ix = start_ix + ix; let candidate_id = mat.candidate_id; let completion = &completions_guard[candidate_id]; - let documentation = &completion.documentation; + + let documentation = if show_completion_documentation { + &completion.documentation + } else { + &None + }; items.push( MouseEventHandler::new::( diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index b06f23429a..75f8b800f9 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -7,6 +7,7 @@ pub struct EditorSettings { pub cursor_blink: bool, pub hover_popover_enabled: bool, pub show_completions_on_input: bool, + pub show_completion_documentation: bool, pub use_on_type_format: bool, pub scrollbar: Scrollbar, pub relative_line_numbers: bool, @@ -33,6 +34,7 @@ pub struct EditorSettingsContent { pub cursor_blink: Option, pub hover_popover_enabled: Option, pub show_completions_on_input: Option, + pub show_completion_documentation: Option, pub use_on_type_format: Option, pub scrollbar: Option, pub relative_line_numbers: Option, From 525ff6bf7458a2f48747fc9e06dd10e83845554b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Oct 2023 10:20:55 +0300 Subject: [PATCH 84/92] Remove zed -> ... -> semantic_index -> zed Cargo dependency cycle --- Cargo.lock | 2 +- crates/semantic_index/Cargo.toml | 4 ---- crates/zed/Cargo.toml | 4 ++++ .../examples/eval.rs => zed/examples/semantic_index_eval.rs} | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename crates/{semantic_index/examples/eval.rs => zed/examples/semantic_index_eval.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index 01153ca0f8..85b80fa487 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6956,7 +6956,6 @@ dependencies = [ "unindent", "util", "workspace", - "zed", ] [[package]] @@ -10049,6 +10048,7 @@ name = "zed" version = "0.109.0" dependencies = [ "activity_indicator", + "ai", "anyhow", "assistant", "async-compression", diff --git a/crates/semantic_index/Cargo.toml b/crates/semantic_index/Cargo.toml index 34850f7035..1febb2af78 100644 --- a/crates/semantic_index/Cargo.toml +++ b/crates/semantic_index/Cargo.toml @@ -51,7 +51,6 @@ workspace = { path = "../workspace", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"]} rust-embed = { version = "8.0", features = ["include-exclude"] } client = { path = "../client" } -zed = { path = "../zed"} node_runtime = { path = "../node_runtime"} pretty_assertions.workspace = true @@ -70,6 +69,3 @@ tree-sitter-elixir.workspace = true tree-sitter-lua.workspace = true tree-sitter-ruby.workspace = true tree-sitter-php.workspace = true - -[[example]] -name = "eval" diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 4174f7d6d5..f9abcc1e91 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -15,6 +15,9 @@ doctest = false name = "Zed" path = "src/main.rs" +[[example]] +name = "semantic_index_eval" + [dependencies] audio = { path = "../audio" } activity_indicator = { path = "../activity_indicator" } @@ -141,6 +144,7 @@ urlencoding = "2.1.2" uuid.workspace = true [dev-dependencies] +ai = { path = "../ai" } call = { path = "../call", features = ["test-support"] } client = { path = "../client", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/semantic_index/examples/eval.rs b/crates/zed/examples/semantic_index_eval.rs similarity index 100% rename from crates/semantic_index/examples/eval.rs rename to crates/zed/examples/semantic_index_eval.rs From 803ab81eb6e1cc718679fdba862163c23d7ef174 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Oct 2023 12:13:18 +0300 Subject: [PATCH 85/92] Update diagnostics indicator when diagnostics are udpated --- crates/diagnostics/src/items.rs | 4 ++++ crates/project/src/project.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index c3733018b6..8d3c2fedd6 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -38,6 +38,10 @@ impl DiagnosticIndicator { this.in_progress_checks.remove(language_server_id); cx.notify(); } + project::Event::DiagnosticsUpdated { .. } => { + this.summary = project.read(cx).diagnostic_summary(cx); + cx.notify(); + } _ => {} }) .detach(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f9e1b1ce96..e3251d7483 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2934,8 +2934,8 @@ impl Project { move |mut params, mut cx| { let this = this; let adapter = adapter.clone(); - adapter.process_diagnostics(&mut params); if let Some(this) = this.upgrade(&cx) { + adapter.process_diagnostics(&mut params); this.update(&mut cx, |this, cx| { this.update_diagnostics( server_id, From bfbe4ae4b47140024cdaf8d9680956bd228d6b84 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 13 Oct 2023 18:58:59 +0200 Subject: [PATCH 86/92] Piotr/z 651 vue support (#3123) Release Notes: - Added Vue language support. --- Cargo.lock | 10 + Cargo.toml | 2 +- crates/language/src/language.rs | 3 - crates/project/src/project.rs | 25 ++- crates/util/src/github.rs | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 10 +- crates/zed/src/languages/vue.rs | 214 ++++++++++++++++++++ crates/zed/src/languages/vue/brackets.scm | 2 + crates/zed/src/languages/vue/config.toml | 14 ++ crates/zed/src/languages/vue/highlights.scm | 15 ++ crates/zed/src/languages/vue/injections.scm | 7 + 12 files changed, 286 insertions(+), 18 deletions(-) create mode 100644 crates/zed/src/languages/vue.rs create mode 100644 crates/zed/src/languages/vue/brackets.scm create mode 100644 crates/zed/src/languages/vue/config.toml create mode 100644 crates/zed/src/languages/vue/highlights.scm create mode 100644 crates/zed/src/languages/vue/injections.scm diff --git a/Cargo.lock b/Cargo.lock index 85b80fa487..2ef86073ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8792,6 +8792,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-vue" +version = "0.0.1" +source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=95b2890#95b28908d90e928c308866f7631e73ef6e1d4b5f" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-yaml" version = "0.0.1" @@ -10161,6 +10170,7 @@ dependencies = [ "tree-sitter-svelte", "tree-sitter-toml", "tree-sitter-typescript", + "tree-sitter-vue", "tree-sitter-yaml", "unindent", "url", diff --git a/Cargo.toml b/Cargo.toml index 532610efd6..995cd15edd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -149,7 +149,7 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", tree-sitter-lua = "0.0.14" tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} - +tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "95b2890"} [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index bd389652a0..eb6f6e89f7 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -110,7 +110,6 @@ pub struct LanguageServerName(pub Arc); pub struct CachedLspAdapter { pub name: LanguageServerName, pub short_name: &'static str, - pub initialization_options: Option, pub disk_based_diagnostic_sources: Vec, pub disk_based_diagnostics_progress_token: Option, pub language_ids: HashMap, @@ -121,7 +120,6 @@ impl CachedLspAdapter { pub async fn new(adapter: Arc) -> Arc { let name = adapter.name().await; let short_name = adapter.short_name(); - let initialization_options = adapter.initialization_options().await; let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await; let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token().await; @@ -130,7 +128,6 @@ impl CachedLspAdapter { Arc::new(CachedLspAdapter { name, short_name, - initialization_options, disk_based_diagnostic_sources, disk_based_diagnostics_progress_token, language_ids, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e3251d7483..875086a4e3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2751,15 +2751,6 @@ impl Project { let lsp = project_settings.lsp.get(&adapter.name.0); let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); - let mut initialization_options = adapter.initialization_options.clone(); - match (&mut initialization_options, override_options) { - (Some(initialization_options), Some(override_options)) => { - merge_json_value_into(override_options, initialization_options); - } - (None, override_options) => initialization_options = override_options, - _ => {} - } - let server_id = pending_server.server_id; let container_dir = pending_server.container_dir.clone(); let state = LanguageServerState::Starting({ @@ -2771,7 +2762,7 @@ impl Project { cx.spawn_weak(|this, mut cx| async move { let result = Self::setup_and_insert_language_server( this, - initialization_options, + override_options, pending_server, adapter.clone(), language.clone(), @@ -2874,7 +2865,7 @@ impl Project { async fn setup_and_insert_language_server( this: WeakModelHandle, - initialization_options: Option, + override_initialization_options: Option, pending_server: PendingLanguageServer, adapter: Arc, language: Arc, @@ -2884,7 +2875,7 @@ impl Project { ) -> Result>> { let setup = Self::setup_pending_language_server( this, - initialization_options, + override_initialization_options, pending_server, adapter.clone(), server_id, @@ -2916,7 +2907,7 @@ impl Project { async fn setup_pending_language_server( this: WeakModelHandle, - initialization_options: Option, + override_options: Option, pending_server: PendingLanguageServer, adapter: Arc, server_id: LanguageServerId, @@ -3062,6 +3053,14 @@ impl Project { } }) .detach(); + let mut initialization_options = adapter.adapter.initialization_options().await; + match (&mut initialization_options, override_options) { + (Some(initialization_options), Some(override_options)) => { + merge_json_value_into(override_options, initialization_options); + } + (None, override_options) => initialization_options = override_options, + _ => {} + } let language_server = language_server.initialize(initialization_options).await?; diff --git a/crates/util/src/github.rs b/crates/util/src/github.rs index b1e981ae49..a3df4c996b 100644 --- a/crates/util/src/github.rs +++ b/crates/util/src/github.rs @@ -16,6 +16,7 @@ pub struct GithubRelease { pub pre_release: bool, pub assets: Vec, pub tarball_url: String, + pub zipball_url: String, } #[derive(Deserialize, Debug)] diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index f9abcc1e91..aeabd4b453 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -138,6 +138,7 @@ tree-sitter-yaml.workspace = true tree-sitter-lua.workspace = true tree-sitter-nix.workspace = true tree-sitter-nu.workspace = true +tree-sitter-vue.workspace = true url = "2.2" urlencoding = "2.1.2" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 04e5292a7d..caf3cbf7c9 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -24,6 +24,7 @@ mod rust; mod svelte; mod tailwind; mod typescript; +mod vue; mod yaml; // 1. Add tree-sitter-{language} parser to zed crate @@ -190,13 +191,20 @@ pub fn init( language( "php", tree_sitter_php::language(), - vec![Arc::new(php::IntelephenseLspAdapter::new(node_runtime))], + vec![Arc::new(php::IntelephenseLspAdapter::new( + node_runtime.clone(), + ))], ); language("elm", tree_sitter_elm::language(), vec![]); language("glsl", tree_sitter_glsl::language(), vec![]); language("nix", tree_sitter_nix::language(), vec![]); language("nu", tree_sitter_nu::language(), vec![]); + language( + "vue", + tree_sitter_vue::language(), + vec![Arc::new(vue::VueLspAdapter::new(node_runtime))], + ); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed/src/languages/vue.rs b/crates/zed/src/languages/vue.rs new file mode 100644 index 0000000000..f0374452df --- /dev/null +++ b/crates/zed/src/languages/vue.rs @@ -0,0 +1,214 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use futures::StreamExt; +pub use language::*; +use lsp::{CodeActionKind, LanguageServerBinary}; +use node_runtime::NodeRuntime; +use parking_lot::Mutex; +use serde_json::Value; +use smol::fs::{self}; +use std::{ + any::Any, + ffi::OsString, + path::{Path, PathBuf}, + sync::Arc, +}; +use util::ResultExt; + +pub struct VueLspVersion { + vue_version: String, + ts_version: String, +} + +pub struct VueLspAdapter { + node: Arc, + typescript_install_path: Mutex>, +} + +impl VueLspAdapter { + const SERVER_PATH: &'static str = + "node_modules/@vue/language-server/bin/vue-language-server.js"; + // TODO: this can't be hardcoded, yet we have to figure out how to pass it in initialization_options. + const TYPESCRIPT_PATH: &'static str = "node_modules/typescript/lib"; + pub fn new(node: Arc) -> Self { + let typescript_install_path = Mutex::new(None); + Self { + node, + typescript_install_path, + } + } +} +#[async_trait] +impl super::LspAdapter for VueLspAdapter { + async fn name(&self) -> LanguageServerName { + LanguageServerName("vue-language-server".into()) + } + + fn short_name(&self) -> &'static str { + "vue-language-server" + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new(VueLspVersion { + vue_version: self + .node + .npm_package_latest_version("@vue/language-server") + .await?, + ts_version: self.node.npm_package_latest_version("typescript").await?, + }) as Box<_>) + } + async fn initialization_options(&self) -> Option { + let typescript_sdk_path = self.typescript_install_path.lock(); + let typescript_sdk_path = typescript_sdk_path + .as_ref() + .expect("initialization_options called without a container_dir for typescript"); + + Some(serde_json::json!({ + "typescript": { + "tsdk": typescript_sdk_path + } + })) + } + fn code_action_kinds(&self) -> Option> { + // REFACTOR is explicitly disabled, as vue-lsp does not adhere to LSP protocol for code actions with these - it + // sends back a CodeAction with neither `command` nor `edits` fields set, which is against the spec. + Some(vec![ + CodeActionKind::EMPTY, + CodeActionKind::QUICKFIX, + CodeActionKind::REFACTOR_REWRITE, + ]) + } + async fn fetch_server_binary( + &self, + version: Box, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + let version = version.downcast::().unwrap(); + let server_path = container_dir.join(Self::SERVER_PATH); + let ts_path = container_dir.join(Self::TYPESCRIPT_PATH); + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages( + &container_dir, + &[("@vue/language-server", version.vue_version.as_str())], + ) + .await?; + } + assert!(fs::metadata(&server_path).await.is_ok()); + if fs::metadata(&ts_path).await.is_err() { + self.node + .npm_install_packages( + &container_dir, + &[("typescript", version.ts_version.as_str())], + ) + .await?; + } + + assert!(fs::metadata(&ts_path).await.is_ok()); + *self.typescript_install_path.lock() = Some(ts_path); + Ok(LanguageServerBinary { + path: self.node.binary_path().await?, + arguments: vue_server_binary_arguments(&server_path), + }) + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + let (server, ts_path) = get_cached_server_binary(container_dir, self.node.clone()).await?; + *self.typescript_install_path.lock() = Some(ts_path); + Some(server) + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + let (server, ts_path) = get_cached_server_binary(container_dir, self.node.clone()) + .await + .map(|(mut binary, ts_path)| { + binary.arguments = vec!["--help".into()]; + (binary, ts_path) + })?; + *self.typescript_install_path.lock() = Some(ts_path); + Some(server) + } + + async fn label_for_completion( + &self, + item: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + use lsp::CompletionItemKind as Kind; + let len = item.label.len(); + let grammar = language.grammar()?; + let highlight_id = match item.kind? { + Kind::CLASS | Kind::INTERFACE => grammar.highlight_id_for_name("type"), + Kind::CONSTRUCTOR => grammar.highlight_id_for_name("type"), + Kind::CONSTANT => grammar.highlight_id_for_name("constant"), + Kind::FUNCTION | Kind::METHOD => grammar.highlight_id_for_name("function"), + Kind::PROPERTY | Kind::FIELD => grammar.highlight_id_for_name("tag"), + Kind::VARIABLE => grammar.highlight_id_for_name("type"), + Kind::KEYWORD => grammar.highlight_id_for_name("keyword"), + Kind::VALUE => grammar.highlight_id_for_name("tag"), + _ => None, + }?; + + let text = match &item.detail { + Some(detail) => format!("{} {}", item.label, detail), + None => item.label.clone(), + }; + + Some(language::CodeLabel { + text, + runs: vec![(0..len, highlight_id)], + filter_range: 0..len, + }) + } +} + +fn vue_server_binary_arguments(server_path: &Path) -> Vec { + vec![server_path.into(), "--stdio".into()] +} + +type TypescriptPath = PathBuf; +async fn get_cached_server_binary( + container_dir: PathBuf, + node: Arc, +) -> Option<(LanguageServerBinary, TypescriptPath)> { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(VueLspAdapter::SERVER_PATH); + let typescript_path = last_version_dir.join(VueLspAdapter::TYPESCRIPT_PATH); + if server_path.exists() && typescript_path.exists() { + Ok(( + LanguageServerBinary { + path: node.binary_path().await?, + arguments: vue_server_binary_arguments(&server_path), + }, + typescript_path, + )) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} diff --git a/crates/zed/src/languages/vue/brackets.scm b/crates/zed/src/languages/vue/brackets.scm new file mode 100644 index 0000000000..2d12b17daa --- /dev/null +++ b/crates/zed/src/languages/vue/brackets.scm @@ -0,0 +1,2 @@ +("<" @open ">" @close) +("\"" @open "\"" @close) diff --git a/crates/zed/src/languages/vue/config.toml b/crates/zed/src/languages/vue/config.toml new file mode 100644 index 0000000000..c41a667b75 --- /dev/null +++ b/crates/zed/src/languages/vue/config.toml @@ -0,0 +1,14 @@ +name = "Vue.js" +path_suffixes = ["vue"] +block_comment = [""] +autoclose_before = ";:.,=}])>" +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = true, newline = true, not_in = ["string", "comment"] }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, + { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] }, + { start = "`", end = "`", close = true, newline = false, not_in = ["string"] }, +] +word_characters = ["-"] diff --git a/crates/zed/src/languages/vue/highlights.scm b/crates/zed/src/languages/vue/highlights.scm new file mode 100644 index 0000000000..1a80c84f68 --- /dev/null +++ b/crates/zed/src/languages/vue/highlights.scm @@ -0,0 +1,15 @@ +(attribute) @property +(directive_attribute) @property +(quoted_attribute_value) @string +(interpolation) @punctuation.special +(raw_text) @embedded + +((tag_name) @type + (#match? @type "^[A-Z]")) + +((directive_name) @keyword + (#match? @keyword "^v-")) + +(start_tag) @tag +(end_tag) @tag +(self_closing_tag) @tag diff --git a/crates/zed/src/languages/vue/injections.scm b/crates/zed/src/languages/vue/injections.scm new file mode 100644 index 0000000000..9084e373f2 --- /dev/null +++ b/crates/zed/src/languages/vue/injections.scm @@ -0,0 +1,7 @@ +(script_element + (raw_text) @content + (#set! "language" "javascript")) + +(style_element + (raw_text) @content + (#set! "language" "css")) From 6f4008ebabd33793cd70a8f042c2c9510680a89b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Sun, 15 Oct 2023 17:27:36 +0200 Subject: [PATCH 87/92] copilot: Propagate action if suggest_next is not possible. (#3129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One of our users ran into an issue where typing "true quote" characters (option-[ for „ and option-] for ‚) was not possible; I've narrowed it down to a collision with Copilot's NextSuggestion and PreviousSuggestion action default keybinds. I explicitly did not want to alter the key bindings, so I've went with a more neutral fix - one that propagates the keystroke if there's no Copilot action to be taken (user is not using Copilot etc). Note however that typing true quotes while using a Copilot is still not possible, as for that we'd have to change a keybind. Fixes zed-industries/community#2072 Release Notes: - Fixed Copilot's "Suggest next" and "Suggest previous" actions colliding with true quotes key bindings (`option-[` and `option-]`). The keystrokes are now propagated if there's no Copilot action to be taken at cursor's position. --- crates/editor/src/editor.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bb6d693d82..7aca4ab98f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4158,7 +4158,10 @@ impl Editor { if self.has_active_copilot_suggestion(cx) { self.cycle_copilot_suggestions(Direction::Next, cx); } else { - self.refresh_copilot_suggestions(false, cx); + let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none(); + if is_copilot_disabled { + cx.propagate_action(); + } } } @@ -4170,7 +4173,10 @@ impl Editor { if self.has_active_copilot_suggestion(cx) { self.cycle_copilot_suggestions(Direction::Prev, cx); } else { - self.refresh_copilot_suggestions(false, cx); + let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none(); + if is_copilot_disabled { + cx.propagate_action(); + } } } From cc335db9e0e7ce677591d544140760ed8c080eec Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:17:44 +0200 Subject: [PATCH 88/92] editor/language: hoist out non-generic parts of edit functions. (#3130) This reduces LLVM IR size of editor (that's one of the heaviest crates to build) by almost 5%. LLVM IR size of `editor` before this PR: 3280386 LLVM IR size with `editor::edit` changed: 3227092 LLVM IR size with `editor::edit` and `language::edit` changed: 3146807 Release Notes: - N/A --- crates/editor/src/multi_buffer.rs | 140 +++++++++++++++------------- crates/language/src/buffer.rs | 149 ++++++++++++++++-------------- 2 files changed, 158 insertions(+), 131 deletions(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index c5d17dfd2e..23a117405c 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -498,77 +498,91 @@ impl MultiBuffer { } } - for (buffer_id, mut edits) in buffer_edits { - edits.sort_unstable_by_key(|edit| edit.range.start); - self.buffers.borrow()[&buffer_id] - .buffer - .update(cx, |buffer, cx| { - let mut edits = edits.into_iter().peekable(); - let mut insertions = Vec::new(); - let mut original_indent_columns = Vec::new(); - let mut deletions = Vec::new(); - let empty_str: Arc = "".into(); - while let Some(BufferEdit { - mut range, - new_text, - mut is_insertion, - original_indent_column, - }) = edits.next() - { + drop(cursor); + drop(snapshot); + // Non-generic part of edit, hoisted out to avoid blowing up LLVM IR. + fn tail( + this: &mut MultiBuffer, + buffer_edits: HashMap>, + autoindent_mode: Option, + edited_excerpt_ids: Vec, + cx: &mut ModelContext, + ) { + for (buffer_id, mut edits) in buffer_edits { + edits.sort_unstable_by_key(|edit| edit.range.start); + this.buffers.borrow()[&buffer_id] + .buffer + .update(cx, |buffer, cx| { + let mut edits = edits.into_iter().peekable(); + let mut insertions = Vec::new(); + let mut original_indent_columns = Vec::new(); + let mut deletions = Vec::new(); + let empty_str: Arc = "".into(); while let Some(BufferEdit { - range: next_range, - is_insertion: next_is_insertion, - .. - }) = edits.peek() + mut range, + new_text, + mut is_insertion, + original_indent_column, + }) = edits.next() { - if range.end >= next_range.start { - range.end = cmp::max(next_range.end, range.end); - is_insertion |= *next_is_insertion; - edits.next(); - } else { - break; + while let Some(BufferEdit { + range: next_range, + is_insertion: next_is_insertion, + .. + }) = edits.peek() + { + if range.end >= next_range.start { + range.end = cmp::max(next_range.end, range.end); + is_insertion |= *next_is_insertion; + edits.next(); + } else { + break; + } + } + + if is_insertion { + original_indent_columns.push(original_indent_column); + insertions.push(( + buffer.anchor_before(range.start) + ..buffer.anchor_before(range.end), + new_text.clone(), + )); + } else if !range.is_empty() { + deletions.push(( + buffer.anchor_before(range.start) + ..buffer.anchor_before(range.end), + empty_str.clone(), + )); } } - if is_insertion { - original_indent_columns.push(original_indent_column); - insertions.push(( - buffer.anchor_before(range.start)..buffer.anchor_before(range.end), - new_text.clone(), - )); - } else if !range.is_empty() { - deletions.push(( - buffer.anchor_before(range.start)..buffer.anchor_before(range.end), - empty_str.clone(), - )); - } - } + let deletion_autoindent_mode = + if let Some(AutoindentMode::Block { .. }) = autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns: Default::default(), + }) + } else { + None + }; + let insertion_autoindent_mode = + if let Some(AutoindentMode::Block { .. }) = autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns, + }) + } else { + None + }; - let deletion_autoindent_mode = - if let Some(AutoindentMode::Block { .. }) = autoindent_mode { - Some(AutoindentMode::Block { - original_indent_columns: Default::default(), - }) - } else { - None - }; - let insertion_autoindent_mode = - if let Some(AutoindentMode::Block { .. }) = autoindent_mode { - Some(AutoindentMode::Block { - original_indent_columns, - }) - } else { - None - }; + buffer.edit(deletions, deletion_autoindent_mode, cx); + buffer.edit(insertions, insertion_autoindent_mode, cx); + }) + } - buffer.edit(deletions, deletion_autoindent_mode, cx); - buffer.edit(insertions, insertion_autoindent_mode, cx); - }) + cx.emit(Event::ExcerptsEdited { + ids: edited_excerpt_ids, + }); } - - cx.emit(Event::ExcerptsEdited { - ids: edited_excerpt_ids, - }); + tail(self, buffer_edits, autoindent_mode, edited_excerpt_ids, cx); } pub fn start_transaction(&mut self, cx: &mut ModelContext) -> Option { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d8ebc1d445..78562ba8c4 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1448,82 +1448,95 @@ impl Buffer { return None; } - self.start_transaction(); - self.pending_autoindent.take(); - let autoindent_request = autoindent_mode - .and_then(|mode| self.language.as_ref().map(|_| (self.snapshot(), mode))); + // Non-generic part hoisted out to reduce LLVM IR size. + fn tail( + this: &mut Buffer, + edits: Vec<(Range, Arc)>, + autoindent_mode: Option, + cx: &mut ModelContext, + ) -> Option { + this.start_transaction(); + this.pending_autoindent.take(); + let autoindent_request = autoindent_mode + .and_then(|mode| this.language.as_ref().map(|_| (this.snapshot(), mode))); - let edit_operation = self.text.edit(edits.iter().cloned()); - let edit_id = edit_operation.timestamp(); + let edit_operation = this.text.edit(edits.iter().cloned()); + let edit_id = edit_operation.timestamp(); - if let Some((before_edit, mode)) = autoindent_request { - let mut delta = 0isize; - let entries = edits - .into_iter() - .enumerate() - .zip(&edit_operation.as_edit().unwrap().new_text) - .map(|((ix, (range, _)), new_text)| { - let new_text_length = new_text.len(); - let old_start = range.start.to_point(&before_edit); - let new_start = (delta + range.start as isize) as usize; - delta += new_text_length as isize - (range.end as isize - range.start as isize); + if let Some((before_edit, mode)) = autoindent_request { + let mut delta = 0isize; + let entries = edits + .into_iter() + .enumerate() + .zip(&edit_operation.as_edit().unwrap().new_text) + .map(|((ix, (range, _)), new_text)| { + let new_text_length = new_text.len(); + let old_start = range.start.to_point(&before_edit); + let new_start = (delta + range.start as isize) as usize; + delta += + new_text_length as isize - (range.end as isize - range.start as isize); - let mut range_of_insertion_to_indent = 0..new_text_length; - let mut first_line_is_new = false; - let mut original_indent_column = None; + let mut range_of_insertion_to_indent = 0..new_text_length; + let mut first_line_is_new = false; + let mut original_indent_column = None; - // When inserting an entire line at the beginning of an existing line, - // treat the insertion as new. - if new_text.contains('\n') - && old_start.column <= before_edit.indent_size_for_line(old_start.row).len - { - first_line_is_new = true; - } - - // When inserting text starting with a newline, avoid auto-indenting the - // previous line. - if new_text.starts_with('\n') { - range_of_insertion_to_indent.start += 1; - first_line_is_new = true; - } - - // Avoid auto-indenting after the insertion. - if let AutoindentMode::Block { - original_indent_columns, - } = &mode - { - original_indent_column = - Some(original_indent_columns.get(ix).copied().unwrap_or_else(|| { - indent_size_for_text( - new_text[range_of_insertion_to_indent.clone()].chars(), - ) - .len - })); - if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') { - range_of_insertion_to_indent.end -= 1; + // When inserting an entire line at the beginning of an existing line, + // treat the insertion as new. + if new_text.contains('\n') + && old_start.column + <= before_edit.indent_size_for_line(old_start.row).len + { + first_line_is_new = true; } - } - AutoindentRequestEntry { - first_line_is_new, - original_indent_column, - indent_size: before_edit.language_indent_size_at(range.start, cx), - range: self.anchor_before(new_start + range_of_insertion_to_indent.start) - ..self.anchor_after(new_start + range_of_insertion_to_indent.end), - } - }) - .collect(); + // When inserting text starting with a newline, avoid auto-indenting the + // previous line. + if new_text.starts_with('\n') { + range_of_insertion_to_indent.start += 1; + first_line_is_new = true; + } - self.autoindent_requests.push(Arc::new(AutoindentRequest { - before_edit, - entries, - is_block_mode: matches!(mode, AutoindentMode::Block { .. }), - })); + // Avoid auto-indenting after the insertion. + if let AutoindentMode::Block { + original_indent_columns, + } = &mode + { + original_indent_column = Some( + original_indent_columns.get(ix).copied().unwrap_or_else(|| { + indent_size_for_text( + new_text[range_of_insertion_to_indent.clone()].chars(), + ) + .len + }), + ); + if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') { + range_of_insertion_to_indent.end -= 1; + } + } + + AutoindentRequestEntry { + first_line_is_new, + original_indent_column, + indent_size: before_edit.language_indent_size_at(range.start, cx), + range: this + .anchor_before(new_start + range_of_insertion_to_indent.start) + ..this.anchor_after(new_start + range_of_insertion_to_indent.end), + } + }) + .collect(); + + this.autoindent_requests.push(Arc::new(AutoindentRequest { + before_edit, + entries, + is_block_mode: matches!(mode, AutoindentMode::Block { .. }), + })); + } + + this.end_transaction(cx); + this.send_operation(Operation::Buffer(edit_operation), cx); + Some(edit_id) } - - self.end_transaction(cx); - self.send_operation(Operation::Buffer(edit_operation), cx); - Some(edit_id) + tail(self, edits, autoindent_mode, cx) } fn did_edit( From 75fbf2ca78a35f80fd0b0b263f8c856d5f173b00 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 16 Oct 2023 12:45:01 -0400 Subject: [PATCH 89/92] 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 70878bf2e4..fd93aaeec8 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 c12f0d26978420479c47c6e2e35463323aa88c75 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Oct 2023 15:33:59 -0600 Subject: [PATCH 90/92] 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 dc5022bea5..4775e15837 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 91/92] 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 a48d425d90..aee67ec943 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 1a17b48f19..a8c4006cb8 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 92/92] 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 faed37a97c..383ca94851 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,