From 09165c15dc5d1fea93604231eaf30ca4c25f1cd6 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 30 May 2026 14:37:39 -0600 Subject: [PATCH 01/39] gpui: Support prompt_for_paths in TestPlatform (#58139) Implements the previously-`unimplemented!()` `TestPlatform::prompt_for_paths` so tests can drive the platform Open dialog deterministically. Adds `TestAppContext::simulate_path_prompt_response` and `did_prompt_for_paths`, mirroring the existing `prompt_for_new_path` test helpers (`simulate_new_path_selection`). The simulated response validates that callers don't return multiple paths when `PathPromptOptions::multiple` is false. Release Notes: - N/A --- crates/gpui/src/app/test_context.rs | 65 +++++++++++++++++++++++ crates/gpui/src/platform/test/platform.rs | 46 +++++++++++++--- 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 8a6d7e3f840d05..9e32c5dc2d4520 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -336,6 +336,20 @@ impl TestAppContext { self.test_platform.simulate_new_path_selection(select_path); } + /// Simulates responding to a `prompt_for_paths` ("Open") dialog. + pub fn simulate_path_prompt_response( + &self, + select_paths: impl FnOnce(&crate::PathPromptOptions) -> Option>, + ) { + self.test_platform + .simulate_path_prompt_response(select_paths); + } + + /// Returns true if there's a path selection dialog pending. + pub fn did_prompt_for_paths(&self) -> bool { + self.test_platform.did_prompt_for_paths() + } + /// Simulates clicking a button in an platform-level alert dialog. #[track_caller] pub fn simulate_prompt_answer(&self, button: &str) { @@ -1098,3 +1112,54 @@ impl AnyWindowHandle { .unwrap() } } + +#[cfg(test)] +mod tests { + use crate::{PathPromptOptions, TestAppContext}; + use std::path::PathBuf; + + #[gpui::test] + async fn test_simulate_path_prompt_response(cx: &mut TestAppContext) { + assert!(!cx.did_prompt_for_paths()); + + let receiver = cx.update(|cx| { + cx.prompt_for_paths(PathPromptOptions { + files: false, + directories: true, + multiple: true, + prompt: None, + }) + }); + assert!(cx.did_prompt_for_paths()); + + let selected = vec![PathBuf::from("/a"), PathBuf::from("/b")]; + cx.simulate_path_prompt_response({ + let selected = selected.clone(); + move |options| { + assert!(options.multiple); + Some(selected) + } + }); + assert!(!cx.did_prompt_for_paths()); + + let response = receiver.await.unwrap().unwrap(); + assert_eq!(response, Some(selected)); + } + + #[gpui::test] + async fn test_simulate_path_prompt_cancellation(cx: &mut TestAppContext) { + let receiver = cx.update(|cx| { + cx.prompt_for_paths(PathPromptOptions { + files: true, + directories: false, + multiple: false, + prompt: None, + }) + }); + + cx.simulate_path_prompt_response(|_options| None); + + let response = receiver.await.unwrap().unwrap(); + assert_eq!(response, None); + } +} diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index cc8c5749bd4696..b3bee3769e063f 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -1,9 +1,10 @@ use crate::{ AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels, - DummyKeyboardMapper, ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, - PlatformHeadlessRenderer, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, - PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, SourceMetadata, - Task, TestDisplay, TestWindow, ThermalState, WindowAppearance, WindowParams, size, + DummyKeyboardMapper, ForegroundExecutor, Keymap, NoopTextSystem, PathPromptOptions, Platform, + PlatformDisplay, PlatformHeadlessRenderer, PlatformKeyboardLayout, PlatformKeyboardMapper, + PlatformTextSystem, PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, + SourceMetadata, Task, TestDisplay, TestWindow, ThermalState, WindowAppearance, WindowParams, + size, }; use anyhow::Result; use collections::VecDeque; @@ -85,6 +86,10 @@ struct TestPrompt { pub(crate) struct TestPrompts { multiple_choice: VecDeque, new_path: VecDeque<(PathBuf, oneshot::Sender>>)>, + paths: VecDeque<( + PathPromptOptions, + oneshot::Sender>>>, + )>, } impl TestPlatform { @@ -147,6 +152,33 @@ impl TestPlatform { tx.send(Ok(select_path(&path))).ok(); } + pub(crate) fn simulate_path_prompt_response( + &self, + select_paths: impl FnOnce(&PathPromptOptions) -> Option>, + ) { + let (options, tx) = self + .prompts + .borrow_mut() + .paths + .pop_front() + .expect("no pending paths prompt"); + let selection = select_paths(&options); + if let Some(paths) = &selection + && !options.multiple + && paths.len() > 1 + { + panic!( + "selected {} paths for a prompt that does not allow multiple selection", + paths.len() + ); + } + tx.send(Ok(selection)).ok(); + } + + pub(crate) fn did_prompt_for_paths(&self) -> bool { + !self.prompts.borrow().paths.is_empty() + } + #[track_caller] pub(crate) fn simulate_prompt_answer(&self, response: &str) { let prompt = self @@ -348,9 +380,11 @@ impl Platform for TestPlatform { fn prompt_for_paths( &self, - _options: crate::PathPromptOptions, + options: crate::PathPromptOptions, ) -> oneshot::Receiver>>> { - unimplemented!() + let (tx, rx) = oneshot::channel(); + self.prompts.borrow_mut().paths.push_back((options, tx)); + rx } fn prompt_for_new_path( From fd93a53c1702dbf4c28c4b23699e9e0cf56c893a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 31 May 2026 17:18:31 +0200 Subject: [PATCH 02/39] Limit editor rendering to visible clipped rows (#58132) Editor prepaint previously used the full parent content mask height when deciding which rows to lay out, while only accounting for top clipping. Embedded, content-sized editors in agent tool cards could therefore ask the display map to highlight rows below the editor's visible intersection with the list viewport. Compute the vertical intersection between the editor bounds and the content mask instead, so highlighted chunks and custom highlight endpoints are built only for rows that can actually be painted. Release Notes: - N/A or Added/Fixed/Improved ... --- crates/editor/src/element.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 026a3032a1c684..5474e6e3cb1405 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -7997,12 +7997,14 @@ impl Element for EditorElement { // Calculate how much of the editor is clipped by parent containers (e.g., List). // This allows us to only render lines that are actually visible, which is - // critical for performance when large AutoHeight editors are inside Lists. + // critical for performance when large content-sized editors are inside Lists. let visible_bounds = window.content_mask().bounds; - let clipped_top = (visible_bounds.origin.y - bounds.origin.y).max(px(0.)); + let visible_top = bounds.top().max(visible_bounds.top()); + let visible_bottom = bounds.bottom().min(visible_bounds.bottom()); + let clipped_top = (visible_top - bounds.top()).max(px(0.)); + let visible_height = (visible_bottom - visible_top).max(px(0.)); let clipped_top_in_lines = f64::from(clipped_top / line_height); - let visible_height_in_lines = - f64::from(visible_bounds.size.height / line_height); + let visible_height_in_lines = f64::from(visible_height / line_height); // The max scroll position for the top of the window let scroll_beyond_last_line = self.editor.read(cx).scroll_beyond_last_line(cx); From 876ec5a8a074ba83cce2129ed4d76b59c05a37e9 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 31 May 2026 17:19:20 +0200 Subject: [PATCH 03/39] Improve performance of `create_highlight_endpoints` (#58119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change gets rid of the tree traversal overhead by batching the anchor resolution > Create highlight endpoints/text_highlights/100 > time: [298.43 µs 298.55 µs 298.69 µs] > Create highlight endpoints/text_highlights/100 > time: [40.347 µs 40.386 µs 40.427 µs] > change: [-86.492% -86.481% -86.471%] (p = 0.00 < 0.05) > Performance has improved. This is especially important given that `CustomHighlightsChunks::seek` tends to get called a lot, which re-creates highlight endpoints a lot. Release Notes: - N/A or Added/Fixed/Improved ... --- crates/editor/benches/display_map.rs | 119 ++++++++++++- .../src/display_map/custom_highlights.rs | 160 +++++++++++------- crates/multi_buffer/src/multi_buffer.rs | 27 ++- 3 files changed, 233 insertions(+), 73 deletions(-) diff --git a/crates/editor/benches/display_map.rs b/crates/editor/benches/display_map.rs index 148c7bd4ed2abf..c48f0c50b727f5 100644 --- a/crates/editor/benches/display_map.rs +++ b/crates/editor/benches/display_map.rs @@ -1,10 +1,12 @@ -use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; -use editor::MultiBuffer; -use gpui::TestDispatcher; +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use editor::{MultiBuffer, display_map::*}; +use gpui::{AppContext as _, HighlightStyle, Hsla, TestDispatcher, font, px}; use itertools::Itertools; use multi_buffer::MultiBufferOffset; +use project::project_settings::DiagnosticSeverity; use rand::{Rng, SeedableRng, rngs::StdRng}; -use std::num::NonZeroU32; +use settings::SettingsStore; +use std::{num::NonZeroU32, time::Duration}; use text::Bias; use util::RandomCharIter; @@ -101,5 +103,112 @@ fn to_fold_point_benchmark(c: &mut Criterion) { group.finish(); } -criterion_group!(benches, to_tab_point_benchmark, to_fold_point_benchmark); +fn create_highlight_endpoints_benchmark(c: &mut Criterion) { + const LINE_COUNT: usize = 20_000; + const LINE_VIEW_PORT_COUNT: usize = 100; + const HIGHLIGHTS_PER_LINE: usize = 4; + + let dispatcher = TestDispatcher::new(1); + let mut cx = gpui::TestAppContext::build(dispatcher, None); + cx.update(|cx| { + let store = SettingsStore::test(cx); + cx.set_global(store); + editor::init(cx); + }); + + let mut text = String::new(); + let mut highlight_ranges = Vec::with_capacity(LINE_COUNT * HIGHLIGHTS_PER_LINE); + for line in 0..LINE_COUNT { + text.push_str("fn item_"); + text.push_str(&format!("{line:05}")); + text.push_str("() { "); + + let start = text.len(); + text.push_str("alpha_highlight"); + highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len())); + + text.push_str(" + "); + let start = text.len(); + text.push_str("beta_highlight"); + highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len())); + + text.push_str(" + "); + let start = text.len(); + text.push_str("gamma_highlight"); + highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len())); + + text.push_str(" + "); + let start = text.len(); + text.push_str("delta_highlight"); + highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len())); + + text.push_str("; }\n"); + } + + let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx)); + let buffer_snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx)); + let highlight_ranges = highlight_ranges + .into_iter() + .map(|range| { + buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_before(range.end) + }) + .collect(); + + let map = cx.new(|cx| { + DisplayMap::new( + buffer, + font("Courier"), + px(16.0), + None, + 1, + 1, + FoldPlaceholder::default(), + DiagnosticSeverity::Warning, + cx, + ) + }); + cx.update(|cx| { + map.update(cx, |map, cx| { + map.highlight_text( + HighlightKey::Editor, + highlight_ranges, + HighlightStyle { + color: Some(Hsla::blue()), + ..Default::default() + }, + false, + cx, + ); + }); + }); + let snapshot = cx.update(|cx| map.update(cx, |map, cx| map.snapshot(cx))); + + let mut group = c.benchmark_group("Create highlight endpoints"); + group.sample_size(10); + group.measurement_time(Duration::from_secs(10)); + group.bench_with_input( + BenchmarkId::new("text_highlights", LINE_VIEW_PORT_COUNT), + &snapshot, + |bench, snapshot| { + bench.iter(|| { + black_box(snapshot.chunks( + DisplayRow(400)..DisplayRow(400 + LINE_VIEW_PORT_COUNT as u32), + language::LanguageAwareStyling { + tree_sitter: false, + diagnostics: false, + }, + Default::default(), + )); + }); + }, + ); + group.finish(); +} + +criterion_group!( + benches, + to_tab_point_benchmark, + to_fold_point_benchmark, + create_highlight_endpoints_benchmark +); criterion_main!(benches); diff --git a/crates/editor/src/display_map/custom_highlights.rs b/crates/editor/src/display_map/custom_highlights.rs index 6e93e562172dec..8ca43fe3cd2215 100644 --- a/crates/editor/src/display_map/custom_highlights.rs +++ b/crates/editor/src/display_map/custom_highlights.rs @@ -1,13 +1,8 @@ use collections::BTreeMap; use gpui::HighlightStyle; use language::{Chunk, LanguageAwareStyling}; -use multi_buffer::{MultiBufferChunks, MultiBufferOffset, MultiBufferSnapshot, ToOffset as _}; -use std::{ - cmp, - iter::{self, Peekable}, - ops::Range, - vec, -}; +use multi_buffer::{MultiBufferChunks, MultiBufferOffset, MultiBufferSnapshot}; +use std::{cmp, ops::Range}; use crate::display_map::{HighlightKey, SemanticTokensHighlights, TextHighlights}; @@ -17,7 +12,7 @@ pub struct CustomHighlightsChunks<'a> { offset: MultiBufferOffset, multibuffer_snapshot: &'a MultiBufferSnapshot, - highlight_endpoints: Peekable>, + highlight_endpoints: Vec, active_highlights: BTreeMap, text_highlights: Option<&'a TextHighlights>, semantic_token_highlights: Option<&'a SemanticTokensHighlights>, @@ -39,17 +34,20 @@ impl<'a> CustomHighlightsChunks<'a> { semantic_token_highlights: Option<&'a SemanticTokensHighlights>, multibuffer_snapshot: &'a MultiBufferSnapshot, ) -> Self { + let mut highlight_endpoints = Vec::new(); + create_highlight_endpoints( + &range, + text_highlights, + semantic_token_highlights, + multibuffer_snapshot, + &mut highlight_endpoints, + ); Self { buffer_chunks: multibuffer_snapshot.chunks(range.clone(), language_aware), buffer_chunk: None, offset: range.start, text_highlights, - highlight_endpoints: create_highlight_endpoints( - &range, - text_highlights, - semantic_token_highlights, - multibuffer_snapshot, - ), + highlight_endpoints, active_highlights: Default::default(), multibuffer_snapshot, semantic_token_highlights, @@ -58,11 +56,12 @@ impl<'a> CustomHighlightsChunks<'a> { #[ztracing::instrument(skip_all)] pub fn seek(&mut self, new_range: Range) { - self.highlight_endpoints = create_highlight_endpoints( + create_highlight_endpoints( &new_range, self.text_highlights, self.semantic_token_highlights, self.multibuffer_snapshot, + &mut self.highlight_endpoints, ); self.offset = new_range.start; self.buffer_chunks.seek(new_range); @@ -76,11 +75,14 @@ fn create_highlight_endpoints( text_highlights: Option<&TextHighlights>, semantic_token_highlights: Option<&SemanticTokensHighlights>, buffer: &MultiBufferSnapshot, -) -> iter::Peekable> { - let mut highlight_endpoints = Vec::new(); + highlight_endpoints: &mut Vec, +) { + highlight_endpoints.clear(); if let Some(text_highlights) = text_highlights { let start = buffer.anchor_after(range.start); let end = buffer.anchor_after(range.end); + let mut text_highlights_scratch = Vec::new(); + for (&tag, text_highlights) in text_highlights.iter() { let style = text_highlights.0; let ranges = &text_highlights.1; @@ -94,30 +96,45 @@ fn create_highlight_endpoints( }) .unwrap_or_else(|i| i); - highlight_endpoints.reserve(2 * end_ix); - - for range in &ranges[start_ix..][..end_ix] { - let start = range.start.to_offset(buffer); - let end = range.end.to_offset(buffer); - if start == end { - continue; - } - highlight_endpoints.push(HighlightEndpoint { - offset: start, - tag, - style: Some(style), - }); - highlight_endpoints.push(HighlightEndpoint { - offset: end, - tag, - style: None, - }); - } + let ranges_ = &ranges[start_ix..][..end_ix]; + text_highlights_scratch.clear(); + text_highlights_scratch.reserve(ranges_.len()); + highlight_endpoints.reserve(2 * ranges_.len()); + + let mut iter = ranges_.iter(); + buffer.summaries_for_anchors_cb( + ranges_.iter().map(|r| &r.start), + |start: MultiBufferOffset| { + text_highlights_scratch.push((start, iter.next().unwrap().end)); + }, + ); + text_highlights_scratch.sort_by(|a, b| a.1.cmp(&b.1, buffer)); + let mut iter = text_highlights_scratch.iter(); + buffer.summaries_for_anchors_cb( + text_highlights_scratch.iter().map(|(_, end)| end), + |end: MultiBufferOffset| { + let start = iter.next().unwrap().0; + if start == end { + return; + } + highlight_endpoints.push(HighlightEndpoint { + offset: start, + tag, + style: Some(style), + }); + highlight_endpoints.push(HighlightEndpoint { + offset: end, + tag, + style: None, + }); + }, + ); } } if let Some(semantic_token_highlights) = semantic_token_highlights { let start = buffer.anchor_after(range.start); let end = buffer.anchor_after(range.end); + let mut semantic_highlights_scratch = Vec::new(); for buffer_id in buffer.buffer_ids_for_range(range.clone()) { let Some((semantic_token_highlights, interner)) = semantic_token_highlights.get(&buffer_id) @@ -133,31 +150,54 @@ fn create_highlight_endpoints( .then(cmp::Ordering::Less) }) .unwrap_or_else(|i| i); - for token in &semantic_token_highlights[start_ix..] { - if token.range.start.cmp(&end, buffer).is_ge() { - break; - } + let end_ix = semantic_token_highlights[start_ix..] + .binary_search_by(|probe| { + probe + .range + .start + .cmp(&end, buffer) + .then(cmp::Ordering::Greater) + }) + .unwrap_or_else(|i| i); - let start = token.range.start.to_offset(buffer); - let end = token.range.end.to_offset(buffer); - if start == end { - continue; - } - highlight_endpoints.push(HighlightEndpoint { - offset: start, - tag: HighlightKey::SemanticToken, - style: Some(interner[token.style]), - }); - highlight_endpoints.push(HighlightEndpoint { - offset: end, - tag: HighlightKey::SemanticToken, - style: None, - }); - } + let ranges_ = &semantic_token_highlights[start_ix..][..end_ix]; + semantic_highlights_scratch.clear(); + semantic_highlights_scratch.reserve(ranges_.len()); + highlight_endpoints.reserve(2 * ranges_.len()); + + let mut iter = ranges_.iter(); + buffer.summaries_for_anchors_cb( + ranges_.iter().map(|token| &token.range.start), + |start: MultiBufferOffset| { + semantic_highlights_scratch.push((start, iter.next().unwrap())); + }, + ); + semantic_highlights_scratch.sort_by(|a, b| a.1.range.end.cmp(&b.1.range.end, buffer)); + let mut iter = semantic_highlights_scratch.iter(); + buffer.summaries_for_anchors_cb( + semantic_highlights_scratch + .iter() + .map(|(_, token)| &token.range.end), + |end: MultiBufferOffset| { + let (start, token) = iter.next().unwrap(); + if *start == end { + return; + } + highlight_endpoints.push(HighlightEndpoint { + offset: *start, + tag: HighlightKey::SemanticToken, + style: Some(interner[token.style]), + }); + highlight_endpoints.push(HighlightEndpoint { + offset: end, + tag: HighlightKey::SemanticToken, + style: None, + }); + }, + ); } } - highlight_endpoints.sort(); - highlight_endpoints.into_iter().peekable() + highlight_endpoints.sort_by(|a, b| a.cmp(b).reverse()); } impl<'a> Iterator for CustomHighlightsChunks<'a> { @@ -166,14 +206,14 @@ impl<'a> Iterator for CustomHighlightsChunks<'a> { #[ztracing::instrument(skip_all)] fn next(&mut self) -> Option { let mut next_highlight_endpoint = MultiBufferOffset(usize::MAX); - while let Some(endpoint) = self.highlight_endpoints.peek().copied() { + while let Some(endpoint) = self.highlight_endpoints.last().copied() { if endpoint.offset <= self.offset { if let Some(style) = endpoint.style { self.active_highlights.insert(endpoint.tag, style); } else { self.active_highlights.remove(&endpoint.tag); } - self.highlight_endpoints.next(); + self.highlight_endpoints.pop(); } else { next_highlight_endpoint = endpoint.offset; break; diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 809f23bc394fdc..1641c460eb2adf 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -5003,6 +5003,20 @@ impl MultiBufferSnapshot { } pub fn summaries_for_anchors<'a, MBD, I>(&'a self, anchors: I) -> Vec + where + MBD: MultiBufferDimension + + Ord + + Sub + + AddAssign, + MBD::TextDimension: Sub + Ord, + I: 'a + IntoIterator, + { + let mut summaries = Vec::new(); + self.summaries_for_anchors_cb(anchors, |summary| summaries.push(summary)); + summaries + } + + pub fn summaries_for_anchors_cb<'a, MBD, I>(&'a self, anchors: I, mut cb: impl FnMut(MBD)) where MBD: MultiBufferDimension + Ord @@ -5018,18 +5032,17 @@ impl MultiBufferSnapshot { .cursor::, OutputDimension>>(()); diff_transforms_cursor.next(); - let mut summaries = Vec::new(); while let Some(anchor) = anchors.peek() { let target = anchor.seek_target(self); let excerpt_anchor = match anchor { Anchor::Min => { - summaries.push(MBD::default()); + cb(MBD::default()); anchors.next(); continue; } Anchor::Excerpt(excerpt_anchor) => excerpt_anchor, Anchor::Max => { - summaries.push(MBD::from_summary(&self.text_summary())); + cb(MBD::from_summary(&self.text_summary())); anchors.next(); continue; } @@ -5047,7 +5060,7 @@ impl MultiBufferSnapshot { excerpt_start_position, &mut diff_transforms_cursor, ); - summaries.push(position); + cb(position); anchors.next(); continue; } @@ -5083,7 +5096,7 @@ impl MultiBufferSnapshot { diff_transforms_cursor.seek_forward(&position, Bias::Left); } - summaries.push(self.summary_for_anchor_with_excerpt_position( + cb(self.summary_for_anchor_with_excerpt_position( excerpt_anchor, position, &mut diff_transforms_cursor, @@ -5097,12 +5110,10 @@ impl MultiBufferSnapshot { excerpt_start_position, &mut diff_transforms_cursor, ); - summaries.push(position); + cb(position); anchors.next(); } } - - summaries } pub fn dimensions_from_points<'a, MBD>( From 6f335c2f595f1ff1cb0f68b6e63692f5a269d3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yara=20=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7=EF=B8=8F?= Date: Sun, 31 May 2026 20:32:46 +0200 Subject: [PATCH 04/39] Do not play join sound in large meetings (#54337) The join sounds get annoying in large meetings, let's not play it anymore when the meeting get's really big. The guest joined sound is plays regardless of group size so participants get a heads-up when someone external joins. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - Improved join sound no longer plays in large meetings --- crates/call/src/call_impl/room.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/call/src/call_impl/room.rs b/crates/call/src/call_impl/room.rs index f269dfdfbbe74f..108c5a46304ffa 100644 --- a/crates/call/src/call_impl/room.rs +++ b/crates/call/src/call_impl/room.rs @@ -895,7 +895,8 @@ impl Room { if this.created.elapsed() > Duration::from_millis(100) { if let proto::ChannelRole::Guest = role { Audio::play_sound(Sound::GuestJoined, cx); - } else { + // Do not play join sound in large meetings + } else if this.remote_participants().len() < 10 { Audio::play_sound(Sound::Joined, cx); } } From f4f527073d2a9afdffea6bc3a1207fe9c1f93af8 Mon Sep 17 00:00:00 2001 From: saberoueslati Date: Sun, 31 May 2026 19:42:00 +0100 Subject: [PATCH 05/39] Fix json! empty-string highlighting in Rust (#55126) ## Context This fixes incorrect syntax highlighting inside Rust `json!` macros when a JSON value is an empty string. In the current Rust tree-sitter injection setup, most macro bodies are reparsed as nested Rust, which works for macros like `vec!` but breaks down for JSON-shaped `json!({ ... })` content. When the nested Rust parse loses sync at `""`, later values can inherit incorrect highlighting. Closes #54838 The fix treats `json!` as an exception to the generic nested-Rust macro injection rule. That keeps the outer Rust layer responsible for token-level highlighting inside the macro body, which is enough to correctly color JSON keys, string values, and booleans without introducing a brittle JSON-specific injection for Rust token trees. Manual test after the fix below : [Screencast from 2026-04-29 00-53-01.webm](https://github.com/user-attachments/assets/26453acf-1d72-4a97-9969-3f8e236dc0cd) ## How to Review - `crates/grammars/src/rust/injections.scm`: Start here. This is the functional fix. The generic Rust macro injection rule now excludes `json`, so `json!` and `serde_json::json!` bodies are no longer reparsed as nested Rust. Existing special cases like `view!`, `html!`, `sql!`, and regex-related behavior are left unchanged. - `crates/language/src/syntax_map/syntax_map_tests.rs`: This adds a regression test covering the reported case. It verifies that an empty string inside `serde_json::json!({ ... })` does not break subsequent highlighting, and that the expected string and boolean captures still appear for the later JSON entries. ## Self-Review Checklist - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the UI/UX checklist - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed incorrect Rust syntax highlighting after empty string values inside `json!` macros. --- crates/grammars/src/rust/injections.scm | 2 +- .../src/syntax_map/syntax_map_tests.rs | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/crates/grammars/src/rust/injections.scm b/crates/grammars/src/rust/injections.scm index 89d839282d3388..4d6b67b9f8198d 100644 --- a/crates/grammars/src/rust/injections.scm +++ b/crates/grammars/src/rust/injections.scm @@ -10,7 +10,7 @@ (scoped_identifier (identifier) @_macro_name .) ] - (#not-any-of? @_macro_name "view" "html") + (#not-any-of? @_macro_name "view" "html" "json") (token_tree) @injection.content (#set! injection.language "rust")) diff --git a/crates/language/src/syntax_map/syntax_map_tests.rs b/crates/language/src/syntax_map/syntax_map_tests.rs index 8bff7ce1415c00..98804037e31b8b 100644 --- a/crates/language/src/syntax_map/syntax_map_tests.rs +++ b/crates/language/src/syntax_map/syntax_map_tests.rs @@ -349,6 +349,56 @@ fn test_dynamic_language_injection(cx: &mut App) { assert!(!syntax_map.contains_unknown_injections()); } +#[gpui::test] +fn test_rust_json_macro_empty_string_highlighting(cx: &mut App) { + let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); + let language = rust_lang(); + registry.add(language.clone()); + + let buffer = Buffer::new( + ReplicaId::LOCAL, + BufferId::new(1).unwrap(), + r#" + serde_json::json!({ + "email": "", + "password": "password123", + "requires2FA": false + }) + "# + .unindent(), + ); + + let mut syntax_map = SyntaxMap::new(&buffer); + syntax_map.set_language_registry(registry); + syntax_map.reparse(language, &buffer); + + assert_capture_ranges( + &syntax_map, + &buffer, + &["string"], + r#" + serde_json::json!({ + «"email"»: «""», + «"password"»: «"password123"», + «"requires2FA"»: false + }) + "#, + ); + + assert_capture_ranges( + &syntax_map, + &buffer, + &["boolean"], + r#" + serde_json::json!({ + "email": "", + "password": "password123", + "requires2FA": «false» + }) + "#, + ); +} + #[gpui::test] fn test_typing_multiple_new_injections(cx: &mut App) { let (buffer, syntax_map) = test_edit_sequence( From dedb2af992a8a9b890f544d5fd8e83086f856eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Schw=C3=A4mmle?= <50438383+felixschwamm@users.noreply.github.com> Date: Sun, 31 May 2026 20:42:51 +0200 Subject: [PATCH 06/39] keymap_editor: Fix create keybinding button clipping out of editor (#54708) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary In the keymap editor, the search input at the top used `size_full()` while the adjacent button row had `min_w_96()`. On narrow panes (e.g. side-by-side splits), this caused the action buttons — including "Create keybinding" — to overflow and clip out of the editor. This change lets the search input flex and shrink (`flex_1()` + `min_w_0()`), and makes the button row `flex_none()` so it keeps its natural width and stays visible at any pane size. ## Before / After Screenshot 2026-04-23 215511 ## Test plan - [x] Open the keymap editor (`zed: open keymap editor`) - [x] Confirm the "Create keybinding" and other action buttons remain visible - [x] Confirm the search input shrinks gracefully instead of pushing buttons off-screen Co-authored-by: Lukas Wirth --- crates/keymap_editor/src/keymap_editor.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/keymap_editor/src/keymap_editor.rs b/crates/keymap_editor/src/keymap_editor.rs index 6a6856e4e68d6f..54c54ab1fca0fd 100644 --- a/crates/keymap_editor/src/keymap_editor.rs +++ b/crates/keymap_editor/src/keymap_editor.rs @@ -2019,7 +2019,8 @@ impl Render for KeymapEditor { context.add("BufferSearchBar"); context }) - .size_full() + .flex_1() + .min_w_0() .h_8() .pl_2() .pr_1() @@ -2032,7 +2033,7 @@ impl Render for KeymapEditor { .child( h_flex() .gap_1() - .min_w_96() + .flex_none() .items_center() .child( IconButton::new( From 0b43719b8aaf8669b407d3df714425579cfc02c5 Mon Sep 17 00:00:00 2001 From: chenmi Date: Mon, 1 Jun 2026 03:17:28 +0800 Subject: [PATCH 07/39] Remove stale SSH LSP log entries after server restarts (#55299) Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #55287 This fixes the SSH remote case where restarting a language server left a stale entry in the LSP Logs panel. The root cause was that the remote client learned about the replacement language server, but never received an explicit removal update for the previous server id. As a result, the old status and log-store entry remained visible even though only the new server continued producing logs. Tested with: - `cargo test -p collab --test collab_tests remote_editing_collaboration_tests::test_ssh_restarting_language_server_replaces_remote_status -- --exact` Release Notes: - Fixed stale duplicate entries in the LSP Logs panel after restarting an SSH remote language server. Co-authored-by: Lukas Wirth --- .../remote_editing_collaboration_tests.rs | 149 +++++++++++++++++- crates/project/src/lsp_store.rs | 9 ++ crates/proto/proto/lsp.proto | 3 + crates/remote_server/src/headless_project.rs | 10 ++ 4 files changed, 170 insertions(+), 1 deletion(-) diff --git a/crates/collab/tests/integration/remote_editing_collaboration_tests.rs b/crates/collab/tests/integration/remote_editing_collaboration_tests.rs index 86bd71f6eb13a5..d82971fe7a6489 100644 --- a/crates/collab/tests/integration/remote_editing_collaboration_tests.rs +++ b/crates/collab/tests/integration/remote_editing_collaboration_tests.rs @@ -21,7 +21,10 @@ use node_runtime::NodeRuntime; use project::{ ProjectPath, debugger::session::ThreadId, - lsp_store::{FormatTrigger, LspFormatTarget}, + lsp_store::{ + FormatTrigger, LspFormatTarget, + log_store::{self, GlobalLogStore}, + }, trusted_worktrees::{PathTrust, TrustedWorktrees}, }; use remote::RemoteClient; @@ -837,6 +840,150 @@ async fn test_ssh_collaboration_formatting_with_prettier( ); } +#[gpui::test(iterations = 10)] +async fn test_ssh_restarting_language_server_replaces_remote_status( + executor: BackgroundExecutor, + cx_a: &mut TestAppContext, + server_cx: &mut TestAppContext, +) { + cx_a.set_name("a"); + server_cx.set_name("server"); + + cx_a.update(|cx| { + release_channel::init(semver::Version::new(0, 0, 0), cx); + }); + server_cx.update(|cx| { + release_channel::init(semver::Version::new(0, 0, 0), cx); + }); + + let mut server = TestServer::start(executor.clone()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let log_store = cx_a.update(|cx| log_store::init(false, cx)); + + let (opts, server_ssh, _) = RemoteClient::fake_server(cx_a, server_cx); + let remote_fs = FakeFs::new(server_cx.executor()); + remote_fs + .insert_tree(path!("/project"), json!({ "a.rs": "fn main() {}" })) + .await; + + client_a.language_registry().add(rust_lang()); + + server_cx.update(HeadlessProject::init); + let languages = Arc::new(LanguageRegistry::new(server_cx.executor())); + languages.add(rust_lang()); + let mut fake_language_servers = languages.register_fake_lsp( + "Rust", + FakeLspAdapter { + name: "the-language-server", + ..Default::default() + }, + ); + let _headless_project = server_cx.new(|cx| { + HeadlessProject::new( + HeadlessAppState { + session: server_ssh, + fs: remote_fs.clone(), + http_client: Arc::new(BlockedHttpClient), + node_runtime: NodeRuntime::unavailable(), + languages, + extension_host_proxy: Arc::new(ExtensionHostProxy::new()), + startup_time: std::time::Instant::now(), + }, + false, + cx, + ) + }); + + let client_ssh = RemoteClient::connect_mock(opts, cx_a).await; + let (project_a, worktree_id) = client_a + .build_ssh_project(path!("/project"), client_ssh, false, cx_a) + .await; + log_store.update(cx_a, |log_store, cx| log_store.add_project(&project_a, cx)); + + let (buffer, _handle) = project_a + .update(cx_a, |project, cx| { + project.open_buffer_with_lsp((worktree_id, rel_path("a.rs")), cx) + }) + .await + .unwrap(); + + let first_server = fake_language_servers.next().await.unwrap(); + let first_server_id = first_server.server.server_id(); + executor.run_until_parked(); + + project_a.read_with(cx_a, |project, cx| { + let statuses = project.language_server_statuses(cx).collect::>(); + assert_eq!(statuses.len(), 1); + assert_eq!(statuses[0].0, first_server_id); + assert_eq!(statuses[0].1.name.0, "the-language-server"); + }); + cx_a.read_global::(|global, cx| { + let log_store = global.0.read(cx); + let matching_server_ids = log_store + .language_servers + .iter() + .filter_map(|(server_id, state)| { + state + .name + .as_ref() + .is_some_and(|name| name.0 == "the-language-server") + .then_some(*server_id) + }) + .collect::>(); + assert_eq!(matching_server_ids, vec![first_server_id]); + }); + + project_a.update(cx_a, |project, cx| { + project.restart_language_servers_for_buffers(vec![buffer], HashSet::default(), cx); + }); + + let restarted_server = fake_language_servers.next().await.unwrap(); + let restarted_server_id = restarted_server.server.server_id(); + assert_ne!(restarted_server_id, first_server_id); + executor.run_until_parked(); + + project_a.read_with(cx_a, |project, cx| { + let statuses = project.language_server_statuses(cx).collect::>(); + assert_eq!( + statuses.len(), + 1, + "restarting a remote language server should replace the previous status entry" + ); + assert_eq!( + statuses[0].0, restarted_server_id, + "restarting a remote language server should publish the replacement server id" + ); + assert_ne!( + statuses[0].0, first_server_id, + "restarting a remote language server should remove the previous server id" + ); + assert_eq!(statuses[0].1.name.0, "the-language-server"); + }); + cx_a.read_global::(|global, cx| { + let log_store = global.0.read(cx); + let matching_server_ids = log_store + .language_servers + .iter() + .filter_map(|(server_id, state)| { + state + .name + .as_ref() + .is_some_and(|name| name.0 == "the-language-server") + .then_some(*server_id) + }) + .collect::>(); + assert_eq!( + matching_server_ids, + vec![restarted_server_id], + "restarting a remote language server should replace the old log store entry" + ); + assert!( + !log_store.language_servers.contains_key(&first_server_id), + "restarting a remote language server should remove the previous log store entry" + ); + }); +} + #[gpui::test] async fn test_remote_server_debugger( cx_a: &mut TestAppContext, diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 811b9aebac6ef4..baa37cde823b8e 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -9910,6 +9910,15 @@ impl LspStore { lsp_store.disk_based_diagnostics_finished(language_server_id, cx) } + proto::update_language_server::Variant::Removed(_) => { + lsp_store + .language_server_statuses + .remove(&language_server_id); + lsp_store.cleanup_lsp_data(language_server_id); + cx.emit(LspStoreEvent::LanguageServerRemoved(language_server_id)); + cx.notify(); + } + non_lsp @ proto::update_language_server::Variant::StatusUpdate(_) | non_lsp @ proto::update_language_server::Variant::RegisteredForBuffer(_) | non_lsp @ proto::update_language_server::Variant::MetadataUpdated(_) => { diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index ff9ec4d4e64fb5..f7aa01c02b1386 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -608,6 +608,7 @@ message UpdateLanguageServer { StatusUpdate status_update = 9; RegisteredForBuffer registered_for_buffer = 10; ServerMetadataUpdated metadata_updated = 11; + ServerRemoved removed = 12; } } @@ -644,6 +645,8 @@ message LspDiskBasedDiagnosticsUpdating {} message LspDiskBasedDiagnosticsUpdated {} +message ServerRemoved {} + message StatusUpdate { optional string message = 1; oneof status { diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 098993debad82e..82ba504963b5ca 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -416,6 +416,16 @@ impl HeadlessProject { log_store.remove_language_server(*id, cx); }); } + self.session + .send(proto::UpdateLanguageServer { + project_id: REMOTE_SERVER_PROJECT_ID, + server_name: None, + language_server_id: id.to_proto(), + variant: Some(proto::update_language_server::Variant::Removed( + proto::ServerRemoved {}, + )), + }) + .log_err(); } LspStoreEvent::LanguageServerUpdate { language_server_id, From dfda0732ad88e89900979c16660378d8651fc4a8 Mon Sep 17 00:00:00 2001 From: David Alecrim <35930364+davidalecrim1@users.noreply.github.com> Date: Sun, 31 May 2026 16:27:21 -0300 Subject: [PATCH 08/39] vim: Support matching bracket motion in multibuffers (#54634) Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #54209 Release Notes: - Fixed vim `%` (matching bracket) motion not working in multibuffers --------- Co-authored-by: Cole Miller Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com> --- crates/vim/src/motion.rs | 96 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 6 deletions(-) diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 28669d4890a2e7..06ca25875204e9 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -2609,9 +2609,6 @@ fn matching( display_point: DisplayPoint, match_quotes: bool, ) -> DisplayPoint { - if !map.is_singleton() { - return display_point; - } // https://github.com/vim/vim/blob/1d87e11a1ef201b26ed87585fba70182ad0c468a/runtime/doc/motion.txt#L1200 let display_point = map.clip_at_line_end(display_point); let point = display_point.to_point(map); @@ -2648,6 +2645,11 @@ fn matching( let is_quote_char = |ch: char| matches!(ch, '\'' | '"' | '`'); + // The filter receives buffer-local ranges, not multibuffer offsets. + let buffer_offset = snapshot + .point_to_buffer_offset(offset) + .map(|(_, buffer_offset)| buffer_offset); + let make_range_filter = |require_on_bracket: bool| { move |buffer: &language::BufferSnapshot, opening_range: Range, @@ -2664,8 +2666,9 @@ fn matching( if require_on_bracket { // Attempt to find the smallest enclosing bracket range that also contains // the offset, which only happens if the cursor is currently in a bracket. - opening_range.contains(&BufferOffset(offset.0)) - || closing_range.contains(&BufferOffset(offset.0)) + buffer_offset.is_some_and(|buffer_offset| { + opening_range.contains(&buffer_offset) || closing_range.contains(&buffer_offset) + }) } else { true } @@ -3406,7 +3409,9 @@ mod test { state::Mode, test::{NeovimBackedTestContext, VimTestContext}, }; - use editor::Inlay; + use editor::{ + Editor, EditorMode, Inlay, MultiBuffer, test::editor_test_context::EditorTestContext, + }; use gpui::KeyBinding; use indoc::indoc; use language::Point; @@ -3553,6 +3558,85 @@ mod test { cx.shared_state().await.assert_eq("func boop(ˇ) {\n}"); } + #[gpui::test] + async fn test_matching_in_multibuffer(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + let (editor, cx) = cx.add_window_view(|window, cx| { + let multi_buffer = MultiBuffer::build_multi( + [ + ( + "fn a() {\n let x = 1;\n}\n", + vec![Point::row_range(0..3)], + ), + ( + "fn b() {\n let y = 2;\n}\n", + vec![Point::row_range(0..3)], + ), + ], + cx, + ); + + let buffer_ids = multi_buffer + .read(cx) + .snapshot(cx) + .excerpts() + .map(|excerpt| excerpt.context.start.buffer_id) + .collect::>(); + + for buffer_id in buffer_ids { + if let Some(buffer) = multi_buffer.read(cx).buffer(buffer_id) { + buffer.update(cx, |buffer, cx| { + buffer.set_language(Some(language::rust_lang()), cx); + }); + } + } + + Editor::new(EditorMode::full(), multi_buffer, None, window, cx) + }); + + let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await; + + cx.simulate_keystrokes("j j j j f {"); + cx.assert_excerpts_with_selections(indoc! {" + [EXCERPT] + fn a() { + let x = 1; + } + [EXCERPT] + fn b() ˇ{ + let y = 2; + } + " + }); + + cx.simulate_keystrokes("%"); + cx.assert_excerpts_with_selections(indoc! {" + [EXCERPT] + fn a() { + let x = 1; + } + [EXCERPT] + fn b() { + let y = 2; + ˇ} + " + }); + + cx.simulate_keystrokes("%"); + cx.assert_excerpts_with_selections(indoc! {" + [EXCERPT] + fn a() { + let x = 1; + } + [EXCERPT] + fn b() ˇ{ + let y = 2; + } + " + }); + } + #[gpui::test] async fn test_matching_quotes_disabled(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; From 315d474c2e2ab1a06ed3857a426d7069f7c31e34 Mon Sep 17 00:00:00 2001 From: Henrique Ferreiro Date: Sun, 31 May 2026 21:28:18 +0200 Subject: [PATCH 09/39] Honor anchored patterns in .git/info/exclude (#57779) Patterns in `.git/info/exclude` that contain a slash (e.g. `.claude/worktrees`) are anchored: Git matches them relative to the project root. Zed was instead matching them relative to the `.git/info/` directory that the file lives in, so they matched nothing and had no effect. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Support anchored patterns in .git/info/exclude --------- Co-authored-by: Cole Miller Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com> --- crates/worktree/src/worktree.rs | 16 +++-- .../tests/integration/worktree_tests.rs | 63 +++++++++++++++++++ 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index ce2f34bc78d52d..0ee6f0cb310ed1 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -3340,12 +3340,16 @@ async fn is_dot_git(path: &Path, fs: &dyn Fs) -> bool { } async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result { + let parent = abs_path.parent().unwrap_or_else(|| Path::new("/")); + build_gitignore_with_root(abs_path, parent, fs).await +} + +async fn build_gitignore_with_root(abs_path: &Path, root: &Path, fs: &dyn Fs) -> Result { let contents = fs .load(abs_path) .await .with_context(|| format!("failed to load gitignore file at {}", abs_path.display()))?; - let parent = abs_path.parent().unwrap_or_else(|| Path::new("/")); - let mut builder = GitignoreBuilder::new(parent); + let mut builder = GitignoreBuilder::new(root); for line in contents.lines() { builder.add_line(Some(abs_path.into()), line)?; } @@ -5329,7 +5333,9 @@ impl BackgroundScanner { // Load gitignores asynchronously (outside the lock) let mut loaded_excludes: Vec<(Arc, Arc)> = Vec::new(); for (work_dir_abs_path, exclude_abs_path) in excludes_to_load { - if let Ok(current_exclude) = build_gitignore(&exclude_abs_path, self.fs.as_ref()).await + if let Ok(current_exclude) = + build_gitignore_with_root(&exclude_abs_path, &work_dir_abs_path, self.fs.as_ref()) + .await { loaded_excludes.push((work_dir_abs_path, Arc::new(current_exclude))); } @@ -5641,7 +5647,9 @@ async fn discover_ancestor_git_repo( let (_, common_dir_abs_path) = discover_git_paths(&dot_git_abs_path, fs.as_ref()).await; let repo_exclude_abs_path = common_dir_abs_path.join(REPO_EXCLUDE); - if let Ok(repo_exclude) = build_gitignore(&repo_exclude_abs_path, fs.as_ref()).await { + if let Ok(repo_exclude) = + build_gitignore_with_root(&repo_exclude_abs_path, ancestor, fs.as_ref()).await + { exclude = Some(Arc::new(repo_exclude)); } diff --git a/crates/worktree/tests/integration/worktree_tests.rs b/crates/worktree/tests/integration/worktree_tests.rs index 2ae248ad0e4053..7691a68392c8f7 100644 --- a/crates/worktree/tests/integration/worktree_tests.rs +++ b/crates/worktree/tests/integration/worktree_tests.rs @@ -3017,6 +3017,69 @@ async fn test_repo_exclude(executor: BackgroundExecutor, cx: &mut TestAppContext }); } +#[gpui::test] +async fn test_repo_exclude_anchored_pattern(executor: BackgroundExecutor, cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(executor); + let project_dir = Path::new(path!("/project")); + fs.insert_tree( + project_dir, + json!({ + ".git": { + "info": { + "exclude": "vendor/cache" + } + }, + "vendor": { + "cache": { + "blob.bin": "", + }, + "keep.txt": "", + }, + "elsewhere": { + "vendor": { + "cache": { + "blob.bin": "", + }, + }, + }, + }), + ) + .await; + + let worktree = Worktree::local( + project_dir, + true, + fs.clone(), + Default::default(), + true, + WorktreeId::from_proto(0), + &mut cx.to_async(), + ) + .await + .unwrap(); + worktree + .update(cx, |worktree, _| { + worktree.as_local().unwrap().scan_complete() + }) + .await; + cx.run_until_parked(); + + // An anchored pattern (containing a `/`) is matched relative to the work + // tree root, so only the top-level `vendor/cache` is ignored. + worktree.update(cx, |worktree, _cx| { + check_worktree_entries( + worktree, + WorktreeExpectations { + ignored_paths: &["vendor/cache"], + tracked_paths: &["vendor/keep.txt", "elsewhere/vendor/cache"], + ..Default::default() + }, + ); + }); +} + #[derive(Default)] struct WorktreeExpectations { excluded_paths: &'static [&'static str], From 8734d0190359251c4cd9e97302ca7dce300174bf Mon Sep 17 00:00:00 2001 From: Xiaobo Liu Date: Mon, 1 Jun 2026 04:04:06 +0800 Subject: [PATCH 10/39] git_ui: Update section header checkboxes immediately on stage/unstage all (#57148) Release Notes: - Fixed Stash All / Unstash All checkbox UI delay issue --------- Signed-off-by: Xiaobo Liu --- crates/git_ui/src/git_panel.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 8e547921842838..b58a7572ac9024 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -1743,17 +1743,14 @@ impl GitPanel { cx.spawn({ async move |this, cx| { let result = this - .update(cx, |this, cx| { - let task = active_repository.update(cx, |repo, cx| { + .update(cx, |_this, cx| { + active_repository.update(cx, |repo, cx| { if stage { repo.stage_all(cx) } else { repo.unstage_all(cx) } - }); - this.update_counts(active_repository.read(cx)); - cx.notify(); - task + }) })? .await; @@ -1761,6 +1758,7 @@ impl GitPanel { if let Err(err) = result { this.show_error_toast(if stage { "add" } else { "reset" }, err, cx); } + this.update_counts(active_repository.read(cx)); cx.notify() }) } From 7f826e82c7331ef386ed959d89623fc165fab44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Raz=20Guzm=C3=A1n=20Macedo?= Date: Sun, 31 May 2026 16:05:41 -0600 Subject: [PATCH 11/39] Fix grammatical errors throughout the documentation (#58183) Fixes a batch of grammatical mistakes found across the `docs/` folder: typos/misspellings, subject-verb agreement errors, missing and duplicated words, article errors (`a`/`an`), wrong word forms, and punctuation issues (doubled periods, unclosed parentheses). Scope is limited to prose in `docs/src/**`; no behavior, settings, or code changes. 33 files updated, 49 lines changed. Representative fixes: - `globs.md`: "features varies" -> "features"; "platforms libc" -> "platform's libc". - `installation.md`: "the follow macOS releases" -> "the following macOS releases". - `key-bindings.md`: "command pallets" -> "command palette's". - `linux.md`: capitalization after a period; "able to the environment variable" -> "able to set the environment variable"; "These feature also requires" -> "This feature also requires". - `multibuffers.md`: "Window/Linux" -> "Windows/Linux"; added a missing closing parenthesis. - `reference/all-settings.md`: several agreement/article/missing-word fixes plus an unclosed parenthesis. - `languages/*`: "partent" -> "parent", "complimentary" -> "complementary", "setup" -> "set up", "is enabled" -> "are enabled", and similar. Release Notes: - N/A --- docs/src/ai/agent-panel.md | 2 +- docs/src/ai/billing.md | 2 +- docs/src/ai/edit-prediction.md | 2 +- docs/src/ai/external-agents.md | 2 +- docs/src/ai/llm-providers.md | 2 +- docs/src/ai/mcp.md | 4 ++-- docs/src/configuring-languages.md | 2 +- docs/src/debugger.md | 2 +- docs/src/extensions/languages.md | 2 +- docs/src/git.md | 2 +- docs/src/globs.md | 6 +++--- docs/src/installation.md | 2 +- docs/src/key-bindings.md | 2 +- docs/src/languages.md | 2 +- docs/src/languages/cpp.md | 2 +- docs/src/languages/lua.md | 2 +- docs/src/languages/ocaml.md | 4 ++-- docs/src/languages/php.md | 2 +- docs/src/languages/python.md | 2 +- docs/src/languages/r.md | 2 +- docs/src/languages/ruby.md | 2 +- docs/src/languages/scala.md | 2 +- docs/src/languages/sml.md | 2 +- docs/src/languages/svelte.md | 2 +- docs/src/linux.md | 6 +++--- docs/src/multibuffers.md | 4 ++-- docs/src/performance.md | 2 +- docs/src/project-panel.md | 2 +- docs/src/reference/all-settings.md | 20 ++++++++++---------- docs/src/remote-development.md | 2 +- docs/src/repl.md | 2 +- docs/src/snippets.md | 2 +- docs/src/visual-customization.md | 2 +- 33 files changed, 49 insertions(+), 49 deletions(-) diff --git a/docs/src/ai/agent-panel.md b/docs/src/ai/agent-panel.md index e657f6b6179dd1..b7d17bba887f71 100644 --- a/docs/src/ai/agent-panel.md +++ b/docs/src/ai/agent-panel.md @@ -255,7 +255,7 @@ Copying an image and pasting it is also supported. Zed surfaces how many tokens you are consuming for your currently active thread near the profile selector in the panel's message editor. Once you approach the model's context window, a banner appears above the message editor suggesting to start a new thread with the current one summarized and added as context. -You can also do this at any time with an ongoing thread via the "Agent Options" menu on the top right, where you'll see a "New from Summary" button, as well as simply @-mentioning a past thread in a new one.. +You can also do this at any time with an ongoing thread via the "Agent Options" menu on the top right, where you'll see a "New from Summary" button, as well as simply @-mentioning a past thread in a new one. ## Changing Models {#changing-models} diff --git a/docs/src/ai/billing.md b/docs/src/ai/billing.md index d5fc6750e83827..eb3a7875e68094 100644 --- a/docs/src/ai/billing.md +++ b/docs/src/ai/billing.md @@ -48,7 +48,7 @@ Zed Business consolidates your team's costs. Seat licenses and AI usage for all ### Billing dashboard {#dashboard} -Owners and admins can access billing information at [dashboard.zed.dev](https://dashboard.zed.dev). The dashboard shows the plan you're currently on and offers jumping off points to update billing details, such as the billing name and address, as well as payment information. You can also access your invoices history, accessible through the Orb billing portal. +Owners and admins can access billing information at [dashboard.zed.dev](https://dashboard.zed.dev). The dashboard shows the plan you're currently on and offers jumping off points to update billing details, such as the billing name and address, as well as payment information. You can also access your invoice history, accessible through the Orb billing portal. ### AI usage {#ai-usage} diff --git a/docs/src/ai/edit-prediction.md b/docs/src/ai/edit-prediction.md index 1f5b3e8adcee44..fda36a39cdd6bf 100644 --- a/docs/src/ai/edit-prediction.md +++ b/docs/src/ai/edit-prediction.md @@ -111,7 +111,7 @@ After that, `alt-tab` remains available for accepting edit predictions, and on L To move both default accept bindings to something else, unbind them and add your replacement: -Open the keymap editor with {#action zed::OpenKeymap} ({#kb zed::OpenKeymap}), search for `AcceptEditPrediction`, right click on the binding for `tab` and delete it. Then right click on the binding for `alt-tab`, select "Edit", and record your desired keystrokes before hitting saving. +Open the keymap editor with {#action zed::OpenKeymap} ({#kb zed::OpenKeymap}), search for `AcceptEditPrediction`, right click on the binding for `tab` and delete it. Then right click on the binding for `alt-tab`, select "Edit", and record your desired keystrokes before saving. Alternatively, you can put the following in your `keymap.json`: diff --git a/docs/src/ai/external-agents.md b/docs/src/ai/external-agents.md index dd3f68d5b348b2..81a4ee5d3da803 100644 --- a/docs/src/ai/external-agents.md +++ b/docs/src/ai/external-agents.md @@ -237,7 +237,7 @@ It's also possible to customize environment variables for registry-installed age ## Debugging Agents -When using external agents in Zed, you can access the debug view via with {#action dev::OpenAcpLogs} from the Command Palette. +When using external agents in Zed, you can access the debug view via {#action dev::OpenAcpLogs} from the Command Palette. This lets you see the messages being sent and received between Zed and the agent. ![The debug view for ACP logs.](https://zed.dev/img/acp/acp-logs.webp) diff --git a/docs/src/ai/llm-providers.md b/docs/src/ai/llm-providers.md index 3c08a960da8a6f..0c8645648c830a 100644 --- a/docs/src/ai/llm-providers.md +++ b/docs/src/ai/llm-providers.md @@ -515,7 +515,7 @@ One such service is [Ollama Turbo](https://ollama.com/turbo). To configure Zed t 4. Paste your API key and press enter. 5. For the API URL enter `https://ollama.com` -Zed will also use the `OLLAMA_API_KEY` environment variables if defined. +Zed will also use the `OLLAMA_API_KEY` environment variable if defined. ### OpenAI {#openai} diff --git a/docs/src/ai/mcp.md b/docs/src/ai/mcp.md index 6582508f5ca101..c3fedb94402bea 100644 --- a/docs/src/ai/mcp.md +++ b/docs/src/ai/mcp.md @@ -81,7 +81,7 @@ For example, the GitHub MCP extension requires you to add a [Personal Access Tok In the case of custom servers, make sure you check the provider documentation to determine what type of command, arguments, and environment variables need to be added to the JSON. To check if your MCP server is properly configured, go to the Agent Panel's settings view and watch the indicator dot next to its name. -If they're running correctly, the indicator will be green and its tooltip will say "Server is active". +If it's running correctly, the indicator will be green and its tooltip will say "Server is active". If not, other colors and tooltip messages will indicate what is happening. ### Agent Panel Usage @@ -162,7 +162,7 @@ For details on what configuration is shared between Zed and external agents, see ### Error Handling -When a MCP server encounters an error while processing a tool call, the agent receives the error message directly and the operation fails. +When an MCP server encounters an error while processing a tool call, the agent receives the error message directly and the operation fails. Common error scenarios include: - Invalid parameters passed to the tool diff --git a/docs/src/configuring-languages.md b/docs/src/configuring-languages.md index d4e76534fd1b66..5113453a7ba8d1 100644 --- a/docs/src/configuring-languages.md +++ b/docs/src/configuring-languages.md @@ -249,7 +249,7 @@ Most of the servers would rely on this way of configuring only. } ``` -Apart of the LSP-related server configuration options, certain servers in Zed allow configuring the way binary is launched by Zed. +Apart from the LSP-related server configuration options, certain servers in Zed allow configuring the way binary is launched by Zed. Language servers are automatically downloaded or launched if found in your path, if you wish to specify an explicit alternate binary you can specify that in settings: diff --git a/docs/src/debugger.md b/docs/src/debugger.md index bf05de0f6ccccf..b503ff09849fc2 100644 --- a/docs/src/debugger.md +++ b/docs/src/debugger.md @@ -80,7 +80,7 @@ Which one you choose depends on what you are trying to achieve. When launching a new instance, Zed (and the underlying debug adapter) can often do a better job at picking up the debug information compared to attaching to an existing process, since it controls the lifetime of a whole program. Running unit tests or a debug build of your application is a good use case for launching. -Compared to launching, attaching to an existing process might seem inferior, but that's far from truth; there are cases where you cannot afford to restart your program, because for example, the bug is not reproducible outside of a production environment or some other circumstances. +Compared to launching, attaching to an existing process might seem inferior, but that's far from the truth; there are cases where you cannot afford to restart your program, because for example, the bug is not reproducible outside of a production environment or some other circumstances. ## Configuration diff --git a/docs/src/extensions/languages.md b/docs/src/extensions/languages.md index 121357306e7355..59f20d16de81b1 100644 --- a/docs/src/extensions/languages.md +++ b/docs/src/extensions/languages.md @@ -528,7 +528,7 @@ Each rule in the `semantic_token_rules` array is defined as follows: - `foreground_color`: The foreground color to use for the token type, in hex format (e.g., `"#ff0000"`). - `background_color`: The background color to use for the token type, in hex format (e.g., `"#ff0000"`). - `underline`: A boolean or color to underline with, in hex format. If `true`, then the token will be underlined with the text color. -- `strikethrough`: A boolean or color to strikethrough with, in hex format. If `true`, then the token have a strikethrough with the text color. +- `strikethrough`: A boolean or color to strikethrough with, in hex format. If `true`, then the token will have a strikethrough with the text color. - `font_weight`: One of `"normal"`, `"bold"`. - `font_style`: One of `"normal"`, `"italic"`. diff --git a/docs/src/git.md b/docs/src/git.md index 0d0fcc1a4e8caf..1f9dc4fb9acdae 100644 --- a/docs/src/git.md +++ b/docs/src/git.md @@ -334,7 +334,7 @@ You can configure multiple custom providers if you work with several self-hosted Zed also has a Copy Permalink feature to create a permanent link to a code snippet on your Git hosting service. These links are useful for sharing a specific line or range of lines in a file at a specific commit. Trigger this action via the [Command Palette](./getting-started.md#command-palette) (search for `permalink`), -by creating a [custom key bindings](key-bindings.md#custom-key-bindings) to the +by creating [custom key bindings](key-bindings.md#custom-key-bindings) for the `editor::CopyPermalinkToLine` or `editor::OpenPermalinkToLine` actions or by simply right clicking and selecting `Copy Permalink` with line(s) selected in your editor. diff --git a/docs/src/globs.md b/docs/src/globs.md index f1fb584ee568d2..e72c7a92a31272 100644 --- a/docs/src/globs.md +++ b/docs/src/globs.md @@ -14,9 +14,9 @@ Zed uses two different rust crates for matching glob patterns: - [ignore crate](https://docs.rs/ignore/latest/ignore/) for matching glob patterns stored in `.gitignore` files - [glob crate](https://docs.rs/glob/latest/glob/) for matching file paths in Zed -While simple expressions are portable across environments (e.g. running `ls *.py` or `*.tmp` in a gitignore) there is significant divergence in the support for and syntax of more advanced features varies (character classes, exclusions, `**`, etc) across implementations. For the rest of this document we will be describing globs as supported in Zed via the `glob` crate implementation. Please see [References](#references) below for documentation links for glob pattern syntax for `.gitignore`, shells and other programming languages. +While simple expressions are portable across environments (e.g. running `ls *.py` or `*.tmp` in a gitignore) there is significant divergence in the support for and syntax of more advanced features (character classes, exclusions, `**`, etc) across implementations. For the rest of this document we will be describing globs as supported in Zed via the `glob` crate implementation. Please see [References](#references) below for documentation links for glob pattern syntax for `.gitignore`, shells and other programming languages. -The `glob` crate is implemented entirely in rust and does not rely on the `glob` / `fnmatch` interfaces provided by your platforms libc. This means that globs in Zed should behave similarly with across platforms. +The `glob` crate is implemented entirely in rust and does not rely on the `glob` / `fnmatch` interfaces provided by your platform's libc. This means that globs in Zed should behave similarly across platforms. ## Introduction @@ -71,7 +71,7 @@ If instead you wanted to restrict yourself only to [Zed Language-Specific Docume When using the "Include" / "Exclude" filters on a Project Search each glob is wrapped in implicit wildcards. For example to exclude any files with license in the path or filename from your search just type `license` in the exclude box. Behind the scenes Zed transforms `license` to `**license**`. This means that files named `license.*`, `*.license` or inside a `license` subdirectory will all be filtered out. This enables users to easily filter for `*.ts` without having to remember to type `**/*.ts` every time. -Alternatively, if in your Zed settings you wanted a [`file_types`](./reference/all-settings.md#file-types) override which only applied to a certain directory you must explicitly include the wildcard globs. For example, if you had a directory of template files with the `html` extension that you wanted to recognize as Jinja2 template you could use the following: +Alternatively, if in your Zed settings you wanted a [`file_types`](./reference/all-settings.md#file-types) override which only applied to a certain directory you must explicitly include the wildcard globs. For example, if you had a directory of template files with the `html` extension that you wanted to recognize as a Jinja2 template you could use the following: ```json [settings] { diff --git a/docs/src/installation.md b/docs/src/installation.md index 2c003da75574e5..152ba85102b81a 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -67,7 +67,7 @@ If this script is insufficient for your use case, you run into problems running ### macOS -Zed supports the follow macOS releases: +Zed supports the following macOS releases: | Version | Codename | Apple Status | Zed Status | | ------------- | -------- | -------------- | ------------------- | diff --git a/docs/src/key-bindings.md b/docs/src/key-bindings.md index ae64ab00b8ccd8..490293c9eba398 100644 --- a/docs/src/key-bindings.md +++ b/docs/src/key-bindings.md @@ -28,7 +28,7 @@ For more information, see the documentation for [Vim mode](./vim.md) and [Helix ## Keymap Editor -You can access the keymap editor through the {#kb zed::OpenKeymap} action or by running {#action zed::OpenKeymap} action from the command palette. You can easily add or change a keybind for an action with the `Change Keybinding` or `Add Keybinding` button on the command pallets left bottom corner. +You can access the keymap editor through the {#kb zed::OpenKeymap} action or by running {#action zed::OpenKeymap} action from the command palette. You can easily add or change a keybind for an action with the `Change Keybinding` or `Add Keybinding` button on the command palette's left bottom corner. In there, you can see all of the existing actions in Zed as well as the associated keybindings set to them by default. diff --git a/docs/src/languages.md b/docs/src/languages.md index b720e725cca816..7c0c618871ade6 100644 --- a/docs/src/languages.md +++ b/docs/src/languages.md @@ -6,7 +6,7 @@ description: "Overview of programming language support in Zed, including built-i # Language Support in Zed Zed supports hundreds of programming languages and text formats. -Some work out-of-the box and others rely on 3rd party extensions. +Some work out-of-the-box and others rely on 3rd party extensions. > The ones included out-of-the-box, natively built into Zed, are marked with \*. diff --git a/docs/src/languages/cpp.md b/docs/src/languages/cpp.md index 1f63460160cc1e..44025da5544315 100644 --- a/docs/src/languages/cpp.md +++ b/docs/src/languages/cpp.md @@ -80,7 +80,7 @@ You can pass any number of arguments to clangd. To see a full set of available o ## Formatting -By default Zed will use the `clangd` language server for formatting C++ code. The Clangd is the same as the `clang-format` CLI tool. To configure this you can add a `.clang-format` file. For example: +By default Zed will use the `clangd` language server for formatting C++ code. Its formatter is the same as the `clang-format` CLI tool. To configure this you can add a `.clang-format` file. For example: ```yaml # yaml-language-server: $schema=https://json.schemastore.org/clang-format-21.x.json diff --git a/docs/src/languages/lua.md b/docs/src/languages/lua.md index 27d3f613634547..861dbb710bfe94 100644 --- a/docs/src/languages/lua.md +++ b/docs/src/languages/lua.md @@ -27,7 +27,7 @@ See [LuaLS Settings Documentation](https://luals.github.io/wiki/settings/) for a ### LuaCATS Definitions -LuaLS can provide enhanced LSP autocompletion suggestions and type validation with the help of LuaCATS (Lua Comment and Type System) definitions. These definitions are available for many common Lua libraries, and local paths containing them can be specified via `workspace.library` in `luarc.json`. You can do this via relative paths if you checkout your definitions into the same partent directory of your project (`../playdate-luacats`, `../love2d`, etc). Alternatively you can create submodule(s) inside your project for each LuaCATS definition repo. +LuaLS can provide enhanced LSP autocompletion suggestions and type validation with the help of LuaCATS (Lua Comment and Type System) definitions. These definitions are available for many common Lua libraries, and local paths containing them can be specified via `workspace.library` in `luarc.json`. You can do this via relative paths if you checkout your definitions into the same parent directory of your project (`../playdate-luacats`, `../love2d`, etc). Alternatively you can create submodule(s) inside your project for each LuaCATS definition repo. ### LÖVE (Love2D) {#love2d} diff --git a/docs/src/languages/ocaml.md b/docs/src/languages/ocaml.md index b78302b77bf9bc..d6e597f255d038 100644 --- a/docs/src/languages/ocaml.md +++ b/docs/src/languages/ocaml.md @@ -12,13 +12,13 @@ OCaml support is available through the [OCaml extension](https://github.com/zed- ## Setup Instructions -If you have the development environment already setup, you can skip to [Launching Zed](#launching-zed) +If you have the development environment already set up, you can skip to [Launching Zed](#launching-zed) ### Using Opam Opam is the official package manager for OCaml and is highly recommended for getting started with OCaml. To get started using Opam, please follow the instructions provided [here](https://ocaml.org/install). -Once you install opam and setup a switch with your development environment as per the instructions, you can proceed. +Once you install opam and set up a switch with your development environment as per the instructions, you can proceed. ### Launching Zed diff --git a/docs/src/languages/php.md b/docs/src/languages/php.md index b83e75fb290c5a..8ce513ba05e4dd 100644 --- a/docs/src/languages/php.md +++ b/docs/src/languages/php.md @@ -157,7 +157,7 @@ These are common troubleshooting tips, in case you run into issues: - Ensure that you have Xdebug installed for the version of PHP you're running. - Ensure that Xdebug is configured to run in `debug` mode. - Ensure that Xdebug is actually starting a debugging session. -- Ensure that the host and port matches between Xdebug and Zed. +- Ensure that the host and port match between Xdebug and Zed. - Look at the diagnostics log by using the `xdebug_info()` function in the page you're trying to debug. ## Using the Tailwind CSS Language Server with PHP diff --git a/docs/src/languages/python.md b/docs/src/languages/python.md index 4687cf15d866d9..0dd931f5140b7d 100644 --- a/docs/src/languages/python.md +++ b/docs/src/languages/python.md @@ -106,7 +106,7 @@ See: [Working with Language Servers](https://zed.dev/docs/configuring-languages# [basedpyright](https://docs.basedpyright.com/latest/) is the primary Python language server in Zed beginning with Zed v0.204.0. It provides core language server functionality like navigation (go to definition/find all references) and type checking. Compared to Pyright, it adds support for additional language server features (like inlay hints) and checking rules. -Note that while basedpyright in isolation defaults to the `recommended` [type-checking mode](https://docs.basedpyright.com/latest/benefits-over-pyright/better-defaults/#typecheckingmode), Zed configures it to use the less-strict `standard` mode by default, which matches the behavior of Pyright. You can set the type-checking mode for your project using the `typeCheckingMode` setting in `pyrightconfig.json` or `pyproject.toml`, which will override Zed's default. Read on more for more details about how to configure basedpyright. +Note that while basedpyright in isolation defaults to the `recommended` [type-checking mode](https://docs.basedpyright.com/latest/benefits-over-pyright/better-defaults/#typecheckingmode), Zed configures it to use the less-strict `standard` mode by default, which matches the behavior of Pyright. You can set the type-checking mode for your project using the `typeCheckingMode` setting in `pyrightconfig.json` or `pyproject.toml`, which will override Zed's default. Read on for more details about how to configure basedpyright. #### Basedpyright Configuration diff --git a/docs/src/languages/r.md b/docs/src/languages/r.md index 1995bb7c4a3502..a40cda0242dd4b 100644 --- a/docs/src/languages/r.md +++ b/docs/src/languages/r.md @@ -142,7 +142,7 @@ TBD: R REPL Docs ### Ark Installation To use the Zed REPL with R you need to install [Ark](https://github.com/posit-dev/ark), an R Kernel for Jupyter applications. -You can down the latest version from the [Ark GitHub Releases](https://github.com/posit-dev/ark/releases) and then extract the `ark` binary to a directory in your `PATH`. +You can download the latest version from the [Ark GitHub Releases](https://github.com/posit-dev/ark/releases) and then extract the `ark` binary to a directory in your `PATH`. For example to install the latest non-debug build: diff --git a/docs/src/languages/ruby.md b/docs/src/languages/ruby.md index 6f8fc1c4957435..475c7e26cd08e2 100644 --- a/docs/src/languages/ruby.md +++ b/docs/src/languages/ruby.md @@ -30,7 +30,7 @@ They both have an overlapping feature set of autocomplete, diagnostics, code act In addition to these two language servers, Zed also supports: -- [rubocop](https://github.com/rubocop/rubocop) which is a static code analyzer and linter for Ruby. Under the hood, it's also used by Zed as a language server, but its functionality is complimentary to that of solargraph and ruby-lsp. +- [rubocop](https://github.com/rubocop/rubocop) which is a static code analyzer and linter for Ruby. Under the hood, it's also used by Zed as a language server, but its functionality is complementary to that of solargraph and ruby-lsp. - [sorbet](https://sorbet.org/) which is a static type checker for Ruby with a custom gradual type system. - [steep](https://github.com/soutaro/steep) which is a static type checker for Ruby that uses Ruby Signature (RBS). - [Herb](https://herb-tools.dev) which is a language server for ERB files. diff --git a/docs/src/languages/scala.md b/docs/src/languages/scala.md index 0f3b0018bb1365..d0b2b1f3e7b933 100644 --- a/docs/src/languages/scala.md +++ b/docs/src/languages/scala.md @@ -27,7 +27,7 @@ Behavior of the Metals language server can be controlled with: - `.scalafix.conf` file - See [Scalafix Configuration](https://scalacenter.github.io/scalafix/docs/users/configuration.html) - `.scalafmt.conf` file - See [Scalafmt Configuration](https://scalameta.org/scalafmt/docs/configuration.html) -You can place these files in the root of your project or specifying their location in the Metals configuration. See [Metals User Configuration](https://scalameta.org/metals/docs/editors/user-configuration) for more. +You can place these files in the root of your project or specify their location in the Metals configuration. See [Metals User Configuration](https://scalameta.org/metals/docs/editors/user-configuration) for more.