Compare commits

...

13 Commits

Author SHA1 Message Date
Peter Tripp
25e2e9c672 zed 0.154.2 2024-09-25 17:39:51 -04:00
Sebastijan Kelnerič
b2ee628360 Implement grapheme support for supermaven completions (#18279)
Closes [#18278](https://github.com/zed-industries/zed/issues/18278)

Release Notes:

- Fixed a panic when graphemes are included in supermaven completions
2024-09-25 17:39:02 -04:00
Thorsten Ball
3ea89a1f9c client: Remove unused fs dependency (#18324)
CI bot notified me about that in
https://github.com/zed-industries/zed/pull/18323


Release Notes:

- N/A
2024-09-25 13:04:25 -04:00
Joseph T Lyons
752f6a7c33 v0.154.x stable 2024-09-25 11:02:25 -04:00
Max Brunsfeld
1e2fcfb386 Fix unnecessarily-specific struct pattern in rust outline query (#18297)
Fixes https://github.com/zed-industries/zed/issues/18294

Release Notes:

- Fixed a recent regression where tuple and unit structs were omitted
from the outline view in Rust (#18294).
2024-09-24 12:19:53 -07:00
Thorsten Ball
3ef8491fcb zed 0.154.1 2024-09-24 15:12:29 +02:00
Thorsten Ball
ce3a6350e6 project search: Fix search results not being highlighted (#18273)
Closes #18254
Closes #18219
Closes #17690

This fixes the project search not highlighting all results.

The problem was relatively simple, even though it took a while to find
it: we inserted multiple excerpts concurrently and the order in the
multi-buffer ended up being wrong. Sorting the resulting `match_ranges`
fixed the problem, but as it turns out, we can do a better job by moving
the concurrency into the method on the MultiBuffer.

Performance is the same, but now the problem is fixed.

Release Notes:

- Fixed search results in project-wide search not being highlighted
consistently and navigation sometimes being broken (#18254, #18219,

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-09-24 15:07:20 +02:00
Joseph T. Lyons
15931d1b77 Bump release_notes to v2 endpoint (#18108)
Partially addresses https://github.com/zed-industries/zed/issues/17527

<img width="1608" alt="SCR-20240919-rcik"
src="https://github.com/user-attachments/assets/25057731-7da6-4b36-b51b-021c67e8736b">

Release Notes:

- Enhanced the `auto update: view release notes locally` feature to
display release notes for each patch version associated with the
installed minor version.
2024-09-24 09:02:19 -04:00
Joseph T. Lyons
2a5925c235 Fix bug where copying from assistant panel appends extra newline to clipboard (#18090)
Closes https://github.com/zed-industries/zed/issues/17661

Release Notes:

- Fixed a bug where copying from the assistant panel appended an
additional newline to the end of the clipboard contents.
2024-09-24 09:02:08 -04:00
Kirill Bulatov
153fb9eab8 Properly use default search options in the buffer search bar (#18271)
Only replace current search options if the search was dismissed and the
new options are different from the default ones.

Follow-up of https://github.com/zed-industries/zed/pull/17179
Closes https://github.com/zed-industries/zed/issues/18166


Release Notes:

- Fixed buffer search options toggling unexpectedly on redeploys
([#18166](https://github.com/zed-industries/zed/issues/18166))
2024-09-24 15:39:45 +03:00
Thorsten Ball
2330f0d2f9 vtsls: Move all default configuration to workspace_configuration (#18259)
This fixes https://github.com/zed-industries/zed/issues/18014 by fixing
the regression that was introduced in
https://github.com/zed-industries/zed/pull/17757.

In short: after digging into the `vtsls` code, it looks like it
essentially doesn't need any `initialization_options`, it's all
workspace configuration, since it tries to use the built-in settings
from VS Code.

I tested the completions, the inlay hints, the max memory - all of it
now works after moving to `workspace_configuration`.

Closes #18014.

Release Notes:

- Fixed `vtsls` being initialized the wrong way, which would mean the
wrong options were used to enable completions or inlay hints.
2024-09-24 14:35:40 +02:00
gcp-cherry-pick-bot[bot]
76b7c1df43 editor: Improve rewrapping when working with comments at different indentation levels (cherry-pick #18146) (#18147)
Cherry-picked editor: Improve rewrapping when working with comments at
different indentation levels (#18146)

This PR improves the `editor::Rewrap` command when working with comments
that were not all at the same indentation level.

We now use a heuristic of finding the most common indentation level for
each line, using the deepest indent in the event of a tie.

It also removes an `.unwrap()` that would previously lead to a panic in
this case. Instead of unwrapping we now log an error to the logs and
skip rewrapping for that selection.

Release Notes:

- Improved the behavior of `editor: rewrap` when working with a
selection that contained comments at different indentation levels.

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-20 11:55:27 -04:00
Joseph T Lyons
a0a1b1c69b v0.154.x preview 2024-09-19 11:02:30 -04:00
17 changed files with 535 additions and 196 deletions

4
Cargo.lock generated
View File

@@ -2408,7 +2408,6 @@ dependencies = [
"cocoa 0.26.0",
"collections",
"feature_flags",
"fs",
"futures 0.3.30",
"gpui",
"http_client",
@@ -10994,6 +10993,7 @@ dependencies = [
"text",
"theme",
"ui",
"unicode-segmentation",
"util",
"windows 0.58.0",
]
@@ -14375,7 +14375,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.154.0"
version = "0.154.2"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -3533,7 +3533,9 @@ impl ContextEditor {
for chunk in context.buffer().read(cx).text_for_range(range) {
text.push_str(chunk);
}
text.push('\n');
if message.offset_range.end < selection.range().end {
text.push('\n');
}
}
}
}

View File

@@ -268,7 +268,7 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
let client = client::Client::global(cx).http_client();
let url = client.build_url(&format!(
"/api/release_notes/{}/{}",
"/api/release_notes/v2/{}/{}",
release_channel.dev_name(),
version
));

View File

@@ -23,7 +23,6 @@ chrono = { workspace = true, features = ["serde"] }
clock.workspace = true
collections.workspace = true
feature_flags.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
http_client.workspace = true

View File

@@ -6743,9 +6743,31 @@ impl Editor {
}
}
let row = selection.head().row;
let indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
let indent_end = Point::new(row, indent_size.len);
// Since not all lines in the selection may be at the same indent
// level, choose the indent size that is the most common between all
// of the lines.
//
// If there is a tie, we use the deepest indent.
let (indent_size, indent_end) = {
let mut indent_size_occurrences = HashMap::default();
let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
for row in start_row..=end_row {
let indent = buffer.indent_size_for_line(MultiBufferRow(row));
rows_by_indent_size.entry(indent).or_default().push(row);
*indent_size_occurrences.entry(indent).or_insert(0) += 1;
}
let indent_size = indent_size_occurrences
.into_iter()
.max_by_key(|(indent, count)| (*count, indent.len))
.map(|(indent, _)| indent)
.unwrap_or_default();
let row = rows_by_indent_size[&indent_size][0];
let indent_end = Point::new(row, indent_size.len);
(indent_size, indent_end)
};
let mut line_prefix = indent_size.chars().collect::<String>();
@@ -6795,10 +6817,22 @@ impl Editor {
let start = Point::new(start_row, 0);
let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
let selection_text = buffer.text_for_range(start..end).collect::<String>();
let unwrapped_text = selection_text
let Some(lines_without_prefixes) = selection_text
.lines()
.map(|line| line.strip_prefix(&line_prefix).unwrap())
.join(" ");
.map(|line| {
line.strip_prefix(&line_prefix)
.or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
.ok_or_else(|| {
anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
})
})
.collect::<Result<Vec<_>, _>>()
.log_err()
else {
continue;
};
let unwrapped_text = lines_without_prefixes.join(" ");
let wrap_column = buffer
.settings_at(Point::new(start_row, 0), cx)
.preferred_line_length as usize;

View File

@@ -4249,6 +4249,80 @@ async fn test_rewrap(cx: &mut TestAppContext) {
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
cx.assert_editor_state(wrapped_text);
}
// Test rewrapping unaligned comments in a selection.
{
let language = Arc::new(Language::new(
LanguageConfig {
line_comments: vec!["// ".into(), "/// ".into()],
..LanguageConfig::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
let unwrapped_text = indoc! {"
fn foo() {
if true {
« // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
// Praesent semper egestas tellus id dignissim.ˇ»
do_something();
} else {
//
}
}
"};
let wrapped_text = indoc! {"
fn foo() {
if true {
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
// mollis elit purus, a ornare lacus gravida vitae. Praesent semper
// egestas tellus id dignissim.ˇ
do_something();
} else {
//
}
}
"};
cx.set_state(unwrapped_text);
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
cx.assert_editor_state(wrapped_text);
let unwrapped_text = indoc! {"
fn foo() {
if true {
«ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
// Praesent semper egestas tellus id dignissim.»
do_something();
} else {
//
}
}
"};
let wrapped_text = indoc! {"
fn foo() {
if true {
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
// mollis elit purus, a ornare lacus gravida vitae. Praesent semper
// egestas tellus id dignissim.ˇ
do_something();
} else {
//
}
}
"};
cx.set_state(unwrapped_text);
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
cx.assert_editor_state(wrapped_text);
}
}
#[gpui::test]

View File

@@ -144,7 +144,7 @@ pub struct BufferSnapshot {
/// The kind and amount of indentation in a particular line. For now,
/// assumes that indentation is all the same character.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct IndentSize {
/// The number of bytes that comprise the indentation.
pub len: u32,
@@ -153,7 +153,7 @@ pub struct IndentSize {
}
/// A whitespace character that's used for indentation.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum IndentKind {
/// An ASCII space character.
#[default]

View File

@@ -4,8 +4,7 @@
(struct_item
(visibility_modifier)? @context
"struct" @context
name: (_) @name
body: (_ "{" @open (_)* "}" @close)) @item
name: (_) @name) @item
(enum_item
(visibility_modifier)? @context

View File

@@ -6,14 +6,14 @@ use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::{CodeActionKind, LanguageServerBinary};
use node_runtime::NodeRuntime;
use project::{lsp_store::language_server_settings, project_settings::BinarySettings};
use serde_json::{json, Value};
use serde_json::Value;
use std::{
any::Any,
ffi::OsString,
path::{Path, PathBuf},
sync::Arc,
};
use util::{maybe, ResultExt};
use util::{maybe, merge_json_value_into, ResultExt};
fn typescript_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![server_path.into(), "--stdio".into()]
@@ -212,11 +212,12 @@ impl LspAdapter for VtslsLspAdapter {
})
}
async fn initialization_options(
async fn workspace_configuration(
self: Arc<Self>,
adapter: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
let tsdk_path = Self::tsdk_path(adapter).await;
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
let tsdk_path = Self::tsdk_path(delegate).await;
let config = serde_json::json!({
"tsdk": tsdk_path,
"suggest": {
@@ -243,10 +244,13 @@ impl LspAdapter for VtslsLspAdapter {
"enumMemberValues": {
"enabled": true
}
}
},
"tsserver": {
"maxTsServerMemory": 8092
},
});
Ok(Some(json!({
let mut default_workspace_configuration = serde_json::json!({
"typescript": config,
"javascript": config,
"vtsls": {
@@ -258,33 +262,18 @@ impl LspAdapter for VtslsLspAdapter {
},
"autoUseWorkspaceTsdk": true
}
})))
}
});
async fn workspace_configuration(
self: Arc<Self>,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
let override_options = cx.update(|cx| {
language_server_settings(delegate.as_ref(), SERVER_NAME, cx)
.and_then(|s| s.settings.clone())
})?;
if let Some(options) = override_options {
return Ok(options);
if let Some(override_options) = override_options {
merge_json_value_into(override_options, &mut default_workspace_configuration)
}
let config = serde_json::json!({
"tsserver": {
"maxTsServerMemory": 8092
},
});
Ok(serde_json::json!({
"typescript": config,
"javascript": config
}))
Ok(default_workspace_configuration)
}
fn language_ids(&self) -> HashMap<String, String> {

View File

@@ -6,7 +6,7 @@ use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet};
use futures::{channel::mpsc, SinkExt};
use git::diff::DiffHunk;
use gpui::{AppContext, EntityId, EventEmitter, Model, ModelContext};
use gpui::{AppContext, EntityId, EventEmitter, Model, ModelContext, Task};
use itertools::Itertools;
use language::{
language_settings::{language_settings, LanguageSettings},
@@ -1106,64 +1106,24 @@ impl MultiBuffer {
}
}
pub fn stream_excerpts_with_context_lines(
pub fn forget_transaction(
&mut self,
buffer: Model<Buffer>,
ranges: Vec<Range<text::Anchor>>,
context_line_count: u32,
transaction_id: TransactionId,
cx: &mut ModelContext<Self>,
) -> mpsc::Receiver<Range<Anchor>> {
let (buffer_id, buffer_snapshot) =
buffer.update(cx, |buffer, _| (buffer.remote_id(), buffer.snapshot()));
let (mut tx, rx) = mpsc::channel(256);
cx.spawn(move |this, mut cx| async move {
let mut excerpt_ranges = Vec::new();
let mut range_counts = Vec::new();
cx.background_executor()
.scoped(|scope| {
scope.spawn(async {
let (ranges, counts) =
build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count);
excerpt_ranges = ranges;
range_counts = counts;
) {
if let Some(buffer) = self.as_singleton() {
buffer.update(cx, |buffer, _| {
buffer.forget_transaction(transaction_id);
});
} else if let Some(transaction) = self.history.forget(transaction_id) {
for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions {
if let Some(state) = self.buffers.borrow_mut().get_mut(&buffer_id) {
state.buffer.update(cx, |buffer, _| {
buffer.forget_transaction(buffer_transaction_id);
});
})
.await;
let mut ranges = ranges.into_iter();
let mut range_counts = range_counts.into_iter();
for excerpt_ranges in excerpt_ranges.chunks(100) {
let excerpt_ids = match this.update(&mut cx, |this, cx| {
this.push_excerpts(buffer.clone(), excerpt_ranges.iter().cloned(), cx)
}) {
Ok(excerpt_ids) => excerpt_ids,
Err(_) => return,
};
for (excerpt_id, range_count) in excerpt_ids.into_iter().zip(range_counts.by_ref())
{
for range in ranges.by_ref().take(range_count) {
let start = Anchor {
buffer_id: Some(buffer_id),
excerpt_id,
text_anchor: range.start,
};
let end = Anchor {
buffer_id: Some(buffer_id),
excerpt_id,
text_anchor: range.end,
};
if tx.send(start..end).await.is_err() {
break;
}
}
}
}
})
.detach();
rx
}
}
pub fn push_excerpts<O>(
@@ -1215,6 +1175,91 @@ impl MultiBuffer {
anchor_ranges
}
pub fn push_multiple_excerpts_with_context_lines(
&mut self,
buffers_with_ranges: Vec<(Model<Buffer>, Vec<Range<text::Anchor>>)>,
context_line_count: u32,
cx: &mut ModelContext<Self>,
) -> Task<Vec<Range<Anchor>>> {
use futures::StreamExt;
let (excerpt_ranges_tx, mut excerpt_ranges_rx) = mpsc::channel(256);
let mut buffer_ids = Vec::with_capacity(buffers_with_ranges.len());
for (buffer, ranges) in buffers_with_ranges {
let (buffer_id, buffer_snapshot) =
buffer.update(cx, |buffer, _| (buffer.remote_id(), buffer.snapshot()));
buffer_ids.push(buffer_id);
cx.background_executor()
.spawn({
let mut excerpt_ranges_tx = excerpt_ranges_tx.clone();
async move {
let (excerpt_ranges, counts) =
build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count);
excerpt_ranges_tx
.send((buffer_id, buffer.clone(), ranges, excerpt_ranges, counts))
.await
.ok();
}
})
.detach()
}
cx.spawn(move |this, mut cx| async move {
let mut results_by_buffer_id = HashMap::default();
while let Some((buffer_id, buffer, ranges, excerpt_ranges, range_counts)) =
excerpt_ranges_rx.next().await
{
results_by_buffer_id
.insert(buffer_id, (buffer, ranges, excerpt_ranges, range_counts));
}
let mut multi_buffer_ranges = Vec::default();
'outer: for buffer_id in buffer_ids {
let Some((buffer, ranges, excerpt_ranges, range_counts)) =
results_by_buffer_id.remove(&buffer_id)
else {
continue;
};
let mut ranges = ranges.into_iter();
let mut range_counts = range_counts.into_iter();
for excerpt_ranges in excerpt_ranges.chunks(100) {
let excerpt_ids = match this.update(&mut cx, |this, cx| {
this.push_excerpts(buffer.clone(), excerpt_ranges.iter().cloned(), cx)
}) {
Ok(excerpt_ids) => excerpt_ids,
Err(_) => continue 'outer,
};
for (excerpt_id, range_count) in
excerpt_ids.into_iter().zip(range_counts.by_ref())
{
for range in ranges.by_ref().take(range_count) {
let start = Anchor {
buffer_id: Some(buffer_id),
excerpt_id,
text_anchor: range.start,
};
let end = Anchor {
buffer_id: Some(buffer_id),
excerpt_id,
text_anchor: range.end,
};
multi_buffer_ranges.push(start..end);
}
}
}
}
multi_buffer_ranges
})
}
pub fn insert_excerpts_after<O>(
&mut self,
prev_excerpt_id: ExcerptId,
@@ -4967,7 +5012,6 @@ where
#[cfg(test)]
mod tests {
use super::*;
use futures::StreamExt;
use gpui::{AppContext, Context, TestAppContext};
use language::{Buffer, Rope};
use parking_lot::RwLock;
@@ -5518,41 +5562,67 @@ mod tests {
);
}
#[gpui::test]
async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) {
let buffer = cx.new_model(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
let snapshot = buffer.read(cx);
let ranges = vec![
snapshot.anchor_before(Point::new(3, 2))..snapshot.anchor_before(Point::new(4, 2)),
snapshot.anchor_before(Point::new(7, 1))..snapshot.anchor_before(Point::new(7, 3)),
snapshot.anchor_before(Point::new(15, 0))
..snapshot.anchor_before(Point::new(15, 0)),
];
multibuffer.stream_excerpts_with_context_lines(buffer.clone(), ranges, 2, cx)
});
#[gpui::test(iterations = 100)]
async fn test_push_multiple_excerpts_with_context_lines(cx: &mut TestAppContext) {
let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(15, 4, 'a'), cx));
let snapshot_1 = buffer_1.update(cx, |buffer, _| buffer.snapshot());
let snapshot_2 = buffer_2.update(cx, |buffer, _| buffer.snapshot());
let ranges_1 = vec![
snapshot_1.anchor_before(Point::new(3, 2))..snapshot_1.anchor_before(Point::new(4, 2)),
snapshot_1.anchor_before(Point::new(7, 1))..snapshot_1.anchor_before(Point::new(7, 3)),
snapshot_1.anchor_before(Point::new(15, 0))
..snapshot_1.anchor_before(Point::new(15, 0)),
];
let ranges_2 = vec![
snapshot_2.anchor_before(Point::new(2, 1))..snapshot_2.anchor_before(Point::new(3, 1)),
snapshot_2.anchor_before(Point::new(10, 0))
..snapshot_2.anchor_before(Point::new(10, 2)),
];
let anchor_ranges = anchor_ranges.collect::<Vec<_>>().await;
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let anchor_ranges = multibuffer
.update(cx, |multibuffer, cx| {
multibuffer.push_multiple_excerpts_with_context_lines(
vec![(buffer_1.clone(), ranges_1), (buffer_2.clone(), ranges_2)],
2,
cx,
)
})
.await;
let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
assert_eq!(
snapshot.text(),
concat!(
"bbb\n", //
"bbb\n", // buffer_1
"ccc\n", //
"ddd\n", //
"eee\n", //
"ddd\n", // <-- excerpt 1
"eee\n", // <-- excerpt 1
"fff\n", //
"ggg\n", //
"hhh\n", //
"hhh\n", // <-- excerpt 2
"iii\n", //
"jjj\n", //
//
"nnn\n", //
"ooo\n", //
"ppp\n", //
"ppp\n", // <-- excerpt 3
"qqq\n", //
"rrr", //
"rrr\n", //
//
"aaaa\n", // buffer 2
"bbbb\n", //
"cccc\n", // <-- excerpt 4
"dddd\n", // <-- excerpt 4
"eeee\n", //
"ffff\n", //
//
"iiii\n", //
"jjjj\n", //
"kkkk\n", // <-- excerpt 5
"llll\n", //
"mmmm", //
)
);
@@ -5564,7 +5634,9 @@ mod tests {
vec![
Point::new(2, 2)..Point::new(3, 2),
Point::new(6, 1)..Point::new(6, 3),
Point::new(11, 0)..Point::new(11, 0)
Point::new(11, 0)..Point::new(11, 0),
Point::new(16, 1)..Point::new(17, 1),
Point::new(22, 0)..Point::new(22, 2)
]
);
}

View File

@@ -87,6 +87,7 @@ pub struct BufferSearchBar {
pending_search: Option<Task<()>>,
search_options: SearchOptions,
default_options: SearchOptions,
configured_options: SearchOptions,
query_contains_error: bool,
dismissed: bool,
search_history: SearchHistory,
@@ -517,6 +518,7 @@ impl BufferSearchBar {
active_match_index: None,
searchable_items_with_matches: Default::default(),
default_options: search_options,
configured_options: search_options,
search_options,
pending_search: None,
query_contains_error: false,
@@ -605,10 +607,11 @@ impl BufferSearchBar {
return false;
};
self.default_options = SearchOptions::from_settings(&EditorSettings::get_global(cx).search);
if self.default_options != self.search_options {
self.search_options = self.default_options;
self.configured_options =
SearchOptions::from_settings(&EditorSettings::get_global(cx).search);
if self.dismissed && self.configured_options != self.default_options {
self.search_options = self.configured_options;
self.default_options = self.configured_options;
}
self.dismissed = false;
@@ -627,6 +630,7 @@ impl BufferSearchBar {
.map(SearchableItemHandle::supported_options)
.unwrap_or_default()
}
pub fn search_suggested(&mut self, cx: &mut ViewContext<Self>) {
let search = self
.query_suggestion(cx)
@@ -1195,10 +1199,11 @@ mod tests {
use std::ops::Range;
use super::*;
use editor::{display_map::DisplayRow, DisplayPoint, Editor, MultiBuffer};
use gpui::{Context, Hsla, TestAppContext, VisualTestContext};
use editor::{display_map::DisplayRow, DisplayPoint, Editor, MultiBuffer, SearchSettings};
use gpui::{Context, Hsla, TestAppContext, UpdateGlobal, VisualTestContext};
use language::{Buffer, Point};
use project::Project;
use settings::SettingsStore;
use smol::stream::StreamExt as _;
use unindent::Unindent as _;
@@ -2320,4 +2325,119 @@ mod tests {
assert!(display_points_of(editor.all_text_background_highlights(cx)).is_empty(),);
});
}
#[gpui::test]
async fn test_search_options_changes(cx: &mut TestAppContext) {
let (_editor, search_bar, cx) = init_test(cx);
update_search_settings(
SearchSettings {
whole_word: false,
case_sensitive: false,
include_ignored: false,
regex: false,
},
cx,
);
let deploy = Deploy {
focus: true,
replace_enabled: false,
selection_search_enabled: true,
};
search_bar.update(cx, |search_bar, cx| {
assert_eq!(
search_bar.search_options,
SearchOptions::NONE,
"Should have no search options enabled by default"
);
search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
assert_eq!(
search_bar.search_options,
SearchOptions::WHOLE_WORD,
"Should enable the option toggled"
);
assert!(
!search_bar.dismissed,
"Search bar should be present and visible"
);
search_bar.deploy(&deploy, cx);
assert_eq!(
search_bar.configured_options,
SearchOptions::NONE,
"Should have configured search options matching the settings"
);
assert_eq!(
search_bar.search_options,
SearchOptions::WHOLE_WORD,
"After (re)deploying, the option should still be enabled"
);
search_bar.dismiss(&Dismiss, cx);
search_bar.deploy(&deploy, cx);
assert_eq!(
search_bar.search_options,
SearchOptions::NONE,
"After hiding and showing the search bar, default options should be used"
);
search_bar.toggle_search_option(SearchOptions::REGEX, cx);
search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
assert_eq!(
search_bar.search_options,
SearchOptions::REGEX | SearchOptions::WHOLE_WORD,
"Should enable the options toggled"
);
assert!(
!search_bar.dismissed,
"Search bar should be present and visible"
);
});
update_search_settings(
SearchSettings {
whole_word: false,
case_sensitive: true,
include_ignored: false,
regex: false,
},
cx,
);
search_bar.update(cx, |search_bar, cx| {
assert_eq!(
search_bar.search_options,
SearchOptions::REGEX | SearchOptions::WHOLE_WORD,
"Should have no search options enabled by default"
);
search_bar.deploy(&deploy, cx);
assert_eq!(
search_bar.configured_options,
SearchOptions::CASE_SENSITIVE,
"Should have configured search options matching the settings"
);
assert_eq!(
search_bar.search_options,
SearchOptions::REGEX | SearchOptions::WHOLE_WORD,
"Toggling a non-dismissed search bar with custom options should not change the default options"
);
search_bar.dismiss(&Dismiss, cx);
search_bar.deploy(&deploy, cx);
assert_eq!(
search_bar.search_options,
SearchOptions::CASE_SENSITIVE,
"After hiding and showing the search bar, default options should be used"
);
});
}
fn update_search_settings(search_settings: SearchSettings, cx: &mut TestAppContext) {
cx.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<EditorSettings>(cx, |settings| {
settings.search = Some(search_settings);
});
});
});
}
}

View File

@@ -264,54 +264,35 @@ impl ProjectSearch {
let mut limit_reached = false;
while let Some(results) = matches.next().await {
let tasks = results
.into_iter()
.map(|result| {
let this = this.clone();
cx.spawn(|mut cx| async move {
match result {
project::search::SearchResult::Buffer { buffer, ranges } => {
let mut match_ranges_rx =
this.update(&mut cx, |this, cx| {
this.excerpts.update(cx, |excerpts, cx| {
excerpts.stream_excerpts_with_context_lines(
buffer,
ranges,
editor::DEFAULT_MULTIBUFFER_CONTEXT,
cx,
)
})
})?;
let mut match_ranges = vec![];
while let Some(range) = match_ranges_rx.next().await {
match_ranges.push(range);
}
anyhow::Ok((match_ranges, false))
}
project::search::SearchResult::LimitReached => {
anyhow::Ok((vec![], true))
}
}
})
})
.collect::<Vec<_>>();
let result_ranges = futures::future::join_all(tasks).await;
let mut combined_ranges = vec![];
for (ranges, result_limit_reached) in result_ranges.into_iter().flatten() {
combined_ranges.extend(ranges);
if result_limit_reached {
limit_reached = result_limit_reached;
let mut buffers_with_ranges = Vec::with_capacity(results.len());
for result in results {
match result {
project::search::SearchResult::Buffer { buffer, ranges } => {
buffers_with_ranges.push((buffer, ranges));
}
project::search::SearchResult::LimitReached => {
limit_reached = true;
}
}
}
let match_ranges = this
.update(&mut cx, |this, cx| {
this.excerpts.update(cx, |excerpts, cx| {
excerpts.push_multiple_excerpts_with_context_lines(
buffers_with_ranges,
editor::DEFAULT_MULTIBUFFER_CONTEXT,
cx,
)
})
})
.ok()?
.await;
this.update(&mut cx, |this, cx| {
if !combined_ranges.is_empty() {
this.no_results = Some(false);
this.match_ranges.extend(combined_ranges);
cx.notify();
}
this.no_results = Some(false);
this.match_ranges.extend(match_ranges);
cx.notify();
})
.ok()?;
}

View File

@@ -29,6 +29,7 @@ supermaven_api.workspace = true
smol.workspace = true
text.workspace = true
ui.workspace = true
unicode-segmentation.workspace = true
util.workspace = true
[target.'cfg(target_os = "windows")'.dependencies]

View File

@@ -12,6 +12,7 @@ use std::{
time::Duration,
};
use text::{ToOffset, ToPoint};
use unicode_segmentation::UnicodeSegmentation;
pub const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
@@ -54,33 +55,34 @@ fn completion_state_from_diff(
) -> CompletionProposal {
let buffer_text = snapshot
.text_for_range(delete_range.clone())
.collect::<String>()
.chars()
.collect::<Vec<char>>();
.collect::<String>();
let mut inlays: Vec<InlayProposal> = Vec::new();
let completion = completion_text.chars().collect::<Vec<char>>();
let completion_graphemes: Vec<&str> = completion_text.graphemes(true).collect();
let buffer_graphemes: Vec<&str> = buffer_text.graphemes(true).collect();
let mut offset = position.to_offset(&snapshot);
let mut i = 0;
let mut j = 0;
while i < completion.len() && j < buffer_text.len() {
while i < completion_graphemes.len() && j < buffer_graphemes.len() {
// find the next instance of the buffer text in the completion text.
let k = completion[i..].iter().position(|c| *c == buffer_text[j]);
let k = completion_graphemes[i..]
.iter()
.position(|c| *c == buffer_graphemes[j]);
match k {
Some(k) => {
if k != 0 {
// the range from the current position to item is an inlay.
inlays.push(InlayProposal::Suggestion(
snapshot.anchor_after(offset),
completion_text[i..i + k].into(),
completion_graphemes[i..i + k].join("").into(),
));
}
i += k + 1;
j += 1;
offset.add_assign(1);
offset.add_assign(buffer_graphemes[j - 1].len());
}
None => {
// there are no more matching completions, so drop the remaining
@@ -90,11 +92,11 @@ fn completion_state_from_diff(
}
}
if j == buffer_text.len() && i < completion.len() {
if j == buffer_graphemes.len() && i < completion_graphemes.len() {
// there is leftover completion text, so drop it as an inlay.
inlays.push(InlayProposal::Suggestion(
snapshot.anchor_after(offset),
completion_text[i..completion_text.len()].into(),
completion_graphemes[i..].join("").into(),
));
}

View File

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

View File

@@ -1 +1 @@
dev
stable

View File

@@ -68,29 +68,95 @@ Prettier will also be used for TypeScript files by default. To disable this:
Zed sets the following initialization options to make the language server send back inlay hints
(that is, when Zed has inlay hints enabled in the settings).
You can override these settings in your configuration file:
You can override these settings in your Zed settings file.
When using `typescript-language-server`:
```json
"lsp": {
"$LANGUAGE_SERVER_NAME": {
"initialization_options": {
"preferences": {
"includeInlayParameterNameHints": "all",
"includeInlayParameterNameHintsWhenArgumentMatchesName": true,
"includeInlayFunctionParameterTypeHints": true,
"includeInlayVariableTypeHints": true,
"includeInlayVariableTypeHintsWhenTypeMatchesName": true,
"includeInlayPropertyDeclarationTypeHints": true,
"includeInlayFunctionLikeReturnTypeHints": true,
"includeInlayEnumMemberValueHints": true,
}
{
"lsp": {
"typescript-language-server": {
"initialization_options": {
"preferences": {
"includeInlayParameterNameHints": "all",
"includeInlayParameterNameHintsWhenArgumentMatchesName": true,
"includeInlayFunctionParameterTypeHints": true,
"includeInlayVariableTypeHints": true,
"includeInlayVariableTypeHintsWhenTypeMatchesName": true,
"includeInlayPropertyDeclarationTypeHints": true,
"includeInlayFunctionLikeReturnTypeHints": true,
"includeInlayEnumMemberValueHints": true
}
}
}
}
```
See [typescript-language-server inlayhints documentation](https://github.com/typescript-language-server/typescript-language-server?tab=readme-ov-file#inlay-hints-textdocumentinlayhint) for more information.
When using `vtsls`:
```json
{
"lsp": {
"vtsls": {
"settings": {
// For JavaScript:
"javascript": {
"inlayHints": {
"parameterNames": {
"enabled": "all",
"suppressWhenArgumentMatchesName": false
},
"parameterTypes": {
"enabled": true
},
"variableTypes": {
"enabled": true,
"suppressWhenTypeMatchesName": true
},
"propertyDeclarationTypes": {
"enabled": true
},
"functionLikeReturnTypes": {
"enabled": true
},
"enumMemberValues": {
"enabled": true
}
}
},
// For TypeScript:
"typescript": {
"inlayHints": {
"parameterNames": {
"enabled": "all",
"suppressWhenArgumentMatchesName": false
},
"parameterTypes": {
"enabled": true
},
"variableTypes": {
"enabled": true,
"suppressWhenTypeMatchesName": true
},
"propertyDeclarationTypes": {
"enabled": true
},
"functionLikeReturnTypes": {
"enabled": true
},
"enumMemberValues": {
"enabled": true
}
}
}
}
}
}
}
```
## See also
- [Zed Yarn documentation](./yarn.md) for a walkthrough of configuring your project to use Yarn.