Compare commits

...

17 Commits

Author SHA1 Message Date
Joseph T. Lyons
bda5713151 zed 0.130.5 2024-04-10 14:35:16 -04:00
gcp-cherry-pick-bot[bot]
778e1ad7ab Fix panic when loading malformed Wasm files (cherry-pick #10370) (#10372)
Cherry-picked Fix panic when loading malformed Wasm files (#10370)

This PR fixes a potential panic that could occur when loading malformed
Wasm files.

We now use the `parse_wasm_extension_version` function that was
previously used just to extract the Zed extension API version from the
Wasm bytes as a pre-validation step. By parsing the entirety of the Wasm
file here instead of returning as soon as we find the version, the
invalid Wasm bytes are now surfaced as an `Err` instead of a panic.

We were able to replicate the panic using the following test:

```rs
#[gpui::test]
async fn test_bad_wasm(cx: &mut TestAppContext) {
    init_test(cx);

    let wasm_host = cx.update(|cx| {
        WasmHost::new(
            FakeFs::new(cx.background_executor().clone()),
            FakeHttpClient::with_200_response(),
            FakeNodeRuntime::new(),
            Arc::new(LanguageRegistry::test(cx.background_executor().clone())),
            PathBuf::from("/the/work/dir".to_string()),
            cx,
        )
    });

    let mut wasm_bytes = std::fs::read("/Users/maxdeviant/Library/Application Support/Zed/extensions/installed/dart/extension.wasm").unwrap();

    // This is the error message we were seeing in the stack trace:
    // range end index 267037 out of range for slice of length 253952

    dbg!(&wasm_bytes.len());

    // Truncate the bytes to the same point:
    wasm_bytes.truncate(253952);

    std::fs::write("/tmp/bad-extension.wasm", wasm_bytes.clone()).unwrap();

    let manifest = Arc::new(ExtensionManifest {
        id: "the-extension".into(),
        name: "The Extension".into(),
        version: "0.0.1".into(),
        schema_version: SchemaVersion(1),
        description: Default::default(),
        repository: Default::default(),
        authors: Default::default(),
        lib: LibManifestEntry {
            kind: None,
            version: None,
        },
        themes: Default::default(),
        languages: Default::default(),
        grammars: Default::default(),
        language_servers: Default::default(),
    });

    // 💥
    let result = wasm_host
        .load_extension(wasm_bytes, manifest, cx.executor())
        .await;
    dbg!(result.map(|_| ()));
```



Release Notes:

- Fixed a crash that could occur when loading malformed Wasm extensions
([#10352](https://github.com/zed-industries/zed/issues/10352)).

---------

Co-authored-by: Max <max@zed.dev>

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Max <max@zed.dev>
2024-04-10 14:29:34 -04:00
Joseph T. Lyons
044b04a104 v0.130.x stable 2024-04-10 11:44:54 -04:00
Marshall Bowers
b038248540 zed 0.130.4 2024-04-09 17:36:44 -04:00
gcp-cherry-pick-bot[bot]
fbfbc68db4 Use first line comment prefix when toggling comments (cherry-pick #10335) (#10339)
Cherry-picked Use first line comment prefix when toggling comments
(#10335)

This fixed an issue introduced in
https://github.com/zed-industries/zed/pull/10126, where, when toggling
comments in a language with multiple line comment prefixes (e.g. Gleam,
Erlang) Zed would insert the *last* prefix instead of the first.

Release Notes:

- Fixed an issue where the `toggle comments` command inserted the wrong
line comment prefix in some languages (preview only).

Co-authored-by: Marshall <marshall@zed.dev>

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Marshall <marshall@zed.dev>
2024-04-09 17:35:06 -04:00
Marshall Bowers
67b52968ff zed 0.130.3 2024-04-09 11:35:07 -04:00
gcp-cherry-pick-bot[bot]
f7fe065f5f Only apply host-side executable fix to binaries downloaded by the extension (cherry-pick #10318) (#10319)
Cherry-picked Only apply host-side executable fix to binaries downloaded
by the extension (#10318)

This PR makes it so our temporary host-side workaround for setting
certain language server binaries as executable only applies to binaries
that are downloaded by the extension.

Previously we would do this for any binary, including ones that could
have been sourced from the $PATH.

Release Notes:

- Fixed a file permissions issue when trying to use a Zig language
server (`zls`) present on the $PATH.

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-09 09:20:30 -04:00
Conrad Irwin
d4a59f69bb zed 0.130.2 2024-04-08 12:42:49 -06:00
Marshall Bowers
4976d8f6bc Respect language server's capabilities when calling GetReferences (cherry-pick #10285) (#10291)
Cherry-pick 56c0345cf3 into `v0.130.x`.

This PR makes Zed respect the language server's capabilities when
calling the `GetReferences` command (used in "Find All References",
etc.).

This fixes a crash that could occur when using Zed with Gleam v1.0.

Release Notes:

- Made "Find All References" respect the language server's capabilities.
This fixes some instances where certain language servers would stop
working after receiving a "Find All References" request.

Co-authored-by: Max <max@zed.dev>
2024-04-08 14:10:32 -04:00
gcp-cherry-pick-bot[bot]
03d06412a3 Fix panic in visual line mode with folds (cherry-pick #10284) (#10290)
Cherry-picked Fix panic in visual line mode with folds (#10284)

Fixes: #10266



Release Notes:

- Added/Fixed/Improved ...

([#<public_issue_number_if_exists>](https://github.com/zed-industries/zed/issues/<public_issue_number_if_exists>)).

Optionally, include screenshots / media showcasing your addition that
can be included in the release notes.

**or**

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-08 12:01:44 -06:00
gcp-cherry-pick-bot[bot]
d43f95db5f Fix panic when deleting just-generated text (cherry-pick #10282) (#10287)
Cherry-picked Fix panic when deleting just-generated text (#10282)

We ran into a panic when deleting text that was just generated by a code
action.

This fixes it by ensuring we don't run into a 0-minus-1 panic when a
buffer_range is empty.

Release Notes:

- Fixed panic that could occur when deleting generated-by-unsaved text.

Co-authored-by: Conrad <conrad@zed.dev>

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
Co-authored-by: Conrad <conrad@zed.dev>
2024-04-08 11:38:55 -06:00
gcp-cherry-pick-bot[bot]
07b70cd4a1 tasks: fix panic in render_match (cherry-pick #10137) (#10288)
Cherry-picked tasks: fix panic in render_match (#10137)

Release Notes:

- Fixed panic in tasks modal (Preview only).

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-04-08 11:38:40 -06:00
gcp-cherry-pick-bot[bot]
77bd4dc413 Fix panic in drag entered (cherry-pick #10277) (#10286)
Cherry-picked Fix panic in drag entered (#10277)

Co-Authored-By: Kirill <kirill@zed.dev>

Release Notes:

- Fixed panic when dragging into Zed.

Optionally, include screenshots / media showcasing your addition that
can be included in the release notes.

**or**

- N/A

Co-authored-by: Kirill <kirill@zed.dev>

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Kirill <kirill@zed.dev>
2024-04-08 11:26:31 -06:00
Kirill Bulatov
b6fd9487eb Return back the ability to save non-dirty singleton buffers (#10174) 2024-04-04 19:07:37 +03:00
Thorsten Ball
b33f7c64f6 zed 0.130.1 2024-04-04 11:47:51 +02:00
Thorsten Ball
e950a992fd Improve handling of prettier errors on format (#10156)
When no formatter for a language is specified, Zed has the default
behaviour:

1. Attempt to format the buffer with `prettier`
2. If that doesn't work, use the language server.

The problem was that if `prettier` failed to format a buffer due to
legitimate syntax errors, we simply did a fallback to the language
server, which would then format over the syntax errors.

With JavaScript/React/TypeScript projects this could lead to a situation
where

1. Syntax error was introduced
2. Prettier fails
3. Zed ignores the error
4. typescript-language-server formats the buffer despite syntax errors

This would lead to some very weird formatting issues.

What this PR does is to fix the issue by handling `prettier` errors and
results in two user facing changes:

1. When no formatter is set (or set to `auto`) and if we attempted to
start a prettier instance to format, we will now display that error and
*not* fall back to language server formatting.
2. If the formatter is explicitly set to `prettier`, we will now show
errors if we failed to spawn prettier or failed to format with it.

This means that we now might show *more* errors than previously, but I
think that's better than not showing anything to the user at all.

And, of course, it also fixes the issue of invalid syntax being
formatted by the language server even though `prettier` failed with an
error.

Release Notes:

- Improved error handling when formatting buffers with `prettier`.
Previously `prettier` errors would be logged but ignored. Now `prettier`
errors are shown in the UI, just like language server errors when
formatting. And if no formatter is specified (or set to `"auto"`) and
Zed attempts to use `prettier` for formatting, then `prettier` errors
are no longer skipped. That fixes the issue of `prettier` not formatting
invalid syntax, but its error being skipped, leading to
`typescript-language-server` or another language server formatting
invalid syntax.
2024-04-04 11:47:20 +02:00
Joseph T. Lyons
8c05d7875e v0.130.x preview 2024-04-03 12:11:10 -04:00
19 changed files with 220 additions and 149 deletions

2
Cargo.lock generated
View File

@@ -12379,7 +12379,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.130.0"
version = "0.130.5"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -4641,9 +4641,16 @@ async fn test_references(
let active_call_a = cx_a.read(ActiveCall::global);
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a
.language_registry()
.register_fake_lsp_adapter("Rust", Default::default());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
references_provider: Some(lsp::OneOf::Left(true)),
..Default::default()
},
..Default::default()
},
);
client_a
.fs()

View File

@@ -433,7 +433,10 @@ impl DisplaySnapshot {
} else if range.start.row == self.max_buffer_row()
|| (range.end.column > 0 && range.end.row == self.max_buffer_row())
{
Point::new(range.start.row - 1, self.line_len(range.start.row - 1))
Point::new(
range.start.row - 1,
self.buffer_snapshot.line_len(range.start.row - 1),
)
} else {
self.prev_line_boundary(range.start).0
};

View File

@@ -7096,22 +7096,13 @@ impl Editor {
.line_comment_prefixes()
.filter(|prefixes| !prefixes.is_empty())
{
// Split the comment prefix's trailing whitespace into a separate string,
// as that portion won't be used for detecting if a line is a comment.
struct Comment {
full_prefix: Arc<str>,
trimmed_prefix_len: usize,
}
let prefixes: SmallVec<[Comment; 4]> = full_comment_prefixes
let first_prefix = full_comment_prefixes
.first()
.expect("prefixes is non-empty");
let prefix_trimmed_lengths = full_comment_prefixes
.iter()
.map(|full_prefix| {
let trimmed_prefix_len = full_prefix.trim_end_matches(' ').len();
Comment {
trimmed_prefix_len,
full_prefix: full_prefix.clone(),
}
})
.collect();
.map(|p| p.trim_end_matches(' ').len())
.collect::<SmallVec<[usize; 4]>>();
let mut all_selection_lines_are_comments = true;
@@ -7120,28 +7111,25 @@ impl Editor {
continue;
}
let Some((prefix, prefix_range)) = prefixes
let prefix_range = full_comment_prefixes
.iter()
.map(|prefix| {
(
prefix,
comment_prefix_range(
snapshot.deref(),
row,
&prefix.full_prefix[..prefix.trimmed_prefix_len],
&prefix.full_prefix[prefix.trimmed_prefix_len..],
),
.zip(prefix_trimmed_lengths.iter().copied())
.map(|(prefix, trimmed_prefix_len)| {
comment_prefix_range(
snapshot.deref(),
row,
&prefix[..trimmed_prefix_len],
&prefix[trimmed_prefix_len..],
)
})
.max_by_key(|(_, range)| range.end.column - range.start.column)
else {
// There has to be at least one prefix.
break;
};
.max_by_key(|range| range.end.column - range.start.column)
.expect("prefixes is non-empty");
if prefix_range.is_empty() {
all_selection_lines_are_comments = false;
}
selection_edit_ranges.push((prefix_range, prefix.full_prefix.clone()));
selection_edit_ranges.push(prefix_range);
}
if all_selection_lines_are_comments {
@@ -7149,17 +7137,17 @@ impl Editor {
selection_edit_ranges
.iter()
.cloned()
.map(|(range, _)| (range, empty_str.clone())),
.map(|range| (range, empty_str.clone())),
);
} else {
let min_column = selection_edit_ranges
.iter()
.map(|(range, _)| range.start.column)
.map(|range| range.start.column)
.min()
.unwrap_or(0);
edits.extend(selection_edit_ranges.iter().map(|(range, prefix)| {
edits.extend(selection_edit_ranges.iter().map(|range| {
let position = Point::new(range.start.row, min_column);
(position..position, prefix.clone())
(position..position, first_prefix.clone())
}));
}
} else if let Some((full_comment_prefix, comment_suffix)) =

View File

@@ -6507,7 +6507,7 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
let language = Arc::new(Language::new(
LanguageConfig {
line_comments: vec!["// ".into()],
line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
..Default::default()
},
Some(tree_sitter_rust::language()),
@@ -6601,6 +6601,25 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
// c();ˇ»
}
"});
// If a selection includes multiple comment prefixes, all lines are uncommented.
cx.set_state(indoc! {"
fn a() {
«// a();
/// b();
//! c();ˇ»
}
"});
cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
cx.assert_editor_state(indoc! {"
fn a() {
«a();
b();
c();ˇ»
}
"});
}
#[gpui::test]

View File

@@ -705,31 +705,38 @@ impl Item for Editor {
.await?;
}
// Only format and save the buffers with changes. For clean buffers,
// we simulate saving by calling `Buffer::did_save`, so that language servers or
// other downstream listeners of save events get notified.
let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| {
buffer
.update(&mut cx, |buffer, _| {
buffer.is_dirty() || buffer.has_conflict()
})
.unwrap_or(false)
});
if buffers.len() == 1 {
// Apply full save routine for singleton buffers, to allow to `touch` the file via the editor.
project
.update(&mut cx, |project, cx| project.save_buffers(buffers, cx))?
.await?;
} else {
// For multi-buffers, only format and save the buffers with changes.
// For clean buffers, we simulate saving by calling `Buffer::did_save`,
// so that language servers or other downstream listeners of save events get notified.
let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| {
buffer
.update(&mut cx, |buffer, _| {
buffer.is_dirty() || buffer.has_conflict()
})
.unwrap_or(false)
});
project
.update(&mut cx, |project, cx| {
project.save_buffers(dirty_buffers, cx)
})?
.await?;
for buffer in clean_buffers {
buffer
.update(&mut cx, |buffer, cx| {
let version = buffer.saved_version().clone();
let fingerprint = buffer.saved_version_fingerprint();
let mtime = buffer.saved_mtime();
buffer.did_save(version, fingerprint, mtime, cx);
})
.ok();
project
.update(&mut cx, |project, cx| {
project.save_buffers(dirty_buffers, cx)
})?
.await?;
for buffer in clean_buffers {
buffer
.update(&mut cx, |buffer, cx| {
let version = buffer.saved_version().clone();
let fingerprint = buffer.saved_version_fingerprint();
let mtime = buffer.saved_mtime();
buffer.did_save(version, fingerprint, mtime, cx);
})
.ok();
}
}
Ok(())

View File

@@ -64,7 +64,9 @@ impl LspAdapter for ExtensionLspAdapter {
// We can remove once the following extension versions no longer see any use:
// - toml@0.0.2
// - zig@0.0.1
if ["toml", "zig"].contains(&self.extension.manifest.id.as_ref()) {
if ["toml", "zig"].contains(&self.extension.manifest.id.as_ref())
&& path.starts_with(&self.host.work_dir)
{
#[cfg(not(windows))]
{
use std::fs::{self, Permissions};

View File

@@ -183,13 +183,15 @@ pub fn parse_wasm_extension_version(
extension_id: &str,
wasm_bytes: &[u8],
) -> Result<SemanticVersion> {
let mut version = None;
for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
if let wasmparser::Payload::CustomSection(s) = part? {
if let wasmparser::Payload::CustomSection(s) =
part.context("error parsing wasm extension")?
{
if s.name() == "zed:api-version" {
let version = parse_wasm_extension_version_custom_section(s.data());
if let Some(version) = version {
return Ok(version);
} else {
version = parse_wasm_extension_version_custom_section(s.data());
if version.is_none() {
bail!(
"extension {} has invalid zed:api-version section: {:?}",
extension_id,
@@ -199,7 +201,13 @@ pub fn parse_wasm_extension_version(
}
}
}
bail!("extension {} has no zed:api-version section", extension_id)
// The reason we wait until we're done parsing all of the Wasm bytes to return the version
// is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
//
// By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
// earlier as an `Err` rather than as a panic.
version.ok_or_else(|| anyhow!("extension {} has no zed:api-version section", extension_id))
}
fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {

View File

@@ -1848,16 +1848,17 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
let window_state = unsafe { get_window_state(this) };
if send_new_event(&window_state, {
let position = drag_event_position(&window_state, dragging_info);
let paths = external_paths_from_event(dragging_info);
PlatformInput::FileDrop(FileDropEvent::Entered { position, paths })
}) {
window_state.lock().external_files_dragged = true;
NSDragOperationCopy
} else {
NSDragOperationNone
let position = drag_event_position(&window_state, dragging_info);
let paths = external_paths_from_event(dragging_info);
if let Some(event) =
paths.map(|paths| PlatformInput::FileDrop(FileDropEvent::Entered { position, paths }))
{
if send_new_event(&window_state, event) {
window_state.lock().external_files_dragged = true;
return NSDragOperationCopy;
}
}
NSDragOperationNone
}
extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
@@ -1895,10 +1896,13 @@ extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -
}
}
fn external_paths_from_event(dragging_info: *mut Object) -> ExternalPaths {
fn external_paths_from_event(dragging_info: *mut Object) -> Option<ExternalPaths> {
let mut paths = SmallVec::new();
let pasteboard: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
let filenames = unsafe { NSPasteboard::propertyListForType(pasteboard, NSFilenamesPboardType) };
if filenames == nil {
return None;
}
for file in unsafe { filenames.iter() } {
let path = unsafe {
let f = NSString::UTF8String(file);
@@ -1906,7 +1910,7 @@ fn external_paths_from_event(dragging_info: *mut Object) -> ExternalPaths {
};
paths.push(PathBuf::from(path))
}
ExternalPaths(paths)
Some(ExternalPaths(paths))
}
extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {

View File

@@ -2777,10 +2777,13 @@ impl BufferSnapshot {
range.start + self.line_len(start.row as u32) as usize - start.column;
}
buffer_ranges.push((range, node_is_name));
if !range.is_empty() {
buffer_ranges.push((range, node_is_name));
}
}
if buffer_ranges.is_empty() {
matches.advance();
continue;
}

View File

@@ -1,7 +1,7 @@
name = "Rust"
grammar = "rust"
path_suffixes = ["rs"]
line_comments = [ "/// ", "//! ", "// "]
line_comments = ["// ", "/// ", "//! "]
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },

View File

@@ -896,6 +896,14 @@ impl LspCommand for GetReferences {
type LspRequest = lsp::request::References;
type ProtoRequest = proto::GetReferences;
fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
match &capabilities.references_provider {
Some(OneOf::Left(has_support)) => *has_support,
Some(OneOf::Right(_)) => true,
None => false,
}
}
fn to_lsp(
&self,
path: &Path,

View File

@@ -4,7 +4,7 @@ use std::{
sync::Arc,
};
use anyhow::Context;
use anyhow::{anyhow, Context, Result};
use collections::HashSet;
use fs::Fs;
use futures::{
@@ -46,51 +46,48 @@ pub(super) async fn format_with_prettier(
project: &WeakModel<Project>,
buffer: &Model<Buffer>,
cx: &mut AsyncAppContext,
) -> Option<FormatOperation> {
if let Some((prettier_path, prettier_task)) = project
) -> Option<Result<FormatOperation>> {
let prettier_instance = project
.update(cx, |project, cx| {
project.prettier_instance_for_buffer(buffer, cx)
})
.ok()?
.await
{
match prettier_task.await {
Ok(prettier) => {
let buffer_path = buffer
.update(cx, |buffer, cx| {
File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
})
.ok()?;
match prettier.format(buffer, buffer_path, cx).await {
Ok(new_diff) => return Some(FormatOperation::Prettier(new_diff)),
Err(e) => {
match prettier_path {
Some(prettier_path) => log::error!(
"Prettier instance from path {prettier_path:?} failed to format a buffer: {e:#}"
),
None => log::error!(
"Default prettier instance failed to format a buffer: {e:#}"
),
}
}
}
}
Err(e) => project
.await;
let Some((prettier_path, prettier_task)) = prettier_instance else {
return None;
};
let prettier_description = match prettier_path.as_ref() {
Some(path) => format!("prettier at {path:?}"),
None => "default prettier instance".to_string(),
};
match prettier_task.await {
Ok(prettier) => {
let buffer_path = buffer
.update(cx, |buffer, cx| {
File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
})
.ok()?;
let format_result = prettier
.format(buffer, buffer_path, cx)
.await
.map(FormatOperation::Prettier)
.with_context(|| format!("{} failed to format buffer", prettier_description));
Some(format_result)
}
Err(error) => {
project
.update(cx, |project, _| {
let instance_to_update = match prettier_path {
Some(prettier_path) => {
log::error!(
"Prettier instance from path {prettier_path:?} failed to spawn: {e:#}"
);
project.prettier_instances.get_mut(&prettier_path)
}
None => {
log::error!("Default prettier instance failed to spawn: {e:#}");
match &mut project.default_prettier.prettier {
PrettierInstallation::NotInstalled { .. } => None,
PrettierInstallation::Installed(instance) => Some(instance),
}
}
Some(prettier_path) => project.prettier_instances.get_mut(&prettier_path),
None => match &mut project.default_prettier.prettier {
PrettierInstallation::NotInstalled { .. } => None,
PrettierInstallation::Installed(instance) => Some(instance),
},
};
if let Some(instance) = instance_to_update {
@@ -98,11 +95,14 @@ pub(super) async fn format_with_prettier(
instance.prettier = None;
}
})
.ok()?,
.log_err();
Some(Err(anyhow!(
"{} failed to spawn: {error:#}",
prettier_description
)))
}
}
None
}
pub struct DefaultPrettier {

View File

@@ -4628,10 +4628,11 @@ impl Project {
}
}
(Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => {
if let Some(new_operation) =
prettier_support::format_with_prettier(&project, buffer, &mut cx).await
{
format_operation = Some(new_operation);
let prettier =
prettier_support::format_with_prettier(&project, buffer, &mut cx).await;
if let Some(operation) = prettier {
format_operation = Some(operation?);
} else if let Some((language_server, buffer_abs_path)) = server_and_buffer {
format_operation = Some(FormatOperation::Lsp(
Self::format_via_lsp(
@@ -4648,10 +4649,11 @@ impl Project {
}
}
(Formatter::Prettier, FormatOnSave::On | FormatOnSave::Off) => {
if let Some(new_operation) =
prettier_support::format_with_prettier(&project, buffer, &mut cx).await
{
format_operation = Some(new_operation);
let prettier =
prettier_support::format_with_prettier(&project, buffer, &mut cx).await;
if let Some(operation) = prettier {
format_operation = Some(operation?);
}
}
};

View File

@@ -307,8 +307,8 @@ impl PickerDelegate for TasksModalDelegate {
cx: &mut ViewContext<picker::Picker<Self>>,
) -> Option<Self::ListItem> {
let candidates = self.candidates.as_ref()?;
let hit = &self.matches[ix];
let (source_kind, _) = &candidates[hit.candidate_id];
let hit = &self.matches.get(ix)?;
let (source_kind, _) = &candidates.get(hit.candidate_id)?;
let details = match source_kind {
TaskSourceKind::UserInput => "user input".to_string(),
TaskSourceKind::Buffer => "language extension".to_string(),

View File

@@ -634,6 +634,25 @@ async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
cx.simulate_shared_keystrokes(["g", "g"]).await;
cx.simulate_shared_keystrokes(["5", "d", "j"]).await;
cx.assert_shared_state(indoc! { "ˇ"}).await;
cx.set_shared_state(indoc! { "
fn boop() {
ˇbarp()
bazp()
}
"})
.await;
cx.simulate_shared_keystrokes(["shift-v", "j", "j", "z", "f"])
.await;
cx.simulate_shared_keystrokes(["escape"]).await;
cx.simulate_shared_keystrokes(["shift-g", "shift-v"]).await;
cx.assert_shared_state(indoc! { "
fn boop() {
barp()
bazp()
}
ˇ"})
.await;
}
#[gpui::test]
@@ -1038,12 +1057,3 @@ async fn test_undo(cx: &mut gpui::TestAppContext) {
3"})
.await;
}
#[gpui::test]
async fn test_command_palette(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.simulate_keystroke(":");
cx.simulate_input("go to definition");
assert!(cx.debug_bounds("KEY_BINDING-f12").is_none());
assert!(cx.debug_bounds("KEY_BINDING-g d").is_some());
}

View File

@@ -11,3 +11,13 @@
{"Key":"d"}
{"Key":"j"}
{"Get":{"state":"ˇ","mode":"Normal"}}
{"Put":{"state":"fn boop() {\n ˇbarp()\n bazp()\n}\n"}}
{"Key":"shift-v"}
{"Key":"j"}
{"Key":"j"}
{"Key":"z"}
{"Key":"f"}
{"Key":"escape"}
{"Key":"shift-g"}
{"Key":"shift-v"}
{"Get":{"state":"fn boop() {\n barp()\n bazp()\n}\nˇ","mode":"VisualLine"}}

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.130.0"
version = "0.130.5"
publish = false
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]

View File

@@ -1 +1 @@
dev
stable