Compare commits

...

17 Commits

Author SHA1 Message Date
Zed Bot
2d019ff5b7 Bump to 0.170.3 for @SomeoneToIgnore 2025-01-27 16:16:41 +00:00
gcp-cherry-pick-bot[bot]
f10b43651b Avoid panics when normalizing completion label with invalid ranges (cherry-pick #23712) (#23718)
Cherry-picked Avoid panics when normalizing completion label with
invalid ranges (#23712)

Dev builds show panics related to completion label normalization

<details>
<summary>Panic</summary>

```
index out of bounds: the len is 103 but the index is 103
zed::reliability::init_panic_hook::{{closure}}::h78130eff43c84f6f+110375521
std::panicking::rust_panic_with_hook::hfe205f6954b2c97b+87457752
std::panicking::begin_panic_handler::{{closure}}::h6cb44b3a50f28c44+87456967
std::sys::backtrace::__rust_end_short_backtrace::hf1c1f2a92799bb0e+87449337
rust_begin_unwind+87456084
core::panicking::panic_fmt::h3d8fc78294164da7+7033011
core::panicking::panic_bounds_check::h9397cb495d89a72d+7033511
project::lsp_store::ensure_uniform_list_compatible_label::haf80316ce11edd67+72663592
project::lsp_store::populate_labels_for_completions::{{closure}}::hc93c3c540ef7d2d6+72642960
project::lsp_store::LspStore::completions::{{closure}}::{{closure}}::hb4b5432e24432ca8+72336627
async_task::raw::RawTask<F,T,S,M>::run::hf444c3dc07dd583b+68504803
<gpui::platform::linux::wayland::client::WaylandClient as gpui::platform::linux::platform::LinuxClient>::run::hbf5a316eb781a10d+50646579
gpui::platform::linux::platform::<impl gpui::platform::Platform for P>::run::hc85518d4552fc4cd+50496669
gpui::app::App::run::hca4e2eaf984ca6f6+109905269
zed::main::h849467ac1a6d32c9+110413414
std::sys::backtrace::__rust_begin_short_backtrace::h81b5ee155a7cf505+110835475
std::rt::lang_start::{{closure}}::h48a83f884cfb6865+110834761
std::rt::lang_start_internal::h5e7c81cecd7f0954+87382485
main+110425932
__libc_start_call_main+22789462491720
__libc_start_main_alias_1+22789462491915
_start+10436606
```
</details>

This can only happen when either `label.runs` or `label.filter_range`
has a range that's larger than the label text, which is an error.
Instead of panicking, log such errors and fall back to last index (which
is not really helpful, but still).

Release Notes:

- N/A

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-01-27 17:18:08 +02:00
Peter Tripp
b66f87598a openai: Add back O1-Preview (#23715)
Follow-up to: https://github.com/zed-industries/zed/pull/23425
2025-01-27 09:45:18 -05:00
Marshall Bowers
24a544ae09 Remove unwrap in GitTraversal::synchronize_statuses (#23559)
This is a backport of https://github.com/zed-industries/zed/pull/23555
to Stable, as we couldn't cherry-pick the fix cleanly.

Release Notes:

- Fixed a potential panic in handling of Git statuses.
2025-01-23 12:45:11 -05:00
Zed Bot
43b8788b29 Bump to 0.170.2 for @SomeoneToIgnore 2025-01-23 16:10:26 +00:00
gcp-cherry-pick-bot[bot]
27bbc0733d Fix LSP violation when dismissing server notifications (cherry-pick #23531) (#23535)
Cherry-picked Fix LSP violation when dismissing server notifications
(#23531)

Part of https://github.com/zed-industries/zed/issues/22606
Closes https://github.com/zed-industries/zed/issues/23509

When a user sees an odd notification from the language server like

<img width="508" alt="image"

src="https://github.com/user-attachments/assets/6f5ef1aa-0f09-4705-a02a-aaf81dd8620c"
/>

they usually dismiss that.

Zed uses channels to wait and handle user interactions with such
notifications, and, due to `?`, sends back
```json
{"jsonrpc":"2.0","id":1,"error":{"message":"receiving from an empty and closed channel"}}
```

which is not spec-compliant:

https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showMessageRequest

> Response:
>
> * result: the selected

[MessageActionItem](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#messageActionItem)
| null if none got selected.
> * error: code and message set in case an exception happens during
showing a message.

Unfortunately, vtsls (and, potentially, others) crash if receive such
non-compliant requests, and do not get back.

After the fix, the message is correct:
```json
{"jsonrpc":"2.0","id":1,"result":null}
```


Release Notes:

- Fixed vtsls crashing on notification dismiss

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
2025-01-23 16:13:03 +02:00
Kirill Bulatov
5f6cbcedf3 Omit tsdk_path from the servers' options if it does not exist 2025-01-23 13:10:25 +02:00
Peter Tripp
b4b2e556bf v0.170.x stable 2025-01-22 11:56:05 -05:00
Peter Tripp
365398a7f1 open_ai: Move from o1-preview to o1 for OpenAI Assistant provider (#23425)
- Closes: https://github.com/zed-industries/zed/issues/22521
- Follow-up to: https://github.com/zed-industries/zed/pull/22376
2025-01-21 15:05:36 -05:00
Peter Tripp
058bdae413 ollama: Add deepseek-r1 context size to defaults (#23420) 2025-01-21 14:52:38 -05:00
Peter Tripp
a64b75f214 zed 0.170.1 2025-01-21 12:05:52 -05:00
gcp-cherry-pick-bot[bot]
c251987d07 Fix completion labels becoming overly large due to LSP completion items with newlines (cherry-pick #23407) (#23409)
Cherry-picked Fix completion labels becoming overly large due to LSP
completion items with newlines (#23407)

Reworks https://github.com/zed-industries/zed/pull/23030 and
https://github.com/zed-industries/zed/pull/15087
Closes https://github.com/zed-industries/zed/issues/23352
Closes https://github.com/zed-industries/zed/issues/23310 

Zed's completion items use `label` from LSP completion items as a base
to show in the list:

d290da7dac/crates/project/src/lsp_store.rs (L4371-L4374)

Besides that, certain language plugins append `detail` or
`label_details.description` as a suffix:

d290da7dac/crates/languages/src/vtsls.rs (L178-L188)

Either of these 3 properties may return `\n` (or multiple) in it,
spoiling Zed's completion menu, which uses `UniformList` to render those
items: a uniform list uses common, minimum possible height for each
element, and `\n` bloats that overly.

Good approach would be to use something else:
https://github.com/zed-industries/zed/issues/21403 but that has its own
drawbacks and relatively hard to use instead (?).

We could follow VSCode's approach and move away all but `label` from
`CodeLabel.text` to the side, where the documentation is, but that does
not solve the issue with `details` having newlines.

So, for now, sanitize all labels and remove any newlines from them. If
newlines are found, also replace whitespace sequences if there's more
than 1 in a row.

Later, this approach can be improved similarly to how Helix and Zed's
inline completions do: rendering a "ghost" text, showing the
completion's edit applied to the editor.

Release Notes:

- Fixed completion labels becoming overly large due to LSP completion
items with newlines

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-01-21 18:15:05 +02:00
Peter Tripp
8a90fa1ff1 Fix older Anthropic models not supporting -latest tags (#23372)
- Closes: https://github.com/zed-industries/zed/issues/22322
2025-01-20 13:20:32 -05:00
Thorsten Ball
eadddc6509 keybindings: Fix AcceptPartialInlineCompletion on macOS (#23357)
Related issue: https://github.com/zed-industries/zed/issues/20167

Release Notes:

- Changed the default keybinding to accept partial inline completions
from `ctrl-right` to `ctrl-cmd-right` on macOS, because `ctrl-right` is
already bound to jump to the end of the line.

Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Kirill <kirill@zed.dev>
Co-authored-by: Bennet <bennet@zed.dev>
2025-01-20 14:12:47 +01:00
gcp-cherry-pick-bot[bot]
ea02a95cf0 editor: Hide horizontal scrollbar if not visible (cherry-pick #23337) (#23339)
Cherry-picked editor: Hide horizontal scrollbar if not visible (#23337)

This PR fixes two visual issues, that were caused by the fact that we
were always painting the horizontal scrollbar even if there is no
horizontal scrolling possible

Obscuring deleted lines when using the inline assistant:


https://github.com/user-attachments/assets/f8460c3f-403e-40a6-8622-65268ba2d875

Cutting off text even when horizontal scrolling is not possible:


https://github.com/user-attachments/assets/23c909f7-1c23-4693-8edc-40a2f089d4a8

This issue was only present in some themes (e.g. Nord, Catpuccin)


Closes #22716

Release Notes:

- Fixed an issue where horizontal scrollbars of editors would always be
painted (even if there is no horizontal scrolling to be done)

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2025-01-20 10:48:36 +01:00
gcp-cherry-pick-bot[bot]
fba2828877 Fix accepting partial inline completion (cherry-pick #23312) (#23327)
Cherry-picked Fix accepting partial inline completion (#23312)

Release Notes:

- Fixed a bug that could prevent accepting a partial inline completion.

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2025-01-20 09:40:34 +01:00
Peter Tripp
ce3752db98 v0.170.x preview 2025-01-15 12:51:21 -05:00
28 changed files with 515 additions and 54 deletions

3
Cargo.lock generated
View File

@@ -6663,6 +6663,7 @@ dependencies = [
"async-trait",
"collections",
"extension",
"fs",
"futures 0.3.31",
"gpui",
"language",
@@ -16102,7 +16103,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.170.0"
version = "0.170.3"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -158,7 +158,7 @@
"bindings": {
"alt-tab": "editor::NextInlineCompletion",
"alt-shift-tab": "editor::PreviousInlineCompletion",
"ctrl-right": "editor::AcceptPartialInlineCompletion"
"ctrl-cmd-right": "editor::AcceptPartialInlineCompletion"
}
},
{

View File

@@ -77,8 +77,8 @@ impl Model {
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
Model::Claude3Opus => "claude-3-opus-latest",
Model::Claude3Sonnet => "claude-3-sonnet-latest",
Model::Claude3Haiku => "claude-3-haiku-latest",
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
Model::Claude3Haiku => "claude-3-haiku-20240307",
Self::Custom { name, .. } => name,
}
}

View File

@@ -1204,6 +1204,7 @@ impl InlineAssistant {
editor.set_show_wrap_guides(false, cx);
editor.set_show_gutter(false, cx);
editor.scroll_manager.set_forbid_vertical_scroll(true);
editor.set_show_scrollbars(false, cx);
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
editor.highlight_rows::<DeletedLines>(

View File

@@ -1276,6 +1276,7 @@ impl InlineAssistant {
editor.set_show_wrap_guides(false, cx);
editor.set_show_gutter(false, cx);
editor.scroll_manager.set_forbid_vertical_scroll(true);
editor.set_show_scrollbars(false, cx);
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
editor.highlight_rows::<DeletedLines>(

View File

@@ -4720,8 +4720,19 @@ impl Editor {
});
}
InlineCompletion::Edit(edits) => {
if edits.len() == 1 && edits[0].0.start == edits[0].0.end {
let text = edits[0].1.as_str();
// Find an insertion that starts at the cursor position.
let snapshot = self.buffer.read(cx).snapshot(cx);
let cursor_offset = self.selections.newest::<usize>(cx).head();
let insertion = edits.iter().find_map(|(range, text)| {
let range = range.to_offset(&snapshot);
if range.is_empty() && range.start == cursor_offset {
Some(text)
} else {
None
}
});
if let Some(text) = insertion {
let mut partial_completion = text
.chars()
.by_ref()
@@ -4744,6 +4755,8 @@ impl Editor {
self.refresh_inline_completion(true, true, cx);
cx.notify();
} else {
self.accept_inline_completion(&Default::default(), cx);
}
}
}

View File

@@ -8437,6 +8437,247 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
apply_additional_edits.await.unwrap();
}
#[gpui::test]
async fn test_multiline_completion(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/a",
json!({
"main.ts": "a",
}),
)
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
let typescript_language = Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..LanguageMatcher::default()
},
line_comments: vec!["// ".into()],
..LanguageConfig::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
));
language_registry.add(typescript_language.clone());
let mut fake_servers = language_registry.register_fake_lsp(
"TypeScript",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
..lsp::CompletionOptions::default()
}),
signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
..lsp::ServerCapabilities::default()
},
// Emulate vtsls label generation
label_for_completion: Some(Box::new(|item, _| {
let text = if let Some(description) = item
.label_details
.as_ref()
.and_then(|label_details| label_details.description.as_ref())
{
format!("{} {}", item.label, description)
} else if let Some(detail) = &item.detail {
format!("{} {}", item.label, detail)
} else {
item.label.clone()
};
let len = text.len();
Some(language::CodeLabel {
text,
runs: Vec::new(),
filter_range: 0..len,
})
})),
..FakeLspAdapter::default()
},
);
let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let worktree_id = workspace
.update(cx, |workspace, cx| {
workspace.project().update(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
})
})
.unwrap();
let _buffer = project
.update(cx, |project, cx| {
project.open_local_buffer_with_lsp("/a/main.ts", cx)
})
.await
.unwrap();
let editor = workspace
.update(cx, |workspace, cx| {
workspace.open_path((worktree_id, "main.ts"), None, true, cx)
})
.unwrap()
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let fake_server = fake_servers.next().await.unwrap();
let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
let multiline_label_2 = "a\nb\nc\n";
let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
let multiline_description = "d\ne\nf\n";
let multiline_detail_2 = "g\nh\ni\n";
let mut completion_handle =
fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
Ok(Some(lsp::CompletionResponse::Array(vec![
lsp::CompletionItem {
label: multiline_label.to_string(),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: params.text_document_position.position.line,
character: params.text_document_position.position.character,
},
end: lsp::Position {
line: params.text_document_position.position.line,
character: params.text_document_position.position.character,
},
},
new_text: "new_text_1".to_string(),
})),
..lsp::CompletionItem::default()
},
lsp::CompletionItem {
label: "single line label 1".to_string(),
detail: Some(multiline_detail.to_string()),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: params.text_document_position.position.line,
character: params.text_document_position.position.character,
},
end: lsp::Position {
line: params.text_document_position.position.line,
character: params.text_document_position.position.character,
},
},
new_text: "new_text_2".to_string(),
})),
..lsp::CompletionItem::default()
},
lsp::CompletionItem {
label: "single line label 2".to_string(),
label_details: Some(lsp::CompletionItemLabelDetails {
description: Some(multiline_description.to_string()),
detail: None,
}),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: params.text_document_position.position.line,
character: params.text_document_position.position.character,
},
end: lsp::Position {
line: params.text_document_position.position.line,
character: params.text_document_position.position.character,
},
},
new_text: "new_text_2".to_string(),
})),
..lsp::CompletionItem::default()
},
lsp::CompletionItem {
label: multiline_label_2.to_string(),
detail: Some(multiline_detail_2.to_string()),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: params.text_document_position.position.line,
character: params.text_document_position.position.character,
},
end: lsp::Position {
line: params.text_document_position.position.line,
character: params.text_document_position.position.character,
},
},
new_text: "new_text_3".to_string(),
})),
..lsp::CompletionItem::default()
},
lsp::CompletionItem {
label: "Label with many spaces and \t but without newlines".to_string(),
detail: Some(
"Details with many spaces and \t but without newlines".to_string(),
),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: params.text_document_position.position.line,
character: params.text_document_position.position.character,
},
end: lsp::Position {
line: params.text_document_position.position.line,
character: params.text_document_position.position.character,
},
},
new_text: "new_text_4".to_string(),
})),
..lsp::CompletionItem::default()
},
])))
});
editor.update(cx, |editor, cx| {
editor.focus(cx);
editor.move_to_end(&MoveToEnd, cx);
editor.handle_input(".", cx);
});
cx.run_until_parked();
completion_handle.next().await.unwrap();
editor.update(cx, |editor, _| {
assert!(editor.context_menu_visible());
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
{
let completion_labels = menu
.completions
.borrow()
.iter()
.map(|c| c.label.text.clone())
.collect::<Vec<_>>();
assert_eq!(
completion_labels,
&[
"StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
"single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
"single line label 2 d e f ",
"a b c g h i ",
"Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
],
"Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
);
for completion in menu
.completions
.borrow()
.iter() {
assert_eq!(
completion.label.filter_range,
0..completion.label.text.len(),
"Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
);
}
} else {
panic!("expected completion menu to be open");
}
});
}
#[gpui::test]
async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});

