Compare commits
17 Commits
update-wor
...
v0.170.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d019ff5b7 | ||
|
|
f10b43651b | ||
|
|
b66f87598a | ||
|
|
24a544ae09 | ||
|
|
43b8788b29 | ||
|
|
27bbc0733d | ||
|
|
5f6cbcedf3 | ||
|
|
b4b2e556bf | ||
|
|
365398a7f1 | ||
|
|
058bdae413 | ||
|
|
a64b75f214 | ||
|
|
c251987d07 | ||
|
|
8a90fa1ff1 | ||
|
|
eadddc6509 | ||
|
|
ea02a95cf0 | ||
|
|
fba2828877 | ||
|
|
ce3752db98 |
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
"bindings": {
|
||||
"alt-tab": "editor::NextInlineCompletion",
|
||||
"alt-shift-tab": "editor::PreviousInlineCompletion",
|
||||
"ctrl-right": "editor::AcceptPartialInlineCompletion"
|
||||
"ctrl-cmd-right": "editor::AcceptPartialInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, |_| {});
|
||||
|
||||
@@ -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)| {
|
||||
|
||||
@@ -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>)]) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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!({
|
||||
|
||||
@@ -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!({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
stable
|
||||
Reference in New Issue
Block a user