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]]
name = "zed"
version = "0.112.0"
version = "0.112.3"
dependencies = [
"activity_indicator",
"ai",

View file

@ -102,6 +102,16 @@
"selections": true
},
"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_hints": {
// 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 settings::Setting;
#[derive(Deserialize)]
#[derive(Clone, Deserialize)]
pub struct EditorSettings {
pub cursor_blink: bool,
pub hover_popover_enabled: bool,
@ -11,6 +11,15 @@ pub struct EditorSettings {
pub use_on_type_format: bool,
pub scrollbar: Scrollbar,
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)]
@ -38,6 +47,7 @@ pub struct EditorSettingsContent {
pub use_on_type_format: Option<bool>,
pub scrollbar: Option<ScrollbarContent>,
pub relative_line_numbers: Option<bool>,
pub seed_search_query_from_cursor: Option<SeedQuerySetting>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]

View file

@ -1,7 +1,7 @@
use crate::{
display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition,
movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition,
persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, EditorSettings, Event,
ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
};
use anyhow::{Context, Result};
use collections::HashSet;
@ -13,8 +13,8 @@ use gpui::{
ViewHandle, WeakViewHandle,
};
use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
SelectionGoal,
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
Point, SelectionGoal,
};
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
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 {
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);
if selection.start == selection.end {
let point = selection.start.to_display_point(&display_map);
let range = surrounding_word(&display_map, point);
let range = range.start.to_offset(&display_map, Bias::Left)
..range.end.to_offset(&display_map, Bias::Right);
let text: String = display_map.buffer_snapshot.text_for_range(range).collect();
if text.trim().is_empty() {
String::new()
} else {
text
match setting {
SeedQuerySetting::Never => String::new(),
SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
snapshot
.text_for_range(selection.start..selection.end)
.collect()
}
SeedQuerySetting::Selection => String::new(),
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 scrollbar: Scrollbar,
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)]
@ -38,6 +47,7 @@ pub struct EditorSettingsContent {
pub use_on_type_format: Option<bool>,
pub scrollbar: Option<ScrollbarContent>,
pub relative_line_numbers: Option<bool>,
pub seed_search_query_from_selection: Option<SeedQuerySetting>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]

View file

@ -1,7 +1,8 @@
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,
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
EditorSettings, Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot,
NavigationData, ToPoint as _,
};
use anyhow::{anyhow, Context, Result};
use collections::HashSet;
@ -12,11 +13,12 @@ use gpui::{
VisualContext, WeakView,
};
use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
SelectionGoal,
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
Point, SelectionGoal,
};
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
use smallvec::SmallVec;
use std::{
borrow::Cow,
@ -950,24 +952,28 @@ impl SearchableItem for Editor {
}
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);
if selection.start == selection.end {
let point = selection.start.to_display_point(&display_map);
let range = surrounding_word(&display_map, point);
let range = range.start.to_offset(&display_map, Bias::Left)
..range.end.to_offset(&display_map, Bias::Right);
let text: String = display_map.buffer_snapshot.text_for_range(range).collect();
if text.trim().is_empty() {
String::new()
} else {
text
match setting {
SeedQuerySetting::Never => String::new(),
SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
snapshot
.text_for_range(selection.start..selection.end)
.collect()
}
SeedQuerySetting::Selection => String::new(),
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)]
#[refineable(debug)]
#[refineable(Debug)]
pub struct Point<T: Clone + Default + Debug> {
pub x: 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)]
#[refineable(debug)]
#[refineable(Debug)]
pub struct Size<T: Clone + Default + Debug> {
pub width: T,
pub height: T,
@ -227,7 +227,7 @@ impl Size<Length> {
}
#[derive(Clone, Default, Refineable, Debug)]
#[refineable(debug)]
#[refineable(Debug)]
pub struct Edges<T: Clone + Default + Debug> {
pub top: T,
pub right: T,

View file

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

View file

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

View file

@ -1692,14 +1692,25 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
r#"
(jsx_element) @element
(string) @string
[
(jsx_opening_element)
(jsx_closing_element)
(jsx_expression)
] @default
"#,
)
.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 =
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 config = snapshot.language_scope_at(0).unwrap();
@ -1710,7 +1721,9 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
&[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(), "// ");
// Second bracket pair is disabled
assert_eq!(
@ -1718,18 +1731,49 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
&[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.block_comment_delimiters(),
Some((&"{/*".into(), &"*/}".into()))
);
// Both bracket pairs are enabled
assert_eq!(
element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
&[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
});
}

View file

@ -1696,14 +1696,25 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
r#"
(jsx_element) @element
(string) @string
[
(jsx_opening_element)
(jsx_closing_element)
(jsx_expression)
] @default
"#,
)
.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 =
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 config = snapshot.language_scope_at(0).unwrap();
@ -1714,7 +1725,9 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
&[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(), "// ");
// Second bracket pair is disabled
assert_eq!(
@ -1722,18 +1735,49 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
&[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.block_comment_delimiters(),
Some((&"{/*".into(), &"*/}".into()))
);
// Both bracket pairs are enabled
assert_eq!(
element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
&[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
});
}