View File

@@ -1333,11 +1333,15 @@ impl EditorElement {
total_text_units
.horizontal
.zip(track_bounds.horizontal)
.map(|(total_text_units_x, track_bounds_x)| {
.and_then(|(total_text_units_x, track_bounds_x)| {
if text_units_per_page.horizontal >= total_text_units_x {
return None;
}
let thumb_percent =
(text_units_per_page.horizontal / total_text_units_x).min(1.);
track_bounds_x.size.width * thumb_percent
Some(track_bounds_x.size.width * thumb_percent)
}),
total_text_units.vertical.zip(track_bounds.vertical).map(
|(total_text_units_y, track_bounds_y)| {

View File

@@ -25,6 +25,7 @@ use crate::language_settings::SoftWrap;
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use collections::{HashMap, HashSet};
use fs::Fs;
use futures::Future;
use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
pub use highlight_map::HighlightMap;
@@ -464,6 +465,7 @@ pub trait LspAdapter: 'static + Send + Sync {
/// Returns initialization options that are going to be sent to a LSP server as a part of [`lsp::InitializeParams`]
async fn initialization_options(
self: Arc<Self>,
_: &dyn Fs,
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<Value>> {
Ok(None)
@@ -471,6 +473,7 @@ pub trait LspAdapter: 'static + Send + Sync {
async fn workspace_configuration(
self: Arc<Self>,
_: &dyn Fs,
_: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_cx: &mut AsyncAppContext,
@@ -763,6 +766,14 @@ pub struct FakeLspAdapter {
pub capabilities: lsp::ServerCapabilities,
pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer)>>,
pub label_for_completion: Option<
Box<
dyn 'static
+ Send
+ Sync
+ Fn(&lsp::CompletionItem, &Arc<Language>) -> Option<CodeLabel>,
>,
>,
}
/// Configuration of handling bracket pairs for a given language.
@@ -1778,6 +1789,7 @@ impl Default for FakeLspAdapter {
arguments: vec![],
env: Default::default(),
},
label_for_completion: None,
}
}
}
@@ -1845,10 +1857,20 @@ impl LspAdapter for FakeLspAdapter {
async fn initialization_options(
self: Arc<Self>,
_: &dyn Fs,
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<Value>> {
Ok(self.initialization_options.clone())
}
async fn label_for_completion(
&self,
item: &lsp::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
let label_for_completion = self.label_for_completion.as_ref()?;
label_for_completion(item, language)
}
}
fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option<u32>)]) {

View File

@@ -17,6 +17,7 @@ async-trait.workspace = true
collections.workspace = true
extension.workspace = true
futures.workspace = true
fs.workspace = true
gpui.workspace = true
language.workspace = true
lsp.workspace = true

View File

@@ -8,6 +8,7 @@ use anyhow::{Context, Result};
use async_trait::async_trait;
use collections::HashMap;
use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate};
use fs::Fs;
use futures::{Future, FutureExt};
use gpui::AsyncAppContext;
use language::{
@@ -224,6 +225,7 @@ impl LspAdapter for ExtensionLspAdapter {
async fn initialization_options(
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
@@ -246,6 +248,7 @@ impl LspAdapter for ExtensionLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_cx: &mut AsyncAppContext,

View File

@@ -81,6 +81,7 @@ impl CloudModel {
| open_ai::Model::FourOmniMini
| open_ai::Model::O1Mini
| open_ai::Model::O1Preview
| open_ai::Model::O1
| open_ai::Model::Custom { .. } => {
LanguageModelAvailability::RequiresPlan(Plan::ZedPro)
}

View File

@@ -362,9 +362,7 @@ pub fn count_open_ai_tokens(
.collect::<Vec<_>>();
match model {
open_ai::Model::Custom { .. }
| open_ai::Model::O1Mini
| open_ai::Model::O1Preview => {
open_ai::Model::Custom { .. } | open_ai::Model::O1Mini | open_ai::Model::O1 => {
tiktoken_rs::num_tokens_from_messages("gpt-4", &messages)
}
_ => tiktoken_rs::num_tokens_from_messages(model.id(), &messages),

View File

@@ -4,6 +4,7 @@ use futures::StreamExt;
use language::{LspAdapter, LspAdapterDelegate};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::NodeRuntime;
use project::Fs;
use serde_json::json;
use smol::fs;
use std::{
@@ -107,6 +108,7 @@ impl LspAdapter for CssLspAdapter {
async fn initialization_options(
self: Arc<Self>,
_: &dyn Fs,
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
Ok(Some(json!({

View File

@@ -6,6 +6,7 @@ use gpui::{AppContext, AsyncAppContext, Task};
use http_client::github::latest_github_release;
pub use language::*;
use lsp::{LanguageServerBinary, LanguageServerName};
use project::Fs;
use regex::Regex;
use serde_json::json;
use smol::fs;
@@ -197,6 +198,7 @@ impl super::LspAdapter for GoLspAdapter {
async fn initialization_options(
self: Arc<Self>,
_: &dyn Fs,
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
Ok(Some(json!({

View File

@@ -9,7 +9,7 @@ use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
use language::{LanguageRegistry, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::NodeRuntime;
use project::{lsp_store::language_server_settings, ContextProviderWithTasks};
use project::{lsp_store::language_server_settings, ContextProviderWithTasks, Fs};
use serde_json::{json, Value};
use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
use smol::{
@@ -208,6 +208,7 @@ impl LspAdapter for JsonLspAdapter {
async fn initialization_options(
self: Arc<Self>,
_: &dyn Fs,
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
Ok(Some(json!({
@@ -217,6 +218,7 @@ impl LspAdapter for JsonLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,

View File

@@ -18,6 +18,7 @@ use pet_core::os_environment::Environment;
use pet_core::python_environment::PythonEnvironmentKind;
use pet_core::Configuration;
use project::lsp_store::language_server_settings;
use project::Fs;
use serde_json::{json, Value};
use smol::lock::OnceCell;
use std::cmp::Ordering;
@@ -250,6 +251,7 @@ impl LspAdapter for PythonLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
_: &dyn Fs,
adapter: &Arc<dyn LspAdapterDelegate>,
toolchains: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,
@@ -931,6 +933,7 @@ impl LspAdapter for PyLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
_: &dyn Fs,
adapter: &Arc<dyn LspAdapterDelegate>,
toolchains: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,

View File

@@ -6,7 +6,7 @@ use gpui::AsyncAppContext;
use language::{LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::NodeRuntime;
use project::lsp_store::language_server_settings;
use project::{lsp_store::language_server_settings, Fs};
use serde_json::{json, Value};
use smol::fs;
use std::{
@@ -116,6 +116,7 @@ impl LspAdapter for TailwindLspAdapter {
async fn initialization_options(
self: Arc<Self>,
_: &dyn Fs,
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
Ok(Some(json!({
@@ -131,6 +132,7 @@ impl LspAdapter for TailwindLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,

View File

@@ -8,8 +8,8 @@ use http_client::github::{build_asset_url, AssetKind, GitHubLspBinaryVersion};
use language::{LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
use node_runtime::NodeRuntime;
use project::lsp_store::language_server_settings;
use project::ContextProviderWithTasks;
use project::{lsp_store::language_server_settings, Fs};
use serde_json::{json, Value};
use smol::{fs, io::BufReader, stream::StreamExt};
use std::{
@@ -77,16 +77,25 @@ impl TypeScriptLspAdapter {
pub fn new(node: NodeRuntime) -> Self {
TypeScriptLspAdapter { node }
}
async fn tsdk_path(adapter: &Arc<dyn LspAdapterDelegate>) -> &'static str {
async fn tsdk_path(fs: &dyn Fs, adapter: &Arc<dyn LspAdapterDelegate>) -> Option<&'static str> {
let is_yarn = adapter
.read_text_file(PathBuf::from(".yarn/sdks/typescript/lib/typescript.js"))
.await
.is_ok();
if is_yarn {
let tsdk_path = if is_yarn {
".yarn/sdks/typescript/lib"
} else {
"node_modules/typescript/lib"
};
if fs
.is_dir(&adapter.worktree_root_path().join(tsdk_path))
.await
{
Some(tsdk_path)
} else {
None
}
}
}
@@ -212,16 +221,14 @@ impl LspAdapter for TypeScriptLspAdapter {
_ => None,
}?;
let one_line = |s: &str| s.replace(" ", "").replace('\n', " ");
let text = if let Some(description) = item
.label_details
.as_ref()
.and_then(|label_details| label_details.description.as_ref())
{
format!("{} {}", item.label, one_line(description))
format!("{} {}", item.label, description)
} else if let Some(detail) = &item.detail {
format!("{} {}", item.label, one_line(detail))
format!("{} {}", item.label, detail)
} else {
item.label.clone()
};
@@ -235,9 +242,10 @@ impl LspAdapter for TypeScriptLspAdapter {
async fn initialization_options(
self: Arc<Self>,
fs: &dyn Fs,
adapter: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
let tsdk_path = Self::tsdk_path(adapter).await;
let tsdk_path = Self::tsdk_path(fs, adapter).await;
Ok(Some(json!({
"provideFormatter": true,
"hostInfo": "zed",
@@ -259,6 +267,7 @@ impl LspAdapter for TypeScriptLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,
@@ -355,6 +364,7 @@ impl LspAdapter for EsLintLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,

View File

@@ -5,7 +5,7 @@ use gpui::AsyncAppContext;
use language::{LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
use node_runtime::NodeRuntime;
use project::lsp_store::language_server_settings;
use project::{lsp_store::language_server_settings, Fs};
use serde_json::Value;
use std::{
any::Any,
@@ -34,16 +34,25 @@ impl VtslsLspAdapter {
VtslsLspAdapter { node }
}
async fn tsdk_path(adapter: &Arc<dyn LspAdapterDelegate>) -> &'static str {
async fn tsdk_path(fs: &dyn Fs, adapter: &Arc<dyn LspAdapterDelegate>) -> Option<&'static str> {
let is_yarn = adapter
.read_text_file(PathBuf::from(".yarn/sdks/typescript/lib/typescript.js"))
.await
.is_ok();
if is_yarn {
let tsdk_path = if is_yarn {
".yarn/sdks/typescript/lib"
} else {
Self::TYPESCRIPT_TSDK_PATH
};
if fs
.is_dir(&adapter.worktree_root_path().join(tsdk_path))
.await
{
Some(tsdk_path)
} else {
None
}
}
}
@@ -175,16 +184,14 @@ impl LspAdapter for VtslsLspAdapter {
_ => None,
}?;
let one_line = |s: &str| s.replace(" ", "").replace('\n', " ");
let text = if let Some(description) = item
.label_details
.as_ref()
.and_then(|label_details| label_details.description.as_ref())
{
format!("{} {}", item.label, one_line(description))
format!("{} {}", item.label, description)
} else if let Some(detail) = &item.detail {
format!("{} {}", item.label, one_line(detail))
format!("{} {}", item.label, detail)
} else {
item.label.clone()
};
@@ -198,11 +205,12 @@ impl LspAdapter for VtslsLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
fs: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
let tsdk_path = Self::tsdk_path(delegate).await;
let tsdk_path = Self::tsdk_path(fs, delegate).await;
let config = serde_json::json!({
"tsdk": tsdk_path,
"suggest": {

View File

@@ -7,7 +7,7 @@ use language::{
};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::NodeRuntime;
use project::lsp_store::language_server_settings;
use project::{lsp_store::language_server_settings, Fs};
use serde_json::Value;
use settings::{Settings, SettingsLocation};
use smol::fs;
@@ -128,6 +128,7 @@ impl LspAdapter for YamlLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,

View File

@@ -84,7 +84,7 @@ fn get_max_tokens(name: &str) -> usize {
"mistral" | "codestral" | "mixstral" | "llava" | "qwen2" | "qwen2.5-coder"
| "dolphin-mixtral" => 32768,
"llama3.1" | "phi3" | "phi3.5" | "phi4" | "command-r" | "deepseek-coder-v2"
| "yi-coder" | "llama3.2" => 128000,
| "deepseek-r1" | "yi-coder" | "llama3.2" => 128000,
_ => DEFAULT_TOKENS,
}
.clamp(1, MAXIMUM_TOKENS)

View File

@@ -72,6 +72,8 @@ pub enum Model {
FourOmni,
#[serde(rename = "gpt-4o-mini", alias = "gpt-4o-mini")]
FourOmniMini,
#[serde(rename = "o1", alias = "o1")]
O1,
#[serde(rename = "o1-preview", alias = "o1-preview")]
O1Preview,
#[serde(rename = "o1-mini", alias = "o1-mini")]
@@ -96,6 +98,7 @@ impl Model {
"gpt-4-turbo-preview" => Ok(Self::FourTurbo),
"gpt-4o" => Ok(Self::FourOmni),
"gpt-4o-mini" => Ok(Self::FourOmniMini),
"o1" => Ok(Self::O1),
"o1-preview" => Ok(Self::O1Preview),
"o1-mini" => Ok(Self::O1Mini),
_ => Err(anyhow!("invalid model id")),
@@ -109,6 +112,7 @@ impl Model {
Self::FourTurbo => "gpt-4-turbo",
Self::FourOmni => "gpt-4o",
Self::FourOmniMini => "gpt-4o-mini",
Self::O1 => "o1",
Self::O1Preview => "o1-preview",
Self::O1Mini => "o1-mini",
Self::Custom { name, .. } => name,
@@ -122,6 +126,7 @@ impl Model {
Self::FourTurbo => "gpt-4-turbo",
Self::FourOmni => "gpt-4o",
Self::FourOmniMini => "gpt-4o-mini",
Self::O1 => "o1",
Self::O1Preview => "o1-preview",
Self::O1Mini => "o1-mini",
Self::Custom {
@@ -137,6 +142,7 @@ impl Model {
Self::FourTurbo => 128000,
Self::FourOmni => 128000,
Self::FourOmniMini => 128000,
Self::O1 => 200000,
Self::O1Preview => 128000,
Self::O1Mini => 128000,
Self::Custom { max_tokens, .. } => *max_tokens,
@@ -475,7 +481,7 @@ pub async fn stream_completion(
api_key: &str,
request: Request,
) -> Result<BoxStream<'static, Result<ResponseStreamEvent>>> {
if request.model == "o1-preview" || request.model == "o1-mini" {
if request.model.starts_with("o1") {
let response = complete(client, api_url, api_key, request).await;
let response_stream_event = response.map(adapt_response_to_stream);
return Ok(stream::once(future::ready(response_stream_event)).boxed());

View File

@@ -245,7 +245,7 @@ impl LocalLspStore {
let language = language.clone();
let key = key.clone();
let adapter = adapter.clone();
let fs = self.fs.clone();
cx.spawn(move |this, mut cx| async move {
let result = {
let delegate = delegate.clone();
@@ -261,13 +261,18 @@ impl LocalLspStore {
let workspace_config = adapter
.adapter
.clone()
.workspace_configuration(&delegate, toolchains.clone(), &mut cx)
.workspace_configuration(
fs.as_ref(),
&delegate,
toolchains.clone(),
&mut cx,
)
.await?;
let mut initialization_options = adapter
.adapter
.clone()
.initialization_options(&(delegate))
.initialization_options(fs.as_ref(), &(delegate))
.await?;
match (&mut initialization_options, override_options) {
@@ -284,7 +289,13 @@ impl LocalLspStore {
adapter.adapter.prepare_initialize_params(params)
})??;
Self::setup_lsp_messages(this.clone(), &language_server, delegate, adapter);
Self::setup_lsp_messages(
this.clone(),
fs,
&language_server,
delegate,
adapter,
);
let did_change_configuration_params =
Arc::new(lsp::DidChangeConfigurationParams {
@@ -496,6 +507,7 @@ impl LocalLspStore {
fn setup_lsp_messages(
this: WeakModel<LspStore>,
fs: Arc<dyn Fs>,
language_server: &LanguageServer,
delegate: Arc<dyn LspAdapterDelegate>,
adapter: Arc<CachedLspAdapter>,
@@ -529,15 +541,17 @@ impl LocalLspStore {
let adapter = adapter.adapter.clone();
let delegate = delegate.clone();
let this = this.clone();
let fs = fs.clone();
move |params, mut cx| {
let adapter = adapter.clone();
let delegate = delegate.clone();
let this = this.clone();
let fs = fs.clone();
async move {
let toolchains =
this.update(&mut cx, |this, cx| this.toolchain_store(cx))?;
let workspace_config = adapter
.workspace_configuration(&delegate, toolchains, &mut cx)
.workspace_configuration(fs.as_ref(), &delegate, toolchains, &mut cx)
.await?;
Ok(params
.items
@@ -843,9 +857,8 @@ impl LocalLspStore {
})
.is_ok();
if did_update {
let response = rx.recv().await?;
Ok(Some(response))
let response = rx.recv().await.ok();
Ok(response)
} else {
Ok(None)
}
@@ -2954,7 +2967,10 @@ impl LspStore {
let _maintain_workspace_config = {
let (sender, receiver) = watch::channel();
(Self::maintain_workspace_config(receiver, cx), sender)
(
Self::maintain_workspace_config(fs.clone(), receiver, cx),
sender,
)
};
Self {
mode: LspStoreMode::Local(LocalLspStore {
@@ -3017,6 +3033,7 @@ impl LspStore {
})
}
#[allow(clippy::too_many_arguments)]
pub(super) fn new_remote(
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
@@ -3024,6 +3041,7 @@ impl LspStore {
languages: Arc<LanguageRegistry>,
upstream_client: AnyProtoClient,
project_id: u64,
fs: Arc<dyn Fs>,
cx: &mut ModelContext<Self>,
) -> Self {
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
@@ -3032,7 +3050,7 @@ impl LspStore {
.detach();
let _maintain_workspace_config = {
let (sender, receiver) = watch::channel();
(Self::maintain_workspace_config(receiver, cx), sender)
(Self::maintain_workspace_config(fs, receiver, cx), sender)
};
Self {
mode: LspStoreMode::Remote(RemoteLspStore {
@@ -4347,7 +4365,7 @@ impl LspStore {
// 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() {
let mut new_label = match snapshot.language() {
Some(language) => {
adapter
.labels_for_completions(&[completion_item.clone()], language)
@@ -4363,6 +4381,7 @@ impl LspStore {
completion_item.filter_text.as_deref(),
)
});
ensure_uniform_list_compatible_label(&mut new_label);
let mut completions = completions.borrow_mut();
let completion = &mut completions[completion_index];
@@ -5124,6 +5143,7 @@ impl LspStore {
pub(crate) async fn refresh_workspace_configurations(
this: &WeakModel<Self>,
fs: Arc<dyn Fs>,
mut cx: AsyncAppContext,
) {
maybe!(async move {
@@ -5170,7 +5190,12 @@ impl LspStore {
.ok()?;
for (adapter, server, delegate) in servers {
let settings = adapter
.workspace_configuration(&delegate, toolchain_store.clone(), &mut cx)
.workspace_configuration(
fs.as_ref(),
&delegate,
toolchain_store.clone(),
&mut cx,
)
.await
.ok()?;
@@ -5193,6 +5218,7 @@ impl LspStore {
}
}
fn maintain_workspace_config(
fs: Arc<dyn Fs>,
external_refresh_requests: watch::Receiver<()>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
@@ -5207,7 +5233,7 @@ impl LspStore {
futures::stream::select(settings_changed_rx, external_refresh_requests);
cx.spawn(move |this, cx| async move {
while let Some(()) = joint_future.next().await {
Self::refresh_workspace_configurations(&this, cx.clone()).await;
Self::refresh_workspace_configurations(&this, fs.clone(), cx.clone()).await;
}
drop(settings_observation);
@@ -7970,15 +7996,18 @@ async fn populate_labels_for_completions(
None
};
let mut label = label.unwrap_or_else(|| {
CodeLabel::plain(
lsp_completion.label.clone(),
lsp_completion.filter_text.as_deref(),
)
});
ensure_uniform_list_compatible_label(&mut label);
completions.push(Completion {
old_range: completion.old_range,
new_text: completion.new_text,
label: label.unwrap_or_else(|| {
CodeLabel::plain(
lsp_completion.label.clone(),
lsp_completion.filter_text.as_deref(),
)
}),
label,
server_id: completion.server_id,
documentation,
lsp_completion,
@@ -8366,6 +8395,7 @@ impl LspAdapter for SshLspAdapter {
async fn initialization_options(
self: Arc<Self>,
_: &dyn Fs,
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
let Some(options) = &self.initialization_options else {
@@ -8707,6 +8737,113 @@ fn include_text(server: &lsp::LanguageServer) -> Option<bool> {
}
}
/// Completion items are displayed in a `UniformList`.
/// Usually, those items are single-line strings, but in LSP responses,
/// completion items `label`, `detail` and `label_details.description` may contain newlines or long spaces.
/// Many language plugins construct these items by joining these parts together, and we may fall back to `CodeLabel::plain` that uses `label`.
/// All that may lead to a newline being inserted into resulting `CodeLabel.text`, which will force `UniformList` to bloat each entry to occupy more space,
/// breaking the completions menu presentation.
///
/// Sanitize the text to ensure there are no newlines, or, if there are some, remove them and also remove long space sequences if there were newlines.
fn ensure_uniform_list_compatible_label(label: &mut CodeLabel) {
let mut new_text = String::with_capacity(label.text.len());
let mut offset_map = vec![0; label.text.len() + 1];
let mut last_char_was_space = false;
let mut new_idx = 0;
let mut chars = label.text.char_indices().fuse();
let mut newlines_removed = false;
while let Some((idx, c)) = chars.next() {
offset_map[idx] = new_idx;
match c {
'\n' if last_char_was_space => {
newlines_removed = true;
}
'\t' | ' ' if last_char_was_space => {}
'\n' if !last_char_was_space => {
new_text.push(' ');
new_idx += 1;
last_char_was_space = true;
newlines_removed = true;
}
' ' | '\t' => {
new_text.push(' ');
new_idx += 1;
last_char_was_space = true;
}
_ => {
new_text.push(c);
new_idx += 1;
last_char_was_space = false;
}
}
}
offset_map[label.text.len()] = new_idx;
// Only modify the label if newlines were removed.
if !newlines_removed {
return;
}
let last_index = new_idx;
let mut run_ranges_errors = Vec::new();
label.runs.retain_mut(|(range, _)| {
match offset_map.get(range.start) {
Some(&start) => range.start = start,
None => {
run_ranges_errors.push(range.clone());
return false;
}
}
match offset_map.get(range.end) {
Some(&end) => range.end = end,
None => {
run_ranges_errors.push(range.clone());
range.end = last_index;
}
}
true
});
if !run_ranges_errors.is_empty() {
log::error!(
"Completion label has errors in its run ranges: {run_ranges_errors:?}, label text: {}",
label.text
);
}
let mut wrong_filter_range = None;
if label.filter_range == (0..label.text.len()) {
label.filter_range = 0..new_text.len();
} else {
let mut original_filter_range = Some(label.filter_range.clone());
match offset_map.get(label.filter_range.start) {
Some(&start) => label.filter_range.start = start,
None => {
wrong_filter_range = original_filter_range.take();
label.filter_range.start = last_index;
}
}
match offset_map.get(label.filter_range.end) {
Some(&end) => label.filter_range.end = end,
None => {
wrong_filter_range = original_filter_range.take();
label.filter_range.end = last_index;
}
}
}
if let Some(wrong_filter_range) = wrong_filter_range {
log::error!(
"Completion label has an invalid filter range: {wrong_filter_range:?}, label text: {}",
label.text
);
}
label.text = new_text;
}
#[cfg(test)]
#[test]
fn test_glob_literal_prefix() {

View File

@@ -800,6 +800,7 @@ impl Project {
languages.clone(),
ssh_proto.clone(),
SSH_PROJECT_ID,
fs.clone(),
cx,
)
});
@@ -972,6 +973,7 @@ impl Project {
languages.clone(),
client.clone().into(),
remote_id,
fs.clone(),
cx,
);
lsp_store.set_language_server_statuses_from_proto(response.payload.language_servers);

View File

@@ -5794,7 +5794,7 @@ impl<'a> GitTraversal<'a> {
} else if entry.is_file() {
// For a file entry, park the cursor on the corresponding status
if statuses.seek_forward(&PathTarget::Path(repo_path.as_ref()), Bias::Left, &()) {
self.current_entry_status = Some(statuses.item().unwrap().combined_status());
self.current_entry_status = statuses.item().map(|item| item.combined_status());
}
}
}

View File

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

View File

@@ -1 +1 @@
dev
stable