Compare commits

...

10 Commits

Author SHA1 Message Date
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
18 changed files with 377 additions and 37 deletions

2
Cargo.lock generated
View File

@@ -16102,7 +16102,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.170.0"
version = "0.170.1"
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

@@ -763,6 +763,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 +1786,7 @@ impl Default for FakeLspAdapter {
arguments: vec![],
env: Default::default(),
},
label_for_completion: None,
}
}
}
@@ -1849,6 +1858,15 @@ impl LspAdapter for FakeLspAdapter {
) -> 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

@@ -80,7 +80,7 @@ impl CloudModel {
| open_ai::Model::FourOmni
| 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

@@ -212,16 +212,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()
};

View File

@@ -175,16 +175,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()
};

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,8 +72,8 @@ pub enum Model {
FourOmni,
#[serde(rename = "gpt-4o-mini", alias = "gpt-4o-mini")]
FourOmniMini,
#[serde(rename = "o1-preview", alias = "o1-preview")]
O1Preview,
#[serde(rename = "o1", alias = "o1-preview")]
O1,
#[serde(rename = "o1-mini", alias = "o1-mini")]
O1Mini,
@@ -96,7 +96,7 @@ impl Model {
"gpt-4-turbo-preview" => Ok(Self::FourTurbo),
"gpt-4o" => Ok(Self::FourOmni),
"gpt-4o-mini" => Ok(Self::FourOmniMini),
"o1-preview" => Ok(Self::O1Preview),
"o1" => Ok(Self::O1),
"o1-mini" => Ok(Self::O1Mini),
_ => Err(anyhow!("invalid model id")),
}
@@ -109,7 +109,7 @@ impl Model {
Self::FourTurbo => "gpt-4-turbo",
Self::FourOmni => "gpt-4o",
Self::FourOmniMini => "gpt-4o-mini",
Self::O1Preview => "o1-preview",
Self::O1 => "o1",
Self::O1Mini => "o1-mini",
Self::Custom { name, .. } => name,
}
@@ -122,7 +122,7 @@ impl Model {
Self::FourTurbo => "gpt-4-turbo",
Self::FourOmni => "gpt-4o",
Self::FourOmniMini => "gpt-4o-mini",
Self::O1Preview => "o1-preview",
Self::O1 => "o1",
Self::O1Mini => "o1-mini",
Self::Custom {
name, display_name, ..
@@ -137,7 +137,7 @@ impl Model {
Self::FourTurbo => 128000,
Self::FourOmni => 128000,
Self::FourOmniMini => 128000,
Self::O1Preview => 128000,
Self::O1 => 128000,
Self::O1Mini => 128000,
Self::Custom { max_tokens, .. } => *max_tokens,
}
@@ -475,7 +475,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

@@ -4347,7 +4347,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 +4363,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];
@@ -7970,15 +7971,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,
@@ -8707,6 +8711,70 @@ 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;
}
for (range, _) in &mut label.runs {
range.start = offset_map[range.start];
range.end = offset_map[range.end];
}
if label.filter_range == (0..label.text.len()) {
label.filter_range = 0..new_text.len();
} else {
label.filter_range.start = offset_map[label.filter_range.start];
label.filter_range.end = offset_map[label.filter_range.end];
}
label.text = new_text;
}
#[cfg(test)]
#[test]
fn test_glob_literal_prefix() {

View File

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

View File

@@ -1 +1 @@
dev
stable