View file

@ -1,3 +1,4 @@
use std::ops::ControlFlow;
use std::path::{Path, PathBuf};
use std::sync::Arc;
@ -58,11 +59,17 @@ impl Prettier {
fs: &dyn Fs,
installed_prettiers: &HashSet<PathBuf>,
locate_from: &Path,
) -> anyhow::Result<Option<PathBuf>> {
) -> anyhow::Result<ControlFlow<(), Option<PathBuf>>> {
let mut path_to_check = locate_from
.components()
.take_while(|component| component.as_os_str().to_string_lossy() != "node_modules")
.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
.metadata(&path_to_check)
.await
@ -76,14 +83,14 @@ impl Prettier {
loop {
if installed_prettiers.contains(&path_to_check) {
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) =
read_package_json(fs, &path_to_check).await?
{
if has_prettier_in_package_json(&package_json_contents) {
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");
return Ok(Some(path_to_check));
return Ok(ControlFlow::Continue(Some(path_to_check)));
} else if project_path_with_prettier_dependency.is_none() {
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");
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 {
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 => {
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;
assert!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/.config/zed/settings.json"),
)
.await
.unwrap()
.is_none(),
matches!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/.config/zed/settings.json"),
)
.await,
Ok(ControlFlow::Continue(None))
),
"Should successfully find no prettier for path hierarchy without it"
);
assert!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/src/index.js")
)
.await
.unwrap()
.is_none(),
matches!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/src/index.js")
)
.await,
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"
);
assert!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/node_modules/expect/build/print.js")
)
.await
.unwrap()
.is_none(),
"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"
matches!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/node_modules/expect/build/print.js")
)
.await,
Ok(ControlFlow::Break(()))
),
"Should not format files inside node_modules/"
);
}
@ -580,7 +590,7 @@ mod tests {
)
.await
.unwrap(),
Some(PathBuf::from("/root/web_blog")),
ControlFlow::Continue(Some(PathBuf::from("/root/web_blog"))),
"Should find a preinstalled prettier in the project root"
);
assert_eq!(
@ -591,8 +601,8 @@ mod tests {
)
.await
.unwrap(),
Some(PathBuf::from("/root/web_blog")),
"Should find a preinstalled prettier in the project root even for node_modules files"
ControlFlow::Break(()),
"Should not allow formatting node_modules/ contents"
);
}
@ -604,6 +614,18 @@ mod tests {
json!({
"work": {
"web_blog": {
"node_modules": {
"expect": {
"build": {
"print.js": "// print.js file contents",
},
"package.json": r#"{
"devDependencies": {
"prettier": "2.5.1"
}
}"#,
},
},
"pages": {
"[slug].tsx": "// [slug].tsx file contents",
},
@ -624,33 +646,55 @@ mod tests {
)
.await;
let path = "/root/work/web_blog/node_modules/pages/[slug].tsx";
match Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new(path)
Path::new("/root/work/web_blog/pages/[slug].tsx")
)
.await {
Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"),
Err(e) => {
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 potential candidates without prettier node_modules contents");
assert!(message.contains("/root/work/web_blog"), "Error message should mention which project had prettier defined");
},
};
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/pages/[slug].tsx")
Path::new("/root/work/web_blog/pages/[slug].tsx")
)
.await
.unwrap(),
Some(PathBuf::from("/root/work")),
"Should return first cached value found without path checks"
ControlFlow::Continue(Some(PathBuf::from("/root/work"))),
"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#"{
"devDependencies": {
"prettier": "^3.0.3"
@ -703,9 +749,32 @@ mod tests {
&HashSet::default(),
Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx"),
).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",
);
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]

View file

@ -7,6 +7,7 @@ use lsp::{LanguageServer, LanguageServerId};
use node_runtime::NodeRuntime;
use serde::{Deserialize, Serialize};
use std::{
ops::ControlFlow,
path::{Path, PathBuf},
sync::Arc,
};
@ -58,11 +59,17 @@ impl Prettier {
fs: &dyn Fs,
installed_prettiers: &HashSet<PathBuf>,
locate_from: &Path,
) -> anyhow::Result<Option<PathBuf>> {
) -> anyhow::Result<ControlFlow<(), Option<PathBuf>>> {
let mut path_to_check = locate_from
.components()
.take_while(|component| component.as_os_str().to_string_lossy() != "node_modules")
.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
.metadata(&path_to_check)
.await
@ -76,14 +83,14 @@ impl Prettier {
loop {
if installed_prettiers.contains(&path_to_check) {
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) =
read_package_json(fs, &path_to_check).await?
{
if has_prettier_in_package_json(&package_json_contents) {
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");
return Ok(Some(path_to_check));
return Ok(ControlFlow::Continue(Some(path_to_check)));
} else if project_path_with_prettier_dependency.is_none() {
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");
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 {
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 => {
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;
assert!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/.config/zed/settings.json"),
)
.await
.unwrap()
.is_none(),
matches!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/.config/zed/settings.json"),
)
.await,
Ok(ControlFlow::Continue(None))
),
"Should successfully find no prettier for path hierarchy without it"
);
assert!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/src/index.js")
)
.await
.unwrap()
.is_none(),
matches!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/src/index.js")
)
.await,
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"
);
assert!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/node_modules/expect/build/print.js")
)
.await
.unwrap()
.is_none(),
"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"
matches!(
Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new("/root/work/project/node_modules/expect/build/print.js")
)
.await,
Ok(ControlFlow::Break(()))
),
"Should not format files inside node_modules/"
);
}
@ -610,7 +620,7 @@ mod tests {
)
.await
.unwrap(),
Some(PathBuf::from("/root/web_blog")),
ControlFlow::Continue(Some(PathBuf::from("/root/web_blog"))),
"Should find a preinstalled prettier in the project root"
);
assert_eq!(
@ -621,8 +631,8 @@ mod tests {
)
.await
.unwrap(),
Some(PathBuf::from("/root/web_blog")),
"Should find a preinstalled prettier in the project root even for node_modules files"
ControlFlow::Break(()),
"Should not allow formatting node_modules/ contents"
);
}
@ -634,6 +644,18 @@ mod tests {
json!({
"work": {
"web_blog": {
"node_modules": {
"expect": {
"build": {
"print.js": "// print.js file contents",
},
"package.json": r#"{
"devDependencies": {
"prettier": "2.5.1"
}
}"#,
},
},
"pages": {
"[slug].tsx": "// [slug].tsx file contents",
},
@ -654,18 +676,16 @@ mod tests {
)
.await;
let path = "/root/work/web_blog/node_modules/pages/[slug].tsx";
match Prettier::locate_prettier_installation(
fs.as_ref(),
&HashSet::default(),
Path::new(path)
Path::new("/root/work/web_blog/pages/[slug].tsx")
)
.await {
Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"),
Err(e) => {
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 potential candidates without prettier node_modules contents");
assert!(message.contains("/root/work/web_blog"), "Error message should mention which project had prettier defined");
},
};
@ -675,12 +695,37 @@ mod tests {
&HashSet::from_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
.unwrap(),
Some(PathBuf::from("/root/work")),
"Should return first cached value found without path checks"
ControlFlow::Continue(Some(PathBuf::from("/root/work"))),
"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#"{
"devDependencies": {
"prettier": "^3.0.3"
@ -733,9 +780,32 @@ mod tests {
&HashSet::default(),
Path::new("/root/work/full-stack-foundations/exercises/03.loading/01.problem.loader/app/routes/users+/$username_+/notes.tsx"),
).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",
);
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]

View file

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

View file

@ -69,7 +69,7 @@ use std::{
hash::Hash,
mem,
num::NonZeroU32,
ops::Range,
ops::{ControlFlow, Range},
path::{self, Component, Path, PathBuf},
process::Stdio,
str,
@ -8488,7 +8488,10 @@ impl Project {
})
.await
{
Ok(None) => {
Ok(ControlFlow::Break(())) => {
return None;
}
Ok(ControlFlow::Continue(None)) => {
match project.update(&mut cx, |project, _| {
project
.prettiers_per_worktree
@ -8520,7 +8523,7 @@ impl Project {
.shared())),
}
}
Ok(Some(prettier_dir)) => {
Ok(ControlFlow::Continue(Some(prettier_dir))) => {
match project.update(&mut cx, |project, _| {
project
.prettiers_per_worktree
@ -8662,7 +8665,7 @@ impl Project {
.await
})
}
None => Task::ready(Ok(None)),
None => Task::ready(Ok(ControlFlow::Break(()))),
};
let mut plugins_to_install = prettier_plugins;
let previous_installation_process =
@ -8692,8 +8695,9 @@ impl Project {
.context("locate prettier installation")
.map_err(Arc::new)?
{
Some(_non_default_prettier) => return Ok(()),
None => {
ControlFlow::Break(()) => return Ok(()),
ControlFlow::Continue(Some(_non_default_prettier)) => return Ok(()),
ControlFlow::Continue(None) => {
let mut needs_install = match previous_installation_process {
Some(previous_installation_process) => {
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 mut impl_debug_on_refinement = false;
let mut derive_serialize_on_refinement = false;
let mut derive_deserialize_on_refinement = false;
let mut refinement_traits_to_derive = vec![];
if let Some(refineable_attr) = refineable_attr {
if let Ok(syn::Meta::List(meta_list)) = refineable_attr.parse_meta() {
@ -29,16 +28,10 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
continue;
};
if path.is_ident("debug") {
if path.is_ident("Debug") {
impl_debug_on_refinement = true;
}
if path.is_ident("serialize") {
derive_serialize_on_refinement = true;
}
if path.is_ident("deserialize") {
derive_deserialize_on_refinement = true;
} else {
refinement_traits_to_derive.push(path);
}
}
}
@ -259,22 +252,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
quote! {}
};
let derive_serialize = if derive_serialize_on_refinement {
quote! { #[derive(serde::Serialize)]}
} else {
quote! {}
};
let derive_deserialize = if derive_deserialize_on_refinement {
quote! { #[derive(serde::Deserialize)]}
} else {
quote! {}
};
let mut derive_stream = quote! {};
for trait_to_derive in refinement_traits_to_derive {
derive_stream.extend(quote! { #[derive(#trait_to_derive)] })
}
let gen = quote! {
#[derive(Clone)]
#derive_serialize
#derive_deserialize
#derive_stream
pub struct #refinement_ident #impl_generics {
#( #field_visibilities #field_names: #wrapped_types ),*
}

View file

@ -1,9 +1,7 @@
use std::sync::Arc;
use crate::{PlayerColors, SyntaxTheme};
use gpui::Hsla;
use refineable::Refineable;
use crate::{PlayerColors, SyntaxTheme};
use std::sync::Arc;
#[derive(Clone)]
pub struct SystemColors {
@ -14,7 +12,7 @@ pub struct SystemColors {
}
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
#[refineable(Debug)]
pub struct StatusColors {
pub conflict: Hsla,
pub created: Hsla,
@ -30,7 +28,7 @@ pub struct StatusColors {
}
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
#[refineable(Debug)]
pub struct GitStatusColors {
pub conflict: Hsla,
pub created: Hsla,
@ -41,7 +39,7 @@ pub struct GitStatusColors {
}
#[derive(Refineable, Clone, Debug)]
#[refineable(debug, deserialize)]
#[refineable(Debug, serde::Deserialize)]
pub struct ThemeColors {
pub border: Hsla,
/// 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 serde::Deserialize;
use crate::{Appearance, ThemeColors, ThemeColorsRefinement};
#[derive(Deserialize)]
pub struct UserThemeFamily {
pub name: String,
@ -18,7 +17,7 @@ pub struct UserTheme {
}
#[derive(Refineable, Clone)]
#[refineable(deserialize)]
#[refineable(Deserialize)]
pub struct UserThemeStyles {
#[refineable]
pub colors: ThemeColors,

View file

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

View file

@ -1 +1 @@
dev
stable

View file

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

View file

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

View file

@ -2,6 +2,7 @@ use anyhow::{anyhow, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use collections::HashMap;
use futures::{future::BoxFuture, FutureExt};
use gpui::AppContext;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
@ -20,12 +21,7 @@ use util::{fs::remove_matching, github::latest_github_release};
use util::{github::GitHubLspBinaryVersion, ResultExt};
fn typescript_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![
server_path.into(),
"--stdio".into(),
"--tsserver-path".into(),
"node_modules/typescript/lib".into(),
]
vec![server_path.into(), "--stdio".into()]
}
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> {
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(

View file

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

View file

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