Compare commits
13 Commits
fix-git-ht
...
v0.154.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25e2e9c672 | ||
|
|
b2ee628360 | ||
|
|
3ea89a1f9c | ||
|
|
752f6a7c33 | ||
|
|
1e2fcfb386 | ||
|
|
3ef8491fcb | ||
|
|
ce3a6350e6 | ||
|
|
15931d1b77 | ||
|
|
2a5925c235 | ||
|
|
153fb9eab8 | ||
|
|
2330f0d2f9 | ||
|
|
76b7c1df43 | ||
|
|
a0a1b1c69b |
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()?;
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -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>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
stable
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user