Compare commits

...

12 Commits

Author SHA1 Message Date
Peter Tripp
a2264c3b75 v0.167.x stable 2025-01-01 12:23:33 -05:00
gcp-cherry-pick-bot[bot]
78e1512c12 Revert "Invalidate tooltips when mouse leaves element's hitbox (#22488)" (cherry-pick #22542) (#22544)
Cherry-picked Revert "Invalidate tooltips when mouse leaves element's
hitbox (#22488)" (#22542)

This reverts commit 344284e013.

That change broke git blame tooltips, as Zed should also show tooltips
which are hovered, even though the mouse had left the origin element's
bounds.

Release Notes:

- N/A

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-01-01 19:13:23 +02:00
gcp-cherry-pick-bot[bot]
3ca88f3a56 Deduplicate edits from WorkspaceEdit LSP responses (cherry-pick #22512) (#22514)
Cherry-picked Deduplicate edits from WorkspaceEdit LSP responses
(#22512)

Closes https://github.com/zed-industries/zed/issues/21515

Release Notes:

- Fixed zls renames applying duplicate edits

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2024-12-31 03:08:49 +02:00
gcp-cherry-pick-bot[bot]
68fe03ae0e Ensure zls is used for Zig as a primary language server (cherry-pick #22511) (#22513)
Cherry-picked Ensure zls is used for Zig as a primary language server
(#22511)

Part of https://github.com/zed-industries/zed/issues/22415

I've noticed that I cannot work with any Zig projects, as there were no
"go to definition", formatting and inlay hints.

After debugging, I've discovered that `typos` was registered as a first
language server, becoming the "primary" one for Zig.
That one does not have any proper capabilities, hence all corresponding
LSP requests were no-op.

While this solution is not ideal (I wonder, how many other set-ups are
broken due to the same thing?), we'd better fix things for now this way
at least.

Release Notes:

- Fixed `zls` not working properly when `typos` extension is installed

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2024-12-31 03:01:28 +02:00
gcp-cherry-pick-bot[bot]
b8ad3a56f7 Invalidate tooltips when mouse leaves element's hitbox (cherry-pick #22488) (#22489)
Cherry-picked Invalidate tooltips when mouse leaves element's hitbox
(#22488)

Closes https://github.com/zed-industries/zed/issues/21657

In case of the task rerun button tooltip from



f6dabadaf7/crates/terminal_view/src/terminal_view.rs (L1051-L1070)

, the actual button element is not styled as invisible, only its parent.
Zed won't render such element since it's parent is hidden, but will
consider it "visible" all the time its `paint` is called, spawning a
task with the delay, that will create the tooltip:



f6dabadaf7/crates/gpui/src/elements/div.rs (L1949-L1959)

When the parent is hidden, the child won't be painted anymore, and no
mouse listeners will be able to detect this fact and hide the tooltip.

Hence, check such cases separately, during `prepaint`, and invalidate
the tooltips that are not valid anymore.
We cannot use `hitbox.is_hovered(cx)` as it's not really hovered during
prepaint, so a mouse position check is used instead.

Release Notes:

- Fixed tooltips getting stuck

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2024-12-29 22:39:19 +02:00
Kirill Bulatov
600923b901 Fix Project strong reference leaks (#22470)
Closes https://github.com/zed-industries/zed/issues/21906

* After https://github.com/zed-industries/zed/pull/21238,
`TerminalPanel` and `Project` strong references were moved into
`Pane`-related closures, creating a cycle, that did not allow
registering project release and shutting down corresponding language
servers

* After https://github.com/zed-industries/zed/pull/22329, a special
`Editor` was created with a strong reference to the `Project` which
seemed to do nothing bad in general, but when a working Zed was running
a Zed Dev build, had the same issue with preventing language servers
from shutting down.

The latter is very odd, and seems quite dangerous, as any arbitrary
`Editor` with `Project` in it may do the same, yet it seems that we did
not store them before the way git panel does.

I have tried creating a test, yet seems that we need to initialize a lot
of Zed for it which I failed — all my attempts resulted in a single
language server being present in the `Project`'s statuses.

Release Notes:

- Fixed language servers not being released between project reopens
2024-12-28 19:46:44 +02:00
Marcel Kersten
36018ff2dc copilot: Update Copilot Chat to o1 GA model version (#22376)
Closes #22375

Release Notes:

- Fixed model version of o1 in GitHub Copilot Chat

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-12-27 13:31:16 -05:00
Kirill Bulatov
ab02944e48 Resolve completion items once exactly (#22448)
Closes https://github.com/zed-industries/zed/issues/19214
Closes https://github.com/zed-industries/zed/pull/22443

Adds `resolved` property into Zed completion item data, to ensure we
resolve every completion item exactly once.

There are 2 paths for singplayer Zed, and corresponding 2 analogues for
multi player code, where resolve may happen:
* completions menu display & selection, that ends up using
`resolve_completions` in `lsp_store.rs`
* applying a completion menu entry, that ends up using
`apply_additional_edits_for_completion` in `lsp_store.rs`

Now, all local counterparts check `enabled` field before resolving and
set it to true afterwards, and reuse the same `resolve_completion_local`
method for resolving the items.

A logic for re-generating docs and item labels was moved out from the
`resolve_completion_local` method into a separate method, as
`apply_additional_edits_for_completion` does not need that, but needs
the rest of the logic for resolving.
During the extraction, I've noted that multiplayer clients are not
getting the item labels, regenerated after the resolve — as the Zed
protocol-based flow is not the exact copy of the local resolving.
To improve that, `resolve_completion_remote` needs to be adjusted, but
this change is omitted to avoid bloating the PR.

Release Notes:

- Fixed autocomplete inserting multiple imports
2024-12-27 18:57:05 +02:00
Piotr Osiewicz
b9d1431e17 terminal: Clear output after venv is activated (#22256)
The command used to activate the venv can still be accessed/scrolled to
if needed.

Release Notes:

- The Python virtual environment activation command is no longer shown
in the terminal output by default.

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-12-19 13:17:08 -05:00
Peter Tripp
d60635b84a zed 0.167.1 2024-12-19 13:05:34 -05:00
Thorsten Ball
0e288bf358 Fix project environment not working correctly with multiple worktrees (#22246)
Fixes https://github.com/zed-industries/zed/issues/21972

This fixes two bugs:

**Bug 1**: this bug caused us to only ever load a single environment in
a multi-worktree project, thanks to this line:

```rust
if let Some(task) = self.get_environment_task.as_ref()
```

We'd only ever run a single task per project, which is wrong.

What does code does is to cache the tasks per `worktree_id`, which means
we don't even need to cache the environments again, since we can just
cache the `Shared<Task<...>>`.

**Bug 2**: we assumed that every `worktree_abs_path` is a directory,
which lead to `Failed to run direnv` log messages when opening a project
that had a worktree with a single file open (easy to reproduce: open a
normal project, open your settings, close Zed, reopen it — the settings
faile caused environments to not load)

It's fixed by checking whether the `worktree_abs_path` is an absolute
directory. Since this is always running locally, it's fine to use
`smol::fs` here instead of using our `Fs`.

Release Notes:

- Fixed shell environments not being loaded properly to be used by
language servers and terminals in case a project had multiple worktrees.
- Fixed `Failed to run direnv` messages showing up in case Zed restored
a window that contained a worktree with a single file.
https://github.com/zed-industries/zed/issues/21972
2024-12-19 13:05:03 -05:00
Peter Tripp
a8776f8a5c v0.167.x preview 2024-12-18 11:41:34 -05:00
18 changed files with 576 additions and 312 deletions

2
Cargo.lock generated
View File

@@ -16000,7 +16000,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.167.0"
version = "0.167.1"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -1101,6 +1101,9 @@
"prettier": {
"allowed": true
}
},
"Zig": {
"language_servers": ["zls", "..."]
}
},
// Different settings for specific language models.

View File

@@ -149,6 +149,7 @@ impl SlashCommandCompletionProvider {
server_id: LanguageServerId(0),
lsp_completion: Default::default(),
confirm,
resolved: true,
})
})
.collect()
@@ -242,6 +243,7 @@ impl SlashCommandCompletionProvider {
server_id: LanguageServerId(0),
lsp_completion: Default::default(),
confirm,
resolved: true,
}
})
.collect())
@@ -330,16 +332,6 @@ impl CompletionProvider for SlashCommandCompletionProvider {
Task::ready(Ok(true))
}
fn apply_additional_edits_for_completion(
&self,
_: Model<Buffer>,
_: project::Completion,
_: bool,
_: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
}
fn is_completion_trigger(
&self,
buffer: &Model<Buffer>,

View File

@@ -79,16 +79,6 @@ impl CompletionProvider for MessageEditorCompletionProvider {
Task::ready(Ok(false))
}
fn apply_additional_edits_for_completion(
&self,
_buffer: Model<Buffer>,
_completion: Completion,
_push_to_history: bool,
_cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
}
fn is_completion_trigger(
&self,
_buffer: &Model<Buffer>,
@@ -319,6 +309,7 @@ impl MessageEditor {
server_id: LanguageServerId(0), // TODO: Make this optional or something?
lsp_completion: Default::default(), // TODO: Make this optional or something?
confirm: None,
resolved: true,
}
})
.collect()

View File

@@ -34,9 +34,9 @@ pub enum Model {
Gpt4,
#[serde(alias = "gpt-3.5-turbo", rename = "gpt-3.5-turbo")]
Gpt3_5Turbo,
#[serde(alias = "o1-preview", rename = "o1-preview-2024-09-12")]
#[serde(alias = "o1-preview", rename = "o1")]
O1Preview,
#[serde(alias = "o1-mini", rename = "o1-mini-2024-09-12")]
#[serde(alias = "o1-mini", rename = "o1-mini")]
O1Mini,
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")]
Claude3_5Sonnet,

View File

@@ -217,6 +217,7 @@ impl CompletionsMenu {
documentation: None,
lsp_completion: Default::default(),
confirm: None,
resolved: true,
})
.collect();

View File

@@ -3828,8 +3828,11 @@ impl Editor {
};
let buffer_handle = completions_menu.buffer;
let completions = completions_menu.completions.borrow_mut();
let completion = completions.get(mat.candidate_id)?;
let completion = completions_menu
.completions
.borrow()
.get(mat.candidate_id)?
.clone();
cx.stop_propagation();
let snippet;
@@ -3973,9 +3976,11 @@ impl Editor {
}
let provider = self.completion_provider.as_ref()?;
drop(completion);
let apply_edits = provider.apply_additional_edits_for_completion(
buffer_handle,
completion.clone(),
completions_menu.completions.clone(),
mat.candidate_id,
true,
cx,
);
@@ -5098,7 +5103,7 @@ impl Editor {
}))
}
#[cfg(feature = "test-support")]
#[cfg(any(feature = "test-support", test))]
pub fn context_menu_visible(&self) -> bool {
self.context_menu
.borrow()
@@ -13433,11 +13438,14 @@ pub trait CompletionProvider {
fn apply_additional_edits_for_completion(
&self,
buffer: Model<Buffer>,
completion: Completion,
push_to_history: bool,
cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>>;
_buffer: Model<Buffer>,
_completions: Rc<RefCell<Box<[Completion]>>>,
_completion_index: usize,
_push_to_history: bool,
_cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
}
fn is_completion_trigger(
&self,
@@ -13596,6 +13604,7 @@ fn snippet_completions(
Some(Completion {
old_range: range,
new_text: snippet.body.clone(),
resolved: false,
label: CodeLabel {
text: matching_prefix.clone(),
runs: vec![],
@@ -13661,19 +13670,30 @@ impl CompletionProvider for Model<Project> {
cx: &mut ViewContext<Editor>,
) -> Task<Result<bool>> {
self.update(cx, |project, cx| {
project.resolve_completions(buffer, completion_indices, completions, cx)
project.lsp_store().update(cx, |lsp_store, cx| {
lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
})
})
}
fn apply_additional_edits_for_completion(
&self,
buffer: Model<Buffer>,
completion: Completion,
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
push_to_history: bool,
cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
self.update(cx, |project, cx| {
project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx)
project.lsp_store().update(cx, |lsp_store, cx| {
lsp_store.apply_additional_edits_for_completion(
buffer,
completions,
completion_index,
push_to_history,
cx,
)
})
})
}

View File

@@ -8398,7 +8398,6 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
additional edit
"});
handle_resolve_completion_request(&mut cx, None).await;
apply_additional_edits.await.unwrap();
update_test_language_settings(&mut cx, |settings| {
@@ -10694,10 +10693,14 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
..lsp::CompletionItem::default()
};
cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
let item1 = item1.clone();
cx.handle_request::<lsp::request::Completion, _, _>({
let item1 = item1.clone();
let item2 = item2.clone();
async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
move |_, _, _| {
let item1 = item1.clone();
let item2 = item2.clone();
async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
}
})
.next()
.await;
@@ -10724,43 +10727,41 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
}
});
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
Ok(lsp::CompletionItem {
label: "method id()".to_string(),
filter_text: Some("id".to_string()),
detail: Some("Now resolved!".to_string()),
documentation: Some(lsp::Documentation::String("Docs".to_string())),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
})
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
let item1 = item1.clone();
move |_, item_to_resolve, _| {
let item1 = item1.clone();
async move {
if item1 == item_to_resolve {
Ok(lsp::CompletionItem {
label: "method id()".to_string(),
filter_text: Some("id".to_string()),
detail: Some("Now resolved!".to_string()),
documentation: Some(lsp::Documentation::String("Docs".to_string())),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(0, 22),
lsp::Position::new(0, 22),
),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
})
} else {
Ok(item_to_resolve)
}
}
}
})
.next()
.await;
.await
.unwrap();
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_next(&Default::default(), cx);
});
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
Ok(lsp::CompletionItem {
label: "invalid changed label".to_string(),
detail: Some("Now resolved!".to_string()),
documentation: Some(lsp::Documentation::String("Docs".to_string())),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
})
})
.next()
.await;
cx.run_until_parked();
cx.update_editor(|editor, _| {
let context_menu = editor.context_menu.borrow_mut();
let context_menu = context_menu
@@ -10783,6 +10784,172 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
});
}
#[gpui::test]
async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
resolve_provider: Some(true),
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
cx.simulate_keystroke(".");
let unresolved_item_1 = lsp::CompletionItem {
label: "id".to_string(),
filter_text: Some("id".to_string()),
detail: None,
documentation: None,
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
};
let resolved_item_1 = lsp::CompletionItem {
additional_text_edits: Some(vec![lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
new_text: "!!".to_string(),
}]),
..unresolved_item_1.clone()
};
let unresolved_item_2 = lsp::CompletionItem {
label: "other".to_string(),
filter_text: Some("other".to_string()),
detail: None,
documentation: None,
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".other".to_string(),
})),
..lsp::CompletionItem::default()
};
let resolved_item_2 = lsp::CompletionItem {
additional_text_edits: Some(vec![lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
new_text: "??".to_string(),
}]),
..unresolved_item_2.clone()
};
let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
cx.lsp
.server
.on_request::<lsp::request::ResolveCompletionItem, _, _>({
let unresolved_item_1 = unresolved_item_1.clone();
let resolved_item_1 = resolved_item_1.clone();
let unresolved_item_2 = unresolved_item_2.clone();
let resolved_item_2 = resolved_item_2.clone();
let resolve_requests_1 = resolve_requests_1.clone();
let resolve_requests_2 = resolve_requests_2.clone();
move |unresolved_request, _| {
let unresolved_item_1 = unresolved_item_1.clone();
let resolved_item_1 = resolved_item_1.clone();
let unresolved_item_2 = unresolved_item_2.clone();
let resolved_item_2 = resolved_item_2.clone();
let resolve_requests_1 = resolve_requests_1.clone();
let resolve_requests_2 = resolve_requests_2.clone();
async move {
if unresolved_request == unresolved_item_1 {
resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
Ok(resolved_item_1.clone())
} else if unresolved_request == unresolved_item_2 {
resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
Ok(resolved_item_2.clone())
} else {
panic!("Unexpected completion item {unresolved_request:?}")
}
}
}
})
.detach();
cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
let unresolved_item_1 = unresolved_item_1.clone();
let unresolved_item_2 = unresolved_item_2.clone();
async move {
Ok(Some(lsp::CompletionResponse::Array(vec![
unresolved_item_1,
unresolved_item_2,
])))
}
})
.next()
.await;
cx.condition(|editor, _| editor.context_menu_visible())
.await;
cx.update_editor(|editor, _| {
let context_menu = editor.context_menu.borrow_mut();
let context_menu = context_menu
.as_ref()
.expect("Should have the context menu deployed");
match context_menu {
CodeContextMenu::Completions(completions_menu) => {
let completions = completions_menu.completions.borrow_mut();
assert_eq!(
completions
.iter()
.map(|completion| &completion.label.text)
.collect::<Vec<_>>(),
vec!["id", "other"]
)
}
CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
}
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_next(&ContextMenuNext, cx);
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_prev(&ContextMenuPrev, cx);
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_next(&ContextMenuNext, cx);
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor
.compose_completion(&ComposeCompletion::default(), cx)
.expect("No task returned")
})
.await
.expect("Completion failed");
cx.run_until_parked();
cx.update_editor(|editor, cx| {
assert_eq!(
resolve_requests_1.load(atomic::Ordering::Acquire),
1,
"Should always resolve once despite multiple selections"
);
assert_eq!(
resolve_requests_2.load(atomic::Ordering::Acquire),
1,
"Should always resolve once after multiple selections and applying the completion"
);
assert_eq!(
editor.text(cx),
"fn main() { let a = ??.other; }",
"Should use resolved data when applying the completion"
);
});
}
#[gpui::test]
async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -10978,8 +11145,8 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
cx.run_until_parked();
assert_eq!(
resolve_requests_number.load(atomic::Ordering::Acquire),
2,
"After re-selecting the first item, another resolve request should have been sent"
1,
"After re-selecting the first item, no new resolve requests should be sent"
);
expect_first_item.store(false, atomic::Ordering::Release);
@@ -10989,7 +11156,7 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
cx.run_until_parked();
assert_eq!(
resolve_requests_number.load(atomic::Ordering::Acquire),
3,
2,
"After selecting the other item, another resolve request should have been sent"
);
}
@@ -11129,7 +11296,7 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
},
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
)));
update_test_language_settings(cx, |settings| {
settings.defaults.prettier = Some(PrettierSettings {
@@ -14601,6 +14768,62 @@ fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
}
}
#[gpui::test]
async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
cx.set_state(indoc! {"
struct Fˇoo {}
"});
cx.update_editor(|editor, cx| {
let highlight_range = Point::new(0, 7)..Point::new(0, 10);
let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
editor.highlight_background::<DocumentHighlightRead>(
&[highlight_range],
|c| c.editor_document_highlight_read_background,
cx,
);
});
cx.update_editor(|e, cx| e.rename(&Rename, cx))
.expect("Rename was not started")
.await
.expect("Rename failed");
let mut rename_handler =
cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
let edit = lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: 0,
character: 7,
},
end: lsp::Position {
line: 0,
character: 10,
},
},
new_text: "FooRenamed".to_string(),
};
Ok(Some(lsp::WorkspaceEdit::new(
// Specify the same edit twice
std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
)))
});
cx.update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
.expect("Confirm rename was not started")
.await
.expect("Confirm rename failed");
rename_handler.next().await.unwrap();
cx.run_until_parked();
// Despite two edits, only one is actually applied as those are identical
cx.assert_editor_state(indoc! {"
struct FooRenamedˇ {}
"});
}
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
point..point

View File

@@ -284,6 +284,7 @@ impl<F: Future> LspRequestFuture<F::Output> for LspRequest<F> {
}
/// Combined capabilities of the server and the adapter.
#[derive(Debug)]
pub struct AdapterServerCapabilities {
// Reported capabilities by the server
pub server_capabilities: ServerCapabilities,

View File

@@ -14,8 +14,7 @@ use crate::{
pub struct ProjectEnvironment {
cli_environment: Option<HashMap<String, String>>,
get_environment_task: Option<Shared<Task<Option<HashMap<String, String>>>>>,
cached_shell_environments: HashMap<WorktreeId, HashMap<String, String>>,
environments: HashMap<WorktreeId, Shared<Task<Option<HashMap<String, String>>>>>,
environment_error_messages: HashMap<WorktreeId, EnvironmentErrorMessage>,
}
@@ -35,27 +34,15 @@ impl ProjectEnvironment {
Self {
cli_environment,
get_environment_task: None,
cached_shell_environments: Default::default(),
environments: Default::default(),
environment_error_messages: Default::default(),
}
})
}
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn set_cached(
&mut self,
shell_environments: &[(WorktreeId, HashMap<String, String>)],
) {
self.cached_shell_environments = shell_environments
.iter()
.cloned()
.collect::<HashMap<_, _>>();
}
pub(crate) fn remove_worktree_environment(&mut self, worktree_id: WorktreeId) {
self.cached_shell_environments.remove(&worktree_id);
self.environment_error_messages.remove(&worktree_id);
self.environments.remove(&worktree_id);
}
/// Returns the inherited CLI environment, if this project was opened from the Zed CLI.
@@ -91,96 +78,83 @@ impl ProjectEnvironment {
worktree_abs_path: Option<Arc<Path>>,
cx: &ModelContext<Self>,
) -> Shared<Task<Option<HashMap<String, String>>>> {
if let Some(task) = self.get_environment_task.as_ref() {
if cfg!(any(test, feature = "test-support")) {
return Task::ready(Some(HashMap::default())).shared();
}
if let Some(cli_environment) = self.get_cli_environment() {
return cx
.spawn(|_, _| async move {
let path = cli_environment
.get("PATH")
.map(|path| path.as_str())
.unwrap_or_default();
log::info!(
"using project environment variables from CLI. PATH={:?}",
path
);
Some(cli_environment)
})
.shared();
}
let Some((worktree_id, worktree_abs_path)) = worktree_id.zip(worktree_abs_path) else {
return Task::ready(None).shared();
};
if let Some(task) = self.environments.get(&worktree_id) {
task.clone()
} else {
let task = self
.build_environment_task(worktree_id, worktree_abs_path, cx)
.get_worktree_env(worktree_id, worktree_abs_path, cx)
.shared();
self.get_environment_task = Some(task.clone());
self.environments.insert(worktree_id, task.clone());
task
}
}
fn build_environment_task(
&mut self,
worktree_id: Option<WorktreeId>,
worktree_abs_path: Option<Arc<Path>>,
cx: &ModelContext<Self>,
) -> Task<Option<HashMap<String, String>>> {
let worktree = worktree_id.zip(worktree_abs_path);
let cli_environment = self.get_cli_environment();
if let Some(environment) = cli_environment {
cx.spawn(|_, _| async move {
let path = environment
.get("PATH")
.map(|path| path.as_str())
.unwrap_or_default();
log::info!(
"using project environment variables from CLI. PATH={:?}",
path
);
Some(environment)
})
} else if let Some((worktree_id, worktree_abs_path)) = worktree {
self.get_worktree_env(worktree_id, worktree_abs_path, cx)
} else {
Task::ready(None)
}
}
fn get_worktree_env(
&mut self,
worktree_id: WorktreeId,
worktree_abs_path: Arc<Path>,
cx: &ModelContext<Self>,
) -> Task<Option<HashMap<String, String>>> {
let cached_env = self.cached_shell_environments.get(&worktree_id).cloned();
if let Some(env) = cached_env {
Task::ready(Some(env))
} else {
let load_direnv = ProjectSettings::get_global(cx).load_direnv.clone();
let load_direnv = ProjectSettings::get_global(cx).load_direnv.clone();
cx.spawn(|this, mut cx| async move {
let (mut shell_env, error_message) = cx
.background_executor()
.spawn({
let cwd = worktree_abs_path.clone();
async move { load_shell_environment(&cwd, &load_direnv).await }
})
.await;
cx.spawn(|this, mut cx| async move {
let (mut shell_env, error_message) = cx
.background_executor()
.spawn({
let worktree_abs_path = worktree_abs_path.clone();
async move {
load_worktree_shell_environment(&worktree_abs_path, &load_direnv).await
}
})
.await;
if let Some(shell_env) = shell_env.as_mut() {
let path = shell_env
.get("PATH")
.map(|path| path.as_str())
.unwrap_or_default();
log::info!(
"using project environment variables shell launched in {:?}. PATH={:?}",
worktree_abs_path,
path
);
this.update(&mut cx, |this, _| {
this.cached_shell_environments
.insert(worktree_id, shell_env.clone());
})
.log_err();
if let Some(shell_env) = shell_env.as_mut() {
let path = shell_env
.get("PATH")
.map(|path| path.as_str())
.unwrap_or_default();
log::info!(
"using project environment variables shell launched in {:?}. PATH={:?}",
worktree_abs_path,
path
);
set_origin_marker(shell_env, EnvironmentOrigin::WorktreeShell);
}
set_origin_marker(shell_env, EnvironmentOrigin::WorktreeShell);
}
if let Some(error) = error_message {
this.update(&mut cx, |this, _| {
this.environment_error_messages.insert(worktree_id, error);
})
.log_err();
}
if let Some(error) = error_message {
this.update(&mut cx, |this, _| {
this.environment_error_messages.insert(worktree_id, error);
})
.log_err();
}
shell_env
})
}
shell_env
})
}
}
@@ -213,6 +187,42 @@ impl EnvironmentErrorMessage {
}
}
async fn load_worktree_shell_environment(
worktree_abs_path: &Path,
load_direnv: &DirenvSettings,
) -> (
Option<HashMap<String, String>>,
Option<EnvironmentErrorMessage>,
) {
match smol::fs::metadata(worktree_abs_path).await {
Ok(meta) => {
let dir = if meta.is_dir() {
worktree_abs_path
} else if let Some(parent) = worktree_abs_path.parent() {
parent
} else {
return (
None,
Some(EnvironmentErrorMessage(format!(
"Failed to load shell environment in {}: not a directory",
worktree_abs_path.display()
))),
);
};
load_shell_environment(&dir, load_direnv).await
}
Err(err) => (
None,
Some(EnvironmentErrorMessage(format!(
"Failed to load shell environment in {}: {}",
worktree_abs_path.display(),
err
))),
),
}
}
#[cfg(any(test, feature = "test-support"))]
async fn load_shell_environment(
_dir: &Path,

View File

@@ -1918,6 +1918,7 @@ impl LspCommand for GetCompletions {
new_text,
server_id,
lsp_completion,
resolved: false,
}
})
.collect())

View File

@@ -2353,8 +2353,16 @@ impl LocalLspStore {
let (mut edits, mut snippet_edits) = (vec![], vec![]);
for edit in op.edits {
match edit {
Edit::Plain(edit) => edits.push(edit),
Edit::Annotated(edit) => edits.push(edit.text_edit),
Edit::Plain(edit) => {
if !edits.contains(&edit) {
edits.push(edit)
}
}
Edit::Annotated(edit) => {
if !edits.contains(&edit.text_edit) {
edits.push(edit.text_edit)
}
}
Edit::Snippet(edit) => {
let Ok(snippet) = Snippet::parse(&edit.snippet.value)
else {
@@ -2365,10 +2373,13 @@ impl LocalLspStore {
snippet_edits.push((edit.range, snippet));
} else {
// Since this buffer is not focused, apply a normal edit.
edits.push(TextEdit {
let new_edit = TextEdit {
range: edit.range,
new_text: snippet.text,
});
};
if !edits.contains(&new_edit) {
edits.push(new_edit);
}
}
}
}
@@ -4152,38 +4163,27 @@ impl LspStore {
let mut did_resolve = false;
if let Some((client, project_id)) = client {
for completion_index in completion_indices {
let (server_id, completion) = {
let completions = completions.borrow_mut();
let completion = &completions[completion_index];
did_resolve = true;
let server_id = completion.server_id;
let completion = completion.lsp_completion.clone();
let server_id = completions.borrow()[completion_index].server_id;
(server_id, completion)
};
Self::resolve_completion_remote(
if Self::resolve_completion_remote(
project_id,
server_id,
buffer_id,
completions.clone(),
completion_index,
completion,
client.clone(),
language_registry.clone(),
)
.await;
.await
.log_err()
.is_some()
{
did_resolve = true;
}
}
} else {
for completion_index in completion_indices {
let (server_id, completion) = {
let completions = completions.borrow_mut();
let completion = &completions[completion_index];
let server_id = completion.server_id;
let completion = completion.lsp_completion.clone();
(server_id, completion)
};
let server_id = completions.borrow()[completion_index].server_id;
let server_and_adapter = this
.read_with(&cx, |lsp_store, _| {
@@ -4198,17 +4198,27 @@ impl LspStore {
continue;
};
did_resolve = true;
Self::resolve_completion_local(
let resolved = Self::resolve_completion_local(
server,
adapter,
&buffer_snapshot,
completions.clone(),
completion_index,
completion,
language_registry.clone(),
)
.await;
.await
.log_err()
.is_some();
if resolved {
Self::regenerate_completion_labels(
adapter,
&buffer_snapshot,
completions.clone(),
completion_index,
language_registry.clone(),
)
.await
.log_err();
did_resolve = true;
}
}
}
@@ -4218,13 +4228,10 @@ impl LspStore {
async fn resolve_completion_local(
server: Arc<lsp::LanguageServer>,
adapter: Arc<CachedLspAdapter>,
snapshot: &BufferSnapshot,
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
completion: lsp::CompletionItem,
language_registry: Arc<LanguageRegistry>,
) {
) -> Result<()> {
let can_resolve = server
.capabilities()
.completion_provider
@@ -4232,30 +4239,17 @@ impl LspStore {
.and_then(|options| options.resolve_provider)
.unwrap_or(false);
if !can_resolve {
return;
return Ok(());
}
let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
let Some(completion_item) = request.await.log_err() else {
return;
let request = {
let completion = &completions.borrow()[completion_index];
if completion.resolved {
return Ok(());
}
server.request::<lsp::request::ResolveCompletionItem>(completion.lsp_completion.clone())
};
if let Some(lsp_documentation) = completion_item.documentation.as_ref() {
let documentation = language::prepare_completion_documentation(
lsp_documentation,
&language_registry,
snapshot.language().cloned(),
)
.await;
let mut completions = completions.borrow_mut();
let completion = &mut completions[completion_index];
completion.documentation = Some(documentation);
} else {
let mut completions = completions.borrow_mut();
let completion = &mut completions[completion_index];
completion.documentation = Some(Documentation::Undocumented);
}
let completion_item = request.await?;
if let Some(text_edit) = completion_item.text_edit.as_ref() {
// Technically we don't have to parse the whole `text_edit`, since the only
@@ -4283,28 +4277,61 @@ impl LspStore {
}
}
let mut completions = completions.borrow_mut();
let completion = &mut completions[completion_index];
completion.lsp_completion = completion_item;
completion.resolved = true;
Ok(())
}
async fn regenerate_completion_labels(
adapter: Arc<CachedLspAdapter>,
snapshot: &BufferSnapshot,
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
language_registry: Arc<LanguageRegistry>,
) -> Result<()> {
let completion_item = completions.borrow()[completion_index]
.lsp_completion
.clone();
if let Some(lsp_documentation) = completion_item.documentation.as_ref() {
let documentation = language::prepare_completion_documentation(
lsp_documentation,
&language_registry,
snapshot.language().cloned(),
)
.await;
let mut completions = completions.borrow_mut();
let completion = &mut completions[completion_index];
completion.documentation = Some(documentation);
} else {
let mut completions = completions.borrow_mut();
let completion = &mut completions[completion_index];
completion.documentation = Some(Documentation::Undocumented);
}
// NB: Zed does not have `details` inside the completion resolve capabilities, but certain language servers violate the spec and do not return `details` immediately, e.g. https://github.com/yioneko/vtsls/issues/213
// So we have to update the label here anyway...
let new_label = match snapshot.language() {
Some(language) => adapter
.labels_for_completions(&[completion_item.clone()], language)
.await
.log_err()
.unwrap_or_default(),
Some(language) => {
adapter
.labels_for_completions(&[completion_item.clone()], language)
.await?
}
None => Vec::new(),
}
.pop()
.flatten()
.unwrap_or_else(|| {
CodeLabel::plain(
completion_item.label.clone(),
completion_item.label,
completion_item.filter_text.as_deref(),
)
});
let mut completions = completions.borrow_mut();
let completion = &mut completions[completion_index];
completion.lsp_completion = completion_item;
if completion.label.filter_text() == new_label.filter_text() {
completion.label = new_label;
} else {
@@ -4317,6 +4344,8 @@ impl LspStore {
new_label.filter_text()
);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
@@ -4326,29 +4355,30 @@ impl LspStore {
buffer_id: BufferId,
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
completion: lsp::CompletionItem,
client: AnyProtoClient,
language_registry: Arc<LanguageRegistry>,
) {
) -> Result<()> {
let lsp_completion = {
let completion = &completions.borrow()[completion_index];
if completion.resolved {
return Ok(());
}
serde_json::to_string(&completion.lsp_completion)
.unwrap()
.into_bytes()
};
let request = proto::ResolveCompletionDocumentation {
project_id,
language_server_id: server_id.0 as u64,
lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
lsp_completion,
buffer_id: buffer_id.into(),
};
let Some(response) = client
let response = client
.request(request)
.await
.context("completion documentation resolve proto request")
.log_err()
else {
return;
};
let Some(lsp_completion) = serde_json::from_slice(&response.lsp_completion).log_err()
else {
return;
};
.context("completion documentation resolve proto request")?;
let lsp_completion = serde_json::from_slice(&response.lsp_completion)?;
let documentation = if response.documentation.is_empty() {
Documentation::Undocumented
@@ -4366,6 +4396,7 @@ impl LspStore {
let completion = &mut completions[completion_index];
completion.documentation = Some(documentation);
completion.lsp_completion = lsp_completion;
completion.resolved = true;
let old_range = response
.old_start
@@ -4377,12 +4408,15 @@ impl LspStore {
completion.old_range = old_start..old_end;
}
}
Ok(())
}
pub fn apply_additional_edits_for_completion(
&self,
buffer_handle: Model<Buffer>,
completion: Completion,
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
push_to_history: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Transaction>>> {
@@ -4391,8 +4425,9 @@ impl LspStore {
if let Some((client, project_id)) = self.upstream_client() {
cx.spawn(move |_, mut cx| async move {
let response = client
.request(proto::ApplyCompletionAdditionalEdits {
let request = {
let completion = completions.borrow()[completion_index].clone();
proto::ApplyCompletionAdditionalEdits {
project_id,
buffer_id: buffer_id.into(),
completion: Some(Self::serialize_completion(&CoreCompletion {
@@ -4400,9 +4435,13 @@ impl LspStore {
new_text: completion.new_text,
server_id: completion.server_id,
lsp_completion: completion.lsp_completion,
resolved: completion.resolved,
})),
})
.await?;
}
};
let response = client.request(request).await?;
completions.borrow_mut()[completion_index].resolved = true;
if let Some(transaction) = response.transaction {
let transaction = language::proto::deserialize_transaction(transaction)?;
@@ -4422,34 +4461,31 @@ impl LspStore {
}
})
} else {
let server_id = completion.server_id;
let lang_server = match self.language_server_for_local_buffer(buffer, server_id, cx) {
let server_id = completions.borrow()[completion_index].server_id;
let server = match self.language_server_for_local_buffer(buffer, server_id, cx) {
Some((_, server)) => server.clone(),
_ => return Task::ready(Ok(Default::default())),
_ => return Task::ready(Ok(None)),
};
let snapshot = buffer_handle.read(&cx).snapshot();
cx.spawn(move |this, mut cx| async move {
let can_resolve = lang_server
.capabilities()
.completion_provider
.as_ref()
.and_then(|options| options.resolve_provider)
.unwrap_or(false);
let additional_text_edits = if can_resolve {
lang_server
.request::<lsp::request::ResolveCompletionItem>(completion.lsp_completion)
.await?
.additional_text_edits
} else {
completion.lsp_completion.additional_text_edits
};
Self::resolve_completion_local(
server.clone(),
&snapshot,
completions.clone(),
completion_index,
)
.await
.context("resolving completion")?;
let completion = completions.borrow()[completion_index].clone();
let additional_text_edits = completion.lsp_completion.additional_text_edits;
if let Some(edits) = additional_text_edits {
let edits = this
.update(&mut cx, |this, cx| {
this.as_local_mut().unwrap().edits_from_lsp(
&buffer_handle,
edits,
lang_server.server_id(),
server.server_id(),
None,
cx,
)
@@ -6803,7 +6839,7 @@ impl LspStore {
let apply_additional_edits = this.update(&mut cx, |this, cx| {
this.apply_additional_edits_for_completion(
buffer,
Completion {
Rc::new(RefCell::new(Box::new([Completion {
old_range: completion.old_range,
new_text: completion.new_text,
lsp_completion: completion.lsp_completion,
@@ -6815,7 +6851,9 @@ impl LspStore {
filter_range: Default::default(),
},
confirm: None,
},
resolved: completion.resolved,
}]))),
0,
false,
cx,
)
@@ -7780,6 +7818,7 @@ impl LspStore {
new_text: completion.new_text.clone(),
server_id: completion.server_id.0 as u64,
lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(),
resolved: completion.resolved,
}
}
@@ -7799,6 +7838,7 @@ impl LspStore {
new_text: completion.new_text,
server_id: LanguageServerId(completion.server_id as usize),
lsp_completion,
resolved: completion.resolved,
})
}
@@ -7900,6 +7940,7 @@ async fn populate_labels_for_completions(
documentation,
lsp_completion,
confirm: None,
resolved: false,
})
}
}

View File

@@ -73,10 +73,8 @@ use snippet::Snippet;
use snippet_provider::SnippetProvider;
use std::{
borrow::Cow,
cell::RefCell,
ops::Range,
path::{Component, Path, PathBuf},
rc::Rc,
str,
sync::Arc,
time::Duration,
@@ -353,6 +351,8 @@ pub struct Completion {
pub documentation: Option<Documentation>,
/// The raw completion provided by the language server.
pub lsp_completion: lsp::CompletionItem,
/// Whether this completion has been resolved, to ensure it happens once per completion.
pub resolved: bool,
/// An optional callback to invoke when this completion is confirmed.
/// Returns, whether new completions should be retriggered after the current one.
/// If `true` is returned, the editor will show a new completion menu after this completion is confirmed.
@@ -380,6 +380,7 @@ pub(crate) struct CoreCompletion {
new_text: String,
server_id: LanguageServerId,
lsp_completion: lsp::CompletionItem,
resolved: bool,
}
/// A code action provided by a language server.
@@ -1207,13 +1208,6 @@ impl Project {
.await
.unwrap();
project.update(cx, |project, cx| {
let tree_id = tree.read(cx).id();
project.environment.update(cx, |environment, _| {
environment.set_cached(&[(tree_id, HashMap::default())])
});
});
tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
.await;
}
@@ -2870,35 +2864,6 @@ impl Project {
})
}
pub fn resolve_completions(
&self,
buffer: Model<Buffer>,
completion_indices: Vec<usize>,
completions: Rc<RefCell<Box<[Completion]>>>,
cx: &mut ModelContext<Self>,
) -> Task<Result<bool>> {
self.lsp_store.update(cx, |lsp_store, cx| {
lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
})
}
pub fn apply_additional_edits_for_completion(
&self,
buffer_handle: Model<Buffer>,
completion: Completion,
push_to_history: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Transaction>>> {
self.lsp_store.update(cx, |lsp_store, cx| {
lsp_store.apply_additional_edits_for_completion(
buffer_handle,
completion,
push_to_history,
cx,
)
})
}
pub fn code_actions<T: Clone + ToOffset>(
&mut self,
buffer_handle: &Model<Buffer>,

View File

@@ -433,7 +433,10 @@ impl Project {
"windows" => "\r",
_ => "\n",
};
Some(format!("{} {}{}", activate_keyword, quoted, line_ending))
Some(format!(
"{} {} ; clear{}",
activate_keyword, quoted, line_ending
))
}
fn activate_python_virtual_environment(
@@ -450,7 +453,7 @@ impl Project {
}
}
pub fn wrap_for_ssh(
fn wrap_for_ssh(
ssh_command: &SshCommand,
command: Option<(&String, &Vec<String>)>,
path: Option<&Path>,

View File

@@ -927,6 +927,7 @@ message Completion {
string new_text = 3;
uint64 server_id = 4;
bytes lsp_completion = 5;
bool resolved = 6;
}
message GetCodeActions {

View File

@@ -964,19 +964,23 @@ pub fn new_terminal_pane(
pane.set_should_display_tab_bar(|_| true);
pane.set_zoom_out_on_close(false);
let terminal_panel_for_split_check = terminal_panel.clone();
let split_closure_terminal_panel = terminal_panel.downgrade();
pane.set_can_split(Some(Arc::new(move |pane, dragged_item, cx| {
if let Some(tab) = dragged_item.downcast_ref::<DraggedTab>() {
let current_pane = cx.view().clone();
let can_drag_away =
terminal_panel_for_split_check.update(cx, |terminal_panel, _| {
let is_current_pane = &tab.pane == cx.view();
let Some(can_drag_away) = split_closure_terminal_panel
.update(cx, |terminal_panel, _| {
let current_panes = terminal_panel.center.panes();
!current_panes.contains(&&tab.pane)
|| current_panes.len() > 1
|| (tab.pane != current_pane || pane.items_len() > 1)
});
|| (!is_current_pane || pane.items_len() > 1)
})
.ok()
else {
return false;
};
if can_drag_away {
let item = if tab.pane == current_pane {
let item = if is_current_pane {
pane.item_for_index(tab.ix)
} else {
tab.pane.read(cx).item_for_index(tab.ix)
@@ -996,7 +1000,12 @@ pub fn new_terminal_pane(
toolbar.add_item(breadcrumbs, cx);
});
let drop_closure_project = project.downgrade();
let drop_closure_terminal_panel = terminal_panel.downgrade();
pane.set_custom_drop_handle(cx, move |pane, dropped_item, cx| {
let Some(project) = drop_closure_project.upgrade() else {
return ControlFlow::Break(());
};
if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>() {
let this_pane = cx.view().clone();
let item = if tab.pane == this_pane {
@@ -1009,10 +1018,10 @@ pub fn new_terminal_pane(
let source = tab.pane.clone();
let item_id_to_move = item.item_id();
let new_split_pane = pane
let Ok(new_split_pane) = pane
.drag_split_direction()
.map(|split_direction| {
terminal_panel.update(cx, |terminal_panel, cx| {
drop_closure_terminal_panel.update(cx, |terminal_panel, cx| {
let is_zoomed = if terminal_panel.active_pane == this_pane {
pane.is_zoomed()
} else {
@@ -1033,9 +1042,12 @@ pub fn new_terminal_pane(
anyhow::Ok(new_pane)
})
})
.transpose();
.transpose()
else {
return ControlFlow::Break(());
};
match new_split_pane {
match new_split_pane.transpose() {
// Source pane may be the one currently updated, so defer the move.
Ok(Some(new_pane)) => cx
.spawn(|_, mut cx| async move {

View File

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

View File

@@ -1 +1 @@
dev
stable