Compare commits

...
Sign in to create a new pull request.

9 commits

Author SHA1 Message Date
Joseph T. Lyons
a0de7d52c8 v0.112.x stable 2023-11-15 12:47:19 -05:00
Joseph T. Lyons
86eafb6b6c zed 0.112.3 2023-11-10 15:56:49 -05:00
Max Brunsfeld
2d3a34c864 Refine search query suggestions (#3293)
This PR fixes some issues in response to feedback from Dan Abramov and
Jose Valim.

To do:

* [x] fix non-word search suggestions
* [x] add setting for disabling search suggestions

Release Notes:

- Fixed an issue where opening a search without text selected would
populate the search query with non-word characters adjacent to the
cursor.
- Added a setting, `seed_search_query_from_cursor`, which controls
whether the search query is automatically populated from the buffer when
starting a new buffer search or project search.

By default, the search query will always be set to the word under the
cursor. If you want to only populate the search query when text is
selected, you can add the following to your `~/.zed/settings.json`:

  ```json
  {
    "seed_search_query_from_cursor": "selection"
  }
  ```

If you don't want the search query to be automatically populated, even
when there is text selected, add the following:

  ```json
  {
    "seed_search_query_from_cursor": "never"
  }
  ```
2023-11-09 15:33:15 -08:00
Joseph T. Lyons
67068faa1d zed 0.112.2 2023-11-09 14:18:47 -05:00
Max Brunsfeld
1d175b4848 Use normal JS comments within JSX tags and JSX expression blocks (#3290)
This fix only required changing the `overrides` queries for JavaScript
and TSX. I've made the fix in both the `zed2` and `zed` crates.

Release Notes:

- Fixed an issue in JavaScript and TSX files, where the 'toggle
comments' command used the wrong comment syntax inside of JSX tags and
expressions within JSX.
2023-11-09 14:17:59 -05:00
Kirill Bulatov
c51d54d86d Do not use prettier for formatting node_modules/** files (#3286)
Fixes

> the most annoying thing i'm running into right now is that when i'm
patching something inside node_modules, Zed tries to pretty-format it
according to my prettier config. this messes up the patch because it has
formatting changes now. i need the pretty formatting on save to be off
inside node_modules, that never makes sense

feedback from #influencers 

Do note though, that language servers will still format any file inside
node_modules, but at least it's not prettier now.
VSCode seem to format the node_modules/** files via language servers
too, so that seems ok for now, and the rest could be fixed during

> "project diagnostics" (eslint) seem to be running inside node_modules,
e.g. i'm seeing 3182 "errors" in my project. that doesn't make sense and
probably wastes resources in addition to being annoying

feedback later.

Release Notes:

- Fixed prettier formatting files inside node_modules
2023-11-09 11:19:24 -05:00
Joseph T. Lyons
dc55644e7d zed 0.112.1 2023-11-09 10:20:51 -05:00
Julia
af9af3ff74 Get tsserver running again 2023-11-09 10:15:29 -05:00
Joseph T. Lyons
8539d97f43 v0.112.x preview 2023-11-08 10:53:12 -05:00
25 changed files with 492 additions and 208 deletions

2
Cargo.lock generated
View file

@ -11078,7 +11078,7 @@ dependencies = [
[[package]] [[package]]
name = "zed" name = "zed"
version = "0.112.0" version = "0.112.3"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"ai", "ai",

View file

@ -102,6 +102,16 @@
"selections": true "selections": true
}, },
"relative_line_numbers": false, "relative_line_numbers": false,
// When to populate a new search's query based on the text under the cursor.
// This setting can take the following three values:
//
// 1. Always populate the search query with the word under the cursor (default).
// "always"
// 2. Only populate the search query when there is text selected
// "selection"
// 3. Never populate the search query
// "never"
"seed_search_query_from_cursor": "always",
// Inlay hint related settings // Inlay hint related settings
"inlay_hints": { "inlay_hints": {
// Global switch to toggle hints on and off, switched off by default. // Global switch to toggle hints on and off, switched off by default.

View file

@ -2,7 +2,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Setting; use settings::Setting;
#[derive(Deserialize)] #[derive(Clone, Deserialize)]
pub struct EditorSettings { pub struct EditorSettings {
pub cursor_blink: bool, pub cursor_blink: bool,
pub hover_popover_enabled: bool, pub hover_popover_enabled: bool,
@ -11,6 +11,15 @@ pub struct EditorSettings {
pub use_on_type_format: bool, pub use_on_type_format: bool,
pub scrollbar: Scrollbar, pub scrollbar: Scrollbar,
pub relative_line_numbers: bool, pub relative_line_numbers: bool,
pub seed_search_query_from_cursor: SeedQuerySetting,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SeedQuerySetting {
Always,
Selection,
Never,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@ -38,6 +47,7 @@ pub struct EditorSettingsContent {
pub use_on_type_format: Option<bool>, pub use_on_type_format: Option<bool>,
pub scrollbar: Option<ScrollbarContent>, pub scrollbar: Option<ScrollbarContent>,
pub relative_line_numbers: Option<bool>, pub relative_line_numbers: Option<bool>,
pub seed_search_query_from_cursor: Option<SeedQuerySetting>,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition,
movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, EditorSettings, Event,
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
}; };
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use collections::HashSet; use collections::HashSet;
@ -13,8 +13,8 @@ use gpui::{
ViewHandle, WeakViewHandle, ViewHandle, WeakViewHandle,
}; };
use language::{ use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
SelectionGoal, Point, SelectionGoal,
}; };
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath}; use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view, PeerId}; use rpc::proto::{self, update_view, PeerId};
@ -937,24 +937,28 @@ impl SearchableItem for Editor {
} }
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String { fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
let display_map = self.snapshot(cx).display_snapshot; let setting = settings::get::<EditorSettings>(cx).seed_search_query_from_cursor;
let snapshot = &self.snapshot(cx).buffer_snapshot;
let selection = self.selections.newest::<usize>(cx); let selection = self.selections.newest::<usize>(cx);
if selection.start == selection.end {
let point = selection.start.to_display_point(&display_map); match setting {
let range = surrounding_word(&display_map, point); SeedQuerySetting::Never => String::new(),
let range = range.start.to_offset(&display_map, Bias::Left) SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
..range.end.to_offset(&display_map, Bias::Right); snapshot
let text: String = display_map.buffer_snapshot.text_for_range(range).collect(); .text_for_range(selection.start..selection.end)
if text.trim().is_empty() { .collect()
String::new() }
} else { SeedQuerySetting::Selection => String::new(),
text SeedQuerySetting::Always => {
let (range, kind) = snapshot.surrounding_word(selection.start);
if kind == Some(CharKind::Word) {
let text: String = snapshot.text_for_range(range).collect();
if !text.trim().is_empty() {
return text;
}
}
String::new()
} }
} else {
display_map
.buffer_snapshot
.text_for_range(selection.start..selection.end)
.collect()
} }
} }

View file

@ -11,6 +11,15 @@ pub struct EditorSettings {
pub use_on_type_format: bool, pub use_on_type_format: bool,
pub scrollbar: Scrollbar, pub scrollbar: Scrollbar,
pub relative_line_numbers: bool, pub relative_line_numbers: bool,
pub seed_search_query_from_cursor: SeedQuerySetting,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SeedQuerySetting {
Always,
Selection,
Never,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@ -38,6 +47,7 @@ pub struct EditorSettingsContent {
pub use_on_type_format: Option<bool>, pub use_on_type_format: Option<bool>,
pub scrollbar: Option<ScrollbarContent>, pub scrollbar: Option<ScrollbarContent>,
pub relative_line_numbers: Option<bool>, pub relative_line_numbers: Option<bool>,
pub seed_search_query_from_selection: Option<SeedQuerySetting>,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]

View file

@ -1,7 +1,8 @@
use crate::{ use crate::{
display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition,
movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, EditorSettings, Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot,
NavigationData, ToPoint as _,
}; };
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use collections::HashSet; use collections::HashSet;
@ -12,11 +13,12 @@ use gpui::{
VisualContext, WeakView, VisualContext, WeakView,
}; };
use language::{ use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
SelectionGoal, Point, SelectionGoal,
}; };
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath}; use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view, PeerId}; use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
borrow::Cow, borrow::Cow,
@ -950,24 +952,28 @@ impl SearchableItem for Editor {
} }
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String { fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
let display_map = self.snapshot(cx).display_snapshot; let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
let snapshot = &self.snapshot(cx).buffer_snapshot;
let selection = self.selections.newest::<usize>(cx); let selection = self.selections.newest::<usize>(cx);
if selection.start == selection.end {
let point = selection.start.to_display_point(&display_map); match setting {
let range = surrounding_word(&display_map, point); SeedQuerySetting::Never => String::new(),
let range = range.start.to_offset(&display_map, Bias::Left) SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
..range.end.to_offset(&display_map, Bias::Right); snapshot
let text: String = display_map.buffer_snapshot.text_for_range(range).collect(); .text_for_range(selection.start..selection.end)
if text.trim().is_empty() { .collect()
String::new() }
} else { SeedQuerySetting::Selection => String::new(),
text SeedQuerySetting::Always => {
let (range, kind) = snapshot.surrounding_word(selection.start);
if kind == Some(CharKind::Word) {
let text: String = snapshot.text_for_range(range).collect();
if !text.trim().is_empty() {
return text;
}
}
String::new()
} }
} else {
display_map
.buffer_snapshot
.text_for_range(selection.start..selection.end)
.collect()
} }
} }

View file

@ -136,7 +136,7 @@ impl ToJson for RectF {
} }
#[derive(Refineable, Debug)] #[derive(Refineable, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct Point<T: Clone + Default + Debug> { pub struct Point<T: Clone + Default + Debug> {
pub x: T, pub x: T,
pub y: T, pub y: T,
@ -161,7 +161,7 @@ impl<T: Clone + Default + Debug> Into<taffy::geometry::Point<T>> for Point<T> {
} }
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct Size<T: Clone + Default + Debug> { pub struct Size<T: Clone + Default + Debug> {
pub width: T, pub width: T,
pub height: T, pub height: T,
@ -227,7 +227,7 @@ impl Size<Length> {
} }
#[derive(Clone, Default, Refineable, Debug)] #[derive(Clone, Default, Refineable, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct Edges<T: Clone + Default + Debug> { pub struct Edges<T: Clone + Default + Debug> {
pub top: T, pub top: T,
pub right: T, pub right: T,

View file

@ -9,7 +9,7 @@ use std::{
}; };
#[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)]
#[refineable(debug)] #[refineable(Debug)]
#[repr(C)] #[repr(C)]
pub struct Point<T: Default + Clone + Debug> { pub struct Point<T: Default + Clone + Debug> {
pub x: T, pub x: T,
@ -140,7 +140,7 @@ impl<T: Clone + Default + Debug> Clone for Point<T> {
} }
#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)] #[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)]
#[refineable(debug)] #[refineable(Debug)]
#[repr(C)] #[repr(C)]
pub struct Size<T: Clone + Default + Debug> { pub struct Size<T: Clone + Default + Debug> {
pub width: T, pub width: T,
@ -295,7 +295,7 @@ impl Size<Length> {
} }
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(debug)] #[refineable(Debug)]
#[repr(C)] #[repr(C)]
pub struct Bounds<T: Clone + Default + Debug> { pub struct Bounds<T: Clone + Default + Debug> {
pub origin: Point<T>, pub origin: Point<T>,
@ -459,7 +459,7 @@ impl Bounds<Pixels> {
impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {} impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {}
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(debug)] #[refineable(Debug)]
#[repr(C)] #[repr(C)]
pub struct Edges<T: Clone + Default + Debug> { pub struct Edges<T: Clone + Default + Debug> {
pub top: T, pub top: T,
@ -592,7 +592,7 @@ impl Edges<Pixels> {
} }
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(debug)] #[refineable(Debug)]
#[repr(C)] #[repr(C)]
pub struct Corners<T: Clone + Default + Debug> { pub struct Corners<T: Clone + Default + Debug> {
pub top_left: T, pub top_left: T,

View file

@ -14,7 +14,7 @@ pub use taffy::style::{
pub type StyleCascade = Cascade<Style>; pub type StyleCascade = Cascade<Style>;
#[derive(Clone, Refineable, Debug)] #[derive(Clone, Refineable, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct Style { pub struct Style {
/// What layout strategy should be used? /// What layout strategy should be used?
pub display: Display, pub display: Display,
@ -129,7 +129,7 @@ pub struct BoxShadow {
} }
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct TextStyle { pub struct TextStyle {
pub color: Hsla, pub color: Hsla,
pub font_family: SharedString, pub font_family: SharedString,
@ -353,7 +353,7 @@ impl Default for Style {
} }
#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq)] #[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq)]
#[refineable(debug)] #[refineable(Debug)]
pub struct UnderlineStyle { pub struct UnderlineStyle {
pub thickness: Pixels, pub thickness: Pixels,
pub color: Option<Hsla>, pub color: Option<Hsla>,

View file

@ -1692,14 +1692,25 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
r#" r#"
(jsx_element) @element (jsx_element) @element
(string) @string (string) @string
[
(jsx_opening_element)
(jsx_closing_element)
(jsx_expression)
] @default
"#, "#,
) )
.unwrap(); .unwrap();
let text = r#"a["b"] = <C d="e"></C>;"#; let text = r#"
a["b"] = <C d="e">
<F></F>
{ g() }
</C>;
"#
.unindent();
let buffer = let buffer =
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(language), cx); Buffer::new(0, cx.model_id() as u64, &text).with_language(Arc::new(language), cx);
let snapshot = buffer.snapshot(); let snapshot = buffer.snapshot();
let config = snapshot.language_scope_at(0).unwrap(); let config = snapshot.language_scope_at(0).unwrap();
@ -1710,7 +1721,9 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
&[true, true] &[true, true]
); );
let string_config = snapshot.language_scope_at(3).unwrap(); let string_config = snapshot
.language_scope_at(text.find("b\"").unwrap())
.unwrap();
assert_eq!(string_config.line_comment_prefix().unwrap().as_ref(), "// "); assert_eq!(string_config.line_comment_prefix().unwrap().as_ref(), "// ");
// Second bracket pair is disabled // Second bracket pair is disabled
assert_eq!( assert_eq!(
@ -1718,18 +1731,49 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
&[true, false] &[true, false]
); );
let element_config = snapshot.language_scope_at(10).unwrap(); // In between JSX tags: use the `element` override.
let element_config = snapshot
.language_scope_at(text.find("<F>").unwrap())
.unwrap();
assert_eq!(element_config.line_comment_prefix(), None); assert_eq!(element_config.line_comment_prefix(), None);
assert_eq!( assert_eq!(
element_config.block_comment_delimiters(), element_config.block_comment_delimiters(),
Some((&"{/*".into(), &"*/}".into())) Some((&"{/*".into(), &"*/}".into()))
); );
// Both bracket pairs are enabled
assert_eq!( assert_eq!(
element_config.brackets().map(|e| e.1).collect::<Vec<_>>(), element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
&[true, true] &[true, true]
); );
// Within a JSX tag: use the default config.
let tag_config = snapshot
.language_scope_at(text.find(" d=").unwrap() + 1)
.unwrap();
assert_eq!(tag_config.line_comment_prefix().unwrap().as_ref(), "// ");
assert_eq!(
tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
&[true, true]
);
// In a JSX expression: use the default config.
let expression_in_element_config = snapshot
.language_scope_at(text.find("{").unwrap() + 1)
.unwrap();
assert_eq!(
expression_in_element_config
.line_comment_prefix()
.unwrap()
.as_ref(),
"// "
);
assert_eq!(
expression_in_element_config
.brackets()
.map(|e| e.1)
.collect::<Vec<_>>(),
&[true, true]
);
buffer buffer
}); });
} }

View file

@ -1696,14 +1696,25 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
r#" r#"
(jsx_element) @element (jsx_element) @element
(string) @string (string) @string
[
(jsx_opening_element)
(jsx_closing_element)
(jsx_expression)
] @default
"#, "#,
) )
.unwrap(); .unwrap();
let text = r#"a["b"] = <C d="e"></C>;"#; let text = r#"
a["b"] = <C d="e">
<F></F>
{ g() }
</C>;
"#
.unindent();
let buffer = let buffer =
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(language), cx); Buffer::new(0, cx.entity_id().as_u64(), &text).with_language(Arc::new(language), cx);
let snapshot = buffer.snapshot(); let snapshot = buffer.snapshot();
let config = snapshot.language_scope_at(0).unwrap(); let config = snapshot.language_scope_at(0).unwrap();
@ -1714,7 +1725,9 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
&[true, true] &[true, true]
); );
let string_config = snapshot.language_scope_at(3).unwrap(); let string_config = snapshot
.language_scope_at(text.find("b\"").unwrap())
.unwrap();
assert_eq!(string_config.line_comment_prefix().unwrap().as_ref(), "// "); assert_eq!(string_config.line_comment_prefix().unwrap().as_ref(), "// ");
// Second bracket pair is disabled // Second bracket pair is disabled
assert_eq!( assert_eq!(
@ -1722,18 +1735,49 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
&[true, false] &[true, false]
); );
let element_config = snapshot.language_scope_at(10).unwrap(); // In between JSX tags: use the `element` override.
let element_config = snapshot
.language_scope_at(text.find("<F>").unwrap())
.unwrap();
assert_eq!(element_config.line_comment_prefix(), None); assert_eq!(element_config.line_comment_prefix(), None);
assert_eq!( assert_eq!(
element_config.block_comment_delimiters(), element_config.block_comment_delimiters(),
Some((&"{/*".into(), &"*/}".into())) Some((&"{/*".into(), &"*/}".into()))
); );
// Both bracket pairs are enabled
assert_eq!( assert_eq!(
element_config.brackets().map(|e| e.1).collect::<Vec<_>>(), element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
&[true, true] &[true, true]
); );
// Within a JSX tag: use the default config.
let tag_config = snapshot
.language_scope_at(text.find(" d=").unwrap() + 1)
.unwrap();
assert_eq!(tag_config.line_comment_prefix().unwrap().as_ref(), "// ");
assert_eq!(
tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
&[true, true]
);
// In a JSX expression: use the default config.
let expression_in_element_config = snapshot
.language_scope_at(text.find("{").unwrap() + 1)
.unwrap();
assert_eq!(
expression_in_element_config
.line_comment_prefix()
.unwrap()
.as_ref(),
"// "
);
assert_eq!(
expression_in_element_config
.brackets()
.map(|e| e.1)
.collect::<Vec<_>>(),
&[true, true]
);
buffer buffer
}); });
} }

View file

@ -1,3 +1,4 @@
use std::ops::ControlFlow;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
@ -58,11 +59,17 @@ impl Prettier {
fs: &dyn Fs, fs: &dyn Fs,
installed_prettiers: &HashSet<PathBuf>, installed_prettiers: &HashSet<PathBuf>,
locate_from: &Path, locate_from: &Path,
) -> anyhow::Result<Option<PathBuf>> { ) -> anyhow::Result<ControlFlow<(), Option<PathBuf>>> {
let mut path_to_check = locate_from let mut path_to_check = locate_from
.components() .components()
.take_while(|component| component.as_os_str().to_string_lossy() != "node_modules") .take_while(|component| component.as_os_str().to_string_lossy() != "node_modules")
.collect::<PathBuf>(); .collect::<PathBuf>();
if path_to_check != locate_from {
log::debug!(
"Skipping prettier location for path {path_to_check:?} that is inside node_modules"
);
return Ok(ControlFlow::Break(()));
}
let path_to_check_metadata = fs let path_to_check_metadata = fs
.metadata(&path_to_check) .metadata(&path_to_check)
.await .await
@ -76,14 +83,14 @@ impl Prettier {
loop { loop {
if installed_prettiers.contains(&path_to_check) { if installed_prettiers.contains(&path_to_check) {
log::debug!("Found prettier path {path_to_check:?} in installed prettiers"); log::debug!("Found prettier path {path_to_check:?} in installed prettiers");
return Ok(Some(path_to_check)); return Ok(ControlFlow::Continue(Some(path_to_check)));
} else if let Some(package_json_contents) = } else if let Some(package_json_contents) =
read_package_json(fs, &path_to_check).await? read_package_json(fs, &path_to_check).await?
{ {
if has_prettier_in_package_json(&package_json_contents) { if has_prettier_in_package_json(&package_json_contents) {
if has_prettier_in_node_modules(fs, &path_to_check).await? { if has_prettier_in_node_modules(fs, &path_to_check).await? {
log::debug!("Found prettier path {path_to_check:?} in both package.json and node_modules"); log::debug!("Found prettier path {path_to_check:?} in both package.json and node_modules");
return Ok(Some(path_to_check)); return Ok(ControlFlow::Continue(Some(path_to_check)));
} else if project_path_with_prettier_dependency.is_none() { } else if project_path_with_prettier_dependency.is_none() {
project_path_with_prettier_dependency = Some(path_to_check.clone()); project_path_with_prettier_dependency = Some(path_to_check.clone());
} }
@ -109,7 +116,7 @@ impl Prettier {
}) { }) {
anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules"); anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules");
log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}"); log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}");
return Ok(Some(path_to_check)); return Ok(ControlFlow::Continue(Some(path_to_check)));
} else { } else {
log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}"); log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}");
} }
@ -132,7 +139,7 @@ impl Prettier {
} }
None => { None => {
log::debug!("Found no prettier in ancestors of {locate_from:?}"); log::debug!("Found no prettier in ancestors of {locate_from:?}");
return Ok(None); return Ok(ControlFlow::Continue(None));
} }
} }
} }
@ -497,37 +504,40 @@ mod tests {
.await; .await;
assert!( assert!(
Prettier::locate_prettier_installation( matches!(
fs.as_ref(), Prettier::locate_prettier_installation(
&HashSet::default(), fs.as_ref(),
Path::new("/root/.config/zed/settings.json"), &HashSet::default(),
) Path::new("/root/.config/zed/settings.json"),
.await )
.unwrap() .await,
.is_none(), Ok(ControlFlow::Continue(None))
),
"Should successfully find no prettier for path hierarchy without it" "Should successfully find no prettier for path hierarchy without it"
); );
assert!( assert!(
Prettier::locate_prettier_installation( matches!(
fs.as_ref(), Prettier::locate_prettier_installation(
&HashSet::default(), fs.as_ref(),
Path::new("/root/work/project/src/index.js") &HashSet::default(),
) Path::new("/root/work/project/src/index.js")
.await )
.unwrap() .await,
.is_none(), Ok(ControlFlow::Continue(None))
),
"Should successfully find no prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it" "Should successfully find no prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it"
); );
assert!( assert!(
Prettier::locate_prettier_installation( matches!(
fs.as_ref(), Prettier::locate_prettier_installation(
&HashSet::default(), fs.as_ref(),
Path::new("/root/work/project/node_modules/expect/build/print.js") &HashSet::default(),
) Path::new("/root/work/project/node_modules/expect/build/print.js")
.await )
.unwrap() .await,
.is_none(), Ok(ControlFlow::Break(()))
"Even though it has package.json with prettier in it and no prettier on node_modules along the path, nothing should fail since declared inside node_modules" ),
"Should not format files inside node_modules/"
); );
} }
@ -580,7 +590,7 @@ mod tests {
) )
.await .await
.unwrap(), .unwrap(),
Some(PathBuf::from("/root/web_blog")), ControlFlow::Continue(Some(PathBuf::from("/root/web_blog"))),
"Should find a preinstalled prettier in the project root" "Should find a preinstalled prettier in the project root"
); );
assert_eq!( assert_eq!(
@ -591,8 +601,8 @@ mod tests {
) )
.await .await
.unwrap(), .unwrap(),
Some(PathBuf::from("/root/web_blog")), ControlFlow::Break(()),
"Should find a preinstalled prettier in the project root even for node_modules files" "Should not allow formatting node_modules/ contents"
); );
} }
@ -604,6 +614,18 @@ mod tests {
json!({ json!({
"work": { "work": {
"web_blog": { "web_blog": {
"node_modules": {
"expect": {
"build": {
"print.js": "// print.js file contents",
},
"package.json": r#"{
"devDependencies": {
"prettier": "2.5.1"
}
}"#,
},
},
"pages": { "pages": {
"[slug].tsx": "// [slug].tsx file contents", "[slug].tsx": "// [slug].tsx file contents",
}, },
@ -624,33 +646,55 @@ mod tests {
) )
.await; .await;
let path = "/root/work/web_blog/node_modules/pages/[slug].tsx";
match Prettier::locate_prettier_installation( match Prettier::locate_prettier_installation(
fs.as_ref(), fs.as_ref(),
&HashSet::default(), &HashSet::default(),
Path::new(path) Path::new("/root/work/web_blog/pages/[slug].tsx")
) )
.await { .await {
Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"), Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"),
Err(e) => { Err(e) => {
let message = e.to_string(); let message = e.to_string();
assert!(message.contains(path), "Error message should mention which start file was used for location"); assert!(message.contains("/root/work/web_blog"), "Error message should mention which project had prettier defined");
assert!(message.contains("/root/work/web_blog"), "Error message should mention potential candidates without prettier node_modules contents");
}, },
}; };
assert_eq!( assert_eq!(
Prettier::locate_prettier_installation( Prettier::locate_prettier_installation(
fs.as_ref(), fs.as_ref(),
&HashSet::from_iter( &HashSet::from_iter(
[PathBuf::from("/root"), PathBuf::from("/root/work")].into_iter() [PathBuf::from("/root"), PathBuf::from("/root/work")].into_iter()
), ),
Path::new("/root/work/web_blog/node_modules/pages/[slug].tsx") Path::new("/root/work/web_blog/pages/[slug].tsx")
) )
.await .await
.unwrap(), .unwrap(),
Some(PathBuf::from("/root/work")), ControlFlow::Continue(Some(PathBuf::from("/root/work"))),
"Should return first cached value found without path checks" "Should return closest cached value found without path checks"
);
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/web_blog/node_modules/expect/build/print.js")
)
.await
.unwrap(),
ControlFlow::Break(()),
"Should not allow formatting files inside node_modules/"
);
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::from_iter(
[PathBuf::from("/root"), PathBuf::from("/root/work")].into_iter()
),
Path::new("/root/work/web_blog/node_modules/expect/build/print.js")
)
.await
.unwrap(),
ControlFlow::Break(()),
"Should ignore cache lookup for files inside node_modules/"
); );
} }
@ -674,7 +718,9 @@ mod tests {
}, },
}, },
}, },
"node_modules": {}, "node_modules": {
"test.js": "// test.js contents",
},
"package.json": r#"{ "package.json": r#"{
"devDependencies": { "devDependencies": {
"prettier": "^3.0.3" "prettier": "^3.0.3"
@ -703,9 +749,32 @@ mod tests {
&HashSet::default(), &HashSet::default(),
Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx"), Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx"),
).await.unwrap(), ).await.unwrap(),
Some(PathBuf::from("/root/work/full-stack-foundations")), ControlFlow::Continue(Some(PathBuf::from("/root/work/full-stack-foundations"))),
"Should ascend to the multi-workspace root and find the prettier there", "Should ascend to the multi-workspace root and find the prettier there",
); );
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/full-stack-foundations/node_modules/prettier/index.js")
)
.await
.unwrap(),
ControlFlow::Break(()),
"Should not allow formatting files inside root node_modules/"
);
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/node_modules/test.js")
)
.await
.unwrap(),
ControlFlow::Break(()),
"Should not allow formatting files inside submodule's node_modules/"
);
} }
#[gpui::test] #[gpui::test]

View file

@ -7,6 +7,7 @@ use lsp::{LanguageServer, LanguageServerId};
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
ops::ControlFlow,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
@ -58,11 +59,17 @@ impl Prettier {
fs: &dyn Fs, fs: &dyn Fs,
installed_prettiers: &HashSet<PathBuf>, installed_prettiers: &HashSet<PathBuf>,
locate_from: &Path, locate_from: &Path,
) -> anyhow::Result<Option<PathBuf>> { ) -> anyhow::Result<ControlFlow<(), Option<PathBuf>>> {
let mut path_to_check = locate_from let mut path_to_check = locate_from
.components() .components()
.take_while(|component| component.as_os_str().to_string_lossy() != "node_modules") .take_while(|component| component.as_os_str().to_string_lossy() != "node_modules")
.collect::<PathBuf>(); .collect::<PathBuf>();
if path_to_check != locate_from {
log::debug!(
"Skipping prettier location for path {path_to_check:?} that is inside node_modules"
);
return Ok(ControlFlow::Break(()));
}
let path_to_check_metadata = fs let path_to_check_metadata = fs
.metadata(&path_to_check) .metadata(&path_to_check)
.await .await
@ -76,14 +83,14 @@ impl Prettier {
loop { loop {
if installed_prettiers.contains(&path_to_check) { if installed_prettiers.contains(&path_to_check) {
log::debug!("Found prettier path {path_to_check:?} in installed prettiers"); log::debug!("Found prettier path {path_to_check:?} in installed prettiers");
return Ok(Some(path_to_check)); return Ok(ControlFlow::Continue(Some(path_to_check)));
} else if let Some(package_json_contents) = } else if let Some(package_json_contents) =
read_package_json(fs, &path_to_check).await? read_package_json(fs, &path_to_check).await?
{ {
if has_prettier_in_package_json(&package_json_contents) { if has_prettier_in_package_json(&package_json_contents) {
if has_prettier_in_node_modules(fs, &path_to_check).await? { if has_prettier_in_node_modules(fs, &path_to_check).await? {
log::debug!("Found prettier path {path_to_check:?} in both package.json and node_modules"); log::debug!("Found prettier path {path_to_check:?} in both package.json and node_modules");
return Ok(Some(path_to_check)); return Ok(ControlFlow::Continue(Some(path_to_check)));
} else if project_path_with_prettier_dependency.is_none() { } else if project_path_with_prettier_dependency.is_none() {
project_path_with_prettier_dependency = Some(path_to_check.clone()); project_path_with_prettier_dependency = Some(path_to_check.clone());
} }
@ -109,7 +116,7 @@ impl Prettier {
}) { }) {
anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules"); anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules");
log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}"); log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}");
return Ok(Some(path_to_check)); return Ok(ControlFlow::Continue(Some(path_to_check)));
} else { } else {
log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}"); log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}");
} }
@ -132,7 +139,7 @@ impl Prettier {
} }
None => { None => {
log::debug!("Found no prettier in ancestors of {locate_from:?}"); log::debug!("Found no prettier in ancestors of {locate_from:?}");
return Ok(None); return Ok(ControlFlow::Continue(None));
} }
} }
} }
@ -527,37 +534,40 @@ mod tests {
.await; .await;
assert!( assert!(
Prettier::locate_prettier_installation( matches!(
fs.as_ref(), Prettier::locate_prettier_installation(
&HashSet::default(), fs.as_ref(),
Path::new("/root/.config/zed/settings.json"), &HashSet::default(),
) Path::new("/root/.config/zed/settings.json"),
.await )
.unwrap() .await,
.is_none(), Ok(ControlFlow::Continue(None))
),
"Should successfully find no prettier for path hierarchy without it" "Should successfully find no prettier for path hierarchy without it"
); );
assert!( assert!(
Prettier::locate_prettier_installation( matches!(
fs.as_ref(), Prettier::locate_prettier_installation(
&HashSet::default(), fs.as_ref(),
Path::new("/root/work/project/src/index.js") &HashSet::default(),
) Path::new("/root/work/project/src/index.js")
.await )
.unwrap() .await,
.is_none(), Ok(ControlFlow::Continue(None))
),
"Should successfully find no prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it" "Should successfully find no prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it"
); );
assert!( assert!(
Prettier::locate_prettier_installation( matches!(
fs.as_ref(), Prettier::locate_prettier_installation(
&HashSet::default(), fs.as_ref(),
Path::new("/root/work/project/node_modules/expect/build/print.js") &HashSet::default(),
) Path::new("/root/work/project/node_modules/expect/build/print.js")
.await )
.unwrap() .await,
.is_none(), Ok(ControlFlow::Break(()))
"Even though it has package.json with prettier in it and no prettier on node_modules along the path, nothing should fail since declared inside node_modules" ),
"Should not format files inside node_modules/"
); );
} }
@ -610,7 +620,7 @@ mod tests {
) )
.await .await
.unwrap(), .unwrap(),
Some(PathBuf::from("/root/web_blog")), ControlFlow::Continue(Some(PathBuf::from("/root/web_blog"))),
"Should find a preinstalled prettier in the project root" "Should find a preinstalled prettier in the project root"
); );
assert_eq!( assert_eq!(
@ -621,8 +631,8 @@ mod tests {
) )
.await .await
.unwrap(), .unwrap(),
Some(PathBuf::from("/root/web_blog")), ControlFlow::Break(()),
"Should find a preinstalled prettier in the project root even for node_modules files" "Should not allow formatting node_modules/ contents"
); );
} }
@ -634,6 +644,18 @@ mod tests {
json!({ json!({
"work": { "work": {
"web_blog": { "web_blog": {
"node_modules": {
"expect": {
"build": {
"print.js": "// print.js file contents",
},
"package.json": r#"{
"devDependencies": {
"prettier": "2.5.1"
}
}"#,
},
},
"pages": { "pages": {
"[slug].tsx": "// [slug].tsx file contents", "[slug].tsx": "// [slug].tsx file contents",
}, },
@ -654,18 +676,16 @@ mod tests {
) )
.await; .await;
let path = "/root/work/web_blog/node_modules/pages/[slug].tsx";
match Prettier::locate_prettier_installation( match Prettier::locate_prettier_installation(
fs.as_ref(), fs.as_ref(),
&HashSet::default(), &HashSet::default(),
Path::new(path) Path::new("/root/work/web_blog/pages/[slug].tsx")
) )
.await { .await {
Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"), Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"),
Err(e) => { Err(e) => {
let message = e.to_string(); let message = e.to_string();
assert!(message.contains(path), "Error message should mention which start file was used for location"); assert!(message.contains("/root/work/web_blog"), "Error message should mention which project had prettier defined");
assert!(message.contains("/root/work/web_blog"), "Error message should mention potential candidates without prettier node_modules contents");
}, },
}; };
@ -675,12 +695,37 @@ mod tests {
&HashSet::from_iter( &HashSet::from_iter(
[PathBuf::from("/root"), PathBuf::from("/root/work")].into_iter() [PathBuf::from("/root"), PathBuf::from("/root/work")].into_iter()
), ),
Path::new("/root/work/web_blog/node_modules/pages/[slug].tsx") Path::new("/root/work/web_blog/pages/[slug].tsx")
) )
.await .await
.unwrap(), .unwrap(),
Some(PathBuf::from("/root/work")), ControlFlow::Continue(Some(PathBuf::from("/root/work"))),
"Should return first cached value found without path checks" "Should return closest cached value found without path checks"
);
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/web_blog/node_modules/expect/build/print.js")
)
.await
.unwrap(),
ControlFlow::Break(()),
"Should not allow formatting files inside node_modules/"
);
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::from_iter(
[PathBuf::from("/root"), PathBuf::from("/root/work")].into_iter()
),
Path::new("/root/work/web_blog/node_modules/expect/build/print.js")
)
.await
.unwrap(),
ControlFlow::Break(()),
"Should ignore cache lookup for files inside node_modules/"
); );
} }
@ -704,7 +749,9 @@ mod tests {
}, },
}, },
}, },
"node_modules": {}, "node_modules": {
"test.js": "// test.js contents",
},
"package.json": r#"{ "package.json": r#"{
"devDependencies": { "devDependencies": {
"prettier": "^3.0.3" "prettier": "^3.0.3"
@ -733,9 +780,32 @@ mod tests {
&HashSet::default(), &HashSet::default(),
Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx"), Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx"),
).await.unwrap(), ).await.unwrap(),
Some(PathBuf::from("/root/work/full-stack-foundations")), ControlFlow::Continue(Some(PathBuf::from("/root/work/full-stack-foundations"))),
"Should ascend to the multi-workspace root and find the prettier there", "Should ascend to the multi-workspace root and find the prettier there",
); );
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/full-stack-foundations/node_modules/prettier/index.js")
)
.await
.unwrap(),
ControlFlow::Break(()),
"Should not allow formatting files inside root node_modules/"
);
assert_eq!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/node_modules/test.js")
)
.await
.unwrap(),
ControlFlow::Break(()),
"Should not allow formatting files inside submodule's node_modules/"
);
} }
#[gpui::test] #[gpui::test]

View file

@ -69,7 +69,7 @@ use std::{
hash::Hash, hash::Hash,
mem, mem,
num::NonZeroU32, num::NonZeroU32,
ops::Range, ops::{ControlFlow, Range},
path::{self, Component, Path, PathBuf}, path::{self, Component, Path, PathBuf},
process::Stdio, process::Stdio,
str, str,
@ -8442,7 +8442,10 @@ impl Project {
}) })
.await .await
{ {
Ok(None) => { Ok(ControlFlow::Break(())) => {
return None;
}
Ok(ControlFlow::Continue(None)) => {
let started_default_prettier = let started_default_prettier =
project.update(&mut cx, |project, _| { project.update(&mut cx, |project, _| {
project project
@ -8466,7 +8469,7 @@ impl Project {
} }
} }
} }
Ok(Some(prettier_dir)) => { Ok(ControlFlow::Continue(Some(prettier_dir))) => {
project.update(&mut cx, |project, _| { project.update(&mut cx, |project, _| {
project project
.prettiers_per_worktree .prettiers_per_worktree
@ -8593,7 +8596,7 @@ impl Project {
.await .await
}) })
} }
None => Task::ready(Ok(None)), None => Task::ready(Ok(ControlFlow::Break(()))),
}; };
let mut plugins_to_install = prettier_plugins; let mut plugins_to_install = prettier_plugins;
let previous_installation_process = let previous_installation_process =
@ -8622,8 +8625,9 @@ impl Project {
.context("locate prettier installation") .context("locate prettier installation")
.map_err(Arc::new)? .map_err(Arc::new)?
{ {
Some(_non_default_prettier) => return Ok(()), ControlFlow::Break(()) => return Ok(()),
None => { ControlFlow::Continue(Some(_non_default_prettier)) => return Ok(()),
ControlFlow::Continue(None) => {
let mut needs_install = match previous_installation_process { let mut needs_install = match previous_installation_process {
Some(previous_installation_process) => { Some(previous_installation_process) => {
previous_installation_process.await.is_err() previous_installation_process.await.is_err()

View file

@ -69,7 +69,7 @@ use std::{
hash::Hash, hash::Hash,
mem, mem,
num::NonZeroU32, num::NonZeroU32,
ops::Range, ops::{ControlFlow, Range},
path::{self, Component, Path, PathBuf}, path::{self, Component, Path, PathBuf},
process::Stdio, process::Stdio,
str, str,
@ -8488,7 +8488,10 @@ impl Project {
}) })
.await .await
{ {
Ok(None) => { Ok(ControlFlow::Break(())) => {
return None;
}
Ok(ControlFlow::Continue(None)) => {
match project.update(&mut cx, |project, _| { match project.update(&mut cx, |project, _| {
project project
.prettiers_per_worktree .prettiers_per_worktree
@ -8520,7 +8523,7 @@ impl Project {
.shared())), .shared())),
} }
} }
Ok(Some(prettier_dir)) => { Ok(ControlFlow::Continue(Some(prettier_dir))) => {
match project.update(&mut cx, |project, _| { match project.update(&mut cx, |project, _| {
project project
.prettiers_per_worktree .prettiers_per_worktree
@ -8662,7 +8665,7 @@ impl Project {
.await .await
}) })
} }
None => Task::ready(Ok(None)), None => Task::ready(Ok(ControlFlow::Break(()))),
}; };
let mut plugins_to_install = prettier_plugins; let mut plugins_to_install = prettier_plugins;
let previous_installation_process = let previous_installation_process =
@ -8692,8 +8695,9 @@ impl Project {
.context("locate prettier installation") .context("locate prettier installation")
.map_err(Arc::new)? .map_err(Arc::new)?
{ {
Some(_non_default_prettier) => return Ok(()), ControlFlow::Break(()) => return Ok(()),
None => { ControlFlow::Continue(Some(_non_default_prettier)) => return Ok(()),
ControlFlow::Continue(None) => {
let mut needs_install = match previous_installation_process { let mut needs_install = match previous_installation_process {
Some(previous_installation_process) => { Some(previous_installation_process) => {
previous_installation_process.await.is_err() previous_installation_process.await.is_err()

View file

@ -19,8 +19,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
let refineable_attr = attrs.iter().find(|attr| attr.path.is_ident("refineable")); let refineable_attr = attrs.iter().find(|attr| attr.path.is_ident("refineable"));
let mut impl_debug_on_refinement = false; let mut impl_debug_on_refinement = false;
let mut derive_serialize_on_refinement = false; let mut refinement_traits_to_derive = vec![];
let mut derive_deserialize_on_refinement = false;
if let Some(refineable_attr) = refineable_attr { if let Some(refineable_attr) = refineable_attr {
if let Ok(syn::Meta::List(meta_list)) = refineable_attr.parse_meta() { if let Ok(syn::Meta::List(meta_list)) = refineable_attr.parse_meta() {
@ -29,16 +28,10 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
continue; continue;
}; };
if path.is_ident("debug") { if path.is_ident("Debug") {
impl_debug_on_refinement = true; impl_debug_on_refinement = true;
} } else {
refinement_traits_to_derive.push(path);
if path.is_ident("serialize") {
derive_serialize_on_refinement = true;
}
if path.is_ident("deserialize") {
derive_deserialize_on_refinement = true;
} }
} }
} }
@ -259,22 +252,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
quote! {} quote! {}
}; };
let derive_serialize = if derive_serialize_on_refinement { let mut derive_stream = quote! {};
quote! { #[derive(serde::Serialize)]} for trait_to_derive in refinement_traits_to_derive {
} else { derive_stream.extend(quote! { #[derive(#trait_to_derive)] })
quote! {} }
};
let derive_deserialize = if derive_deserialize_on_refinement {
quote! { #[derive(serde::Deserialize)]}
} else {
quote! {}
};
let gen = quote! { let gen = quote! {
#[derive(Clone)] #[derive(Clone)]
#derive_serialize #derive_stream
#derive_deserialize
pub struct #refinement_ident #impl_generics { pub struct #refinement_ident #impl_generics {
#( #field_visibilities #field_names: #wrapped_types ),* #( #field_visibilities #field_names: #wrapped_types ),*
} }

View file

@ -1,9 +1,7 @@
use std::sync::Arc; use crate::{PlayerColors, SyntaxTheme};
use gpui::Hsla; use gpui::Hsla;
use refineable::Refineable; use refineable::Refineable;
use std::sync::Arc;
use crate::{PlayerColors, SyntaxTheme};
#[derive(Clone)] #[derive(Clone)]
pub struct SystemColors { pub struct SystemColors {
@ -14,7 +12,7 @@ pub struct SystemColors {
} }
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct StatusColors { pub struct StatusColors {
pub conflict: Hsla, pub conflict: Hsla,
pub created: Hsla, pub created: Hsla,
@ -30,7 +28,7 @@ pub struct StatusColors {
} }
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct GitStatusColors { pub struct GitStatusColors {
pub conflict: Hsla, pub conflict: Hsla,
pub created: Hsla, pub created: Hsla,
@ -41,7 +39,7 @@ pub struct GitStatusColors {
} }
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug, deserialize)] #[refineable(Debug, serde::Deserialize)]
pub struct ThemeColors { pub struct ThemeColors {
pub border: Hsla, pub border: Hsla,
/// Border color. Used for deemphasized borders, like a visual divider between two sections /// Border color. Used for deemphasized borders, like a visual divider between two sections

View file

@ -1,8 +1,7 @@
use crate::{Appearance, ThemeColors, ThemeColorsRefinement};
use refineable::Refineable; use refineable::Refineable;
use serde::Deserialize; use serde::Deserialize;
use crate::{Appearance, ThemeColors, ThemeColorsRefinement};
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct UserThemeFamily { pub struct UserThemeFamily {
pub name: String, pub name: String,
@ -18,7 +17,7 @@ pub struct UserTheme {
} }
#[derive(Refineable, Clone)] #[derive(Refineable, Clone)]
#[refineable(deserialize)] #[refineable(Deserialize)]
pub struct UserThemeStyles { pub struct UserThemeStyles {
#[refineable] #[refineable]
pub colors: ThemeColors, pub colors: ThemeColors,

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor." description = "The fast, collaborative code editor."
edition = "2021" edition = "2021"
name = "zed" name = "zed"
version = "0.112.0" version = "0.112.3"
publish = false publish = false
[lib] [lib]

View file

@ -1 +1 @@
dev stable

View file

@ -8,6 +8,11 @@
[ [
(jsx_element) (jsx_element)
(jsx_fragment) (jsx_fragment)
] @element
[
(jsx_opening_element)
(jsx_closing_element)
(jsx_self_closing_element) (jsx_self_closing_element)
(jsx_expression) (jsx_expression)
] @element ] @default

View file

@ -8,6 +8,11 @@
[ [
(jsx_element) (jsx_element)
(jsx_fragment) (jsx_fragment)
] @element
[
(jsx_opening_element)
(jsx_closing_element)
(jsx_self_closing_element) (jsx_self_closing_element)
(jsx_expression) (jsx_expression)
] @element ] @default

View file

@ -2,6 +2,7 @@ use anyhow::{anyhow, Result};
use async_compression::futures::bufread::GzipDecoder; use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive; use async_tar::Archive;
use async_trait::async_trait; use async_trait::async_trait;
use collections::HashMap;
use futures::{future::BoxFuture, FutureExt}; use futures::{future::BoxFuture, FutureExt};
use gpui::AppContext; use gpui::AppContext;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
@ -20,12 +21,7 @@ use util::{fs::remove_matching, github::latest_github_release};
use util::{github::GitHubLspBinaryVersion, ResultExt}; use util::{github::GitHubLspBinaryVersion, ResultExt};
fn typescript_server_binary_arguments(server_path: &Path) -> Vec<OsString> { fn typescript_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![ vec![server_path.into(), "--stdio".into()]
server_path.into(),
"--stdio".into(),
"--tsserver-path".into(),
"node_modules/typescript/lib".into(),
]
} }
fn eslint_server_binary_arguments(server_path: &Path) -> Vec<OsString> { fn eslint_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
@ -158,9 +154,20 @@ impl LspAdapter for TypeScriptLspAdapter {
async fn initialization_options(&self) -> Option<serde_json::Value> { async fn initialization_options(&self) -> Option<serde_json::Value> {
Some(json!({ Some(json!({
"provideFormatter": true "provideFormatter": true,
"tsserver": {
"path": "node_modules/typescript/lib",
},
})) }))
} }
async fn language_ids(&self) -> HashMap<String, String> {
HashMap::from_iter([
("TypeScript".into(), "typescript".into()),
("JavaScript".into(), "javascript".into()),
("TSX".into(), "typescriptreact".into()),
])
}
} }
async fn get_cached_ts_server_binary( async fn get_cached_ts_server_binary(

View file

@ -8,6 +8,11 @@
[ [
(jsx_element) (jsx_element)
(jsx_fragment) (jsx_fragment)
] @element
[
(jsx_opening_element)
(jsx_closing_element)
(jsx_self_closing_element) (jsx_self_closing_element)
(jsx_expression) (jsx_expression)
] @element ] @default

View file

@ -8,6 +8,11 @@
[ [
(jsx_element) (jsx_element)
(jsx_fragment) (jsx_fragment)
] @element
[
(jsx_opening_element)
(jsx_closing_element)
(jsx_self_closing_element) (jsx_self_closing_element)
(jsx_expression) (jsx_expression)
] @element ] @default