Compare commits

...

18 Commits

Author SHA1 Message Date
Zed Bot
364862f667 Bump to 0.168.3 for @osiewicz 2025-01-13 22:44:47 +00:00
gcp-cherry-pick-bot[bot]
63ac307a99 editor: Adjust offset of the opened jump target in the multibuffer (cherry-pick #23091) (#23100)
Cherry-picked editor: Adjust offset of the opened jump target in the
multibuffer (#23091)

This PR fixes an issue with jumping from multi_buffer to a file; namely,
the scroll offset of the opened buffer used to match the position within
the multibuffer, but it broke a while back. This is because we were
opening a buffer without providing the data about the origin scroll
offset.

Closes #ISSUE

Release Notes:

- Fixed a bug where the relative position of an excerpt within the
multibuffer was not accounted for while jumping to the buffer, causing
the clicked line to drastically change position on screen.

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-01-13 23:40:57 +01:00
gcp-cherry-pick-bot[bot]
d9a30905db terminal: Fix unresponsive buttons on load until center pane is clicked + Auto-focus docked terminal on load if no other item is focused (cherry-pick #23039) (#23093)
Cherry-picked terminal: Fix unresponsive buttons on load until center
pane is clicked + Auto-focus docked terminal on load if no other item is
focused (#23039)

Closes #23006

This PR should have been split into two, but since the changes are
related, I merged them into one.

1. On load, the title bar actions and bottom bar toggles are
unresponsive until the center pane is clicked. This happens because the
terminal captures focus (even if it's closed) long after the workspace
sets focus to itself during loading.

The issue was in the `focus_view` call used in the `new` method of
`TerminalPanel`. Since new terminal views can be created behind the
scenes (i.e., without the terminal being visible to the user), we
shouldn't handle focus for the terminal in this case. Removing
`focus_view` from the `new` method has no impact on the existing
terminal focusing logic. I've tested scenarios such as creating new
terminals, splitting terminals, zooming, etc., and everything works as
expected.

2. Currently, on load, docked terminals do not automatically focus when
they are only visible item to the user. This PR implements it.

Before/After:

1. When only the dock terminal is visible on load. Terminal is focused.

<img

src="https://github.com/user-attachments/assets/af8848aa-ccb5-4a3b-b2c6-486e8d588f09"
alt="image" height="280px" />

<img

src="https://github.com/user-attachments/assets/8f76ca2e-de29-4cc0-979b-749b50a00bbd"
alt="image" height="280px" />

2. When other items are visible along with the dock terminal on load.
Editor is focused.

<img

src="https://github.com/user-attachments/assets/d3248272-a75d-4763-9e99-defb8a369b68"
alt="image" height="280px" />

<img

src="https://github.com/user-attachments/assets/fba5184e-1ab2-406c-9669-b141aaf1c32f"
alt="image" height="280px" />

3. Multiple tabs along with split panes. Last terminal is focused.

<img

src="https://github.com/user-attachments/assets/7a10c3cf-8bb3-4b88-aacc-732b678bee19"
alt="image" height="270px" />

<img

src="https://github.com/user-attachments/assets/4d16e98f-9d7a-45f6-8701-d6652e411d3b"
alt="image" height="270px" />

Future:

When a docked terminal is in a zoomed state and Zed is loaded, we should
prioritize focusing on the terminal over the active item (e.g., an
editor) behind it. This hasn't been implemented in this PR because the
zoomed state during the load function is stale. The correct state is
received later via the workspace. I'm still investigating where exactly
this should be handled, so this will be a separate PR.

cc: @SomeoneToIgnore 

Release Notes:

- Fixed unresponsive buttons on load until the center pane is clicked.  
- Added auto-focus for the docked terminal on load when no other item is
focused.

Co-authored-by: tims <0xtimsb@gmail.com>
2025-01-13 23:28:57 +02:00
gcp-cherry-pick-bot[bot]
a47527041e Reuse vtsls logic for completion details display (cherry-pick #23030) (#23033) 2025-01-12 16:45:54 +02:00
gcp-cherry-pick-bot[bot]
4baaa2f26f Do not try to activate the terminal panel twice (cherry-pick #23029) (#23031)
Cherry-picked Do not try to activate the terminal panel twice (#23029)

Closes https://github.com/zed-industries/zed/issues/23023

Fixes terminal pane button opening two terminals on click.

The culprit is in


61115bd047/crates/workspace/src/workspace.rs (L2412-L2417)

* We cannot get any panel by index from the Dock, only an active one
* Both `dock.activate_panel(panel_index, cx);` and `dock.set_open(true,
cx);` do `active_panel.panel.set_active(true, cx);`

So, follow other pane's impls that have `active: bool` property for this
case, e.g.

3ec52d8451/crates/assistant/src/inline_assistant.rs (L2687)

Release Notes:

- Fixed terminal pane button opening two terminals on click

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-01-12 15:57:01 +02:00
Conrad Irwin
14b2cd06a7 Fix panic in vim text-objects (#22753)
Caused by messing up offsets between multi-buffers and excerpts :(

Fixes #22739

Release Notes:

- Fixed a panic in vim text objects in multibuffers
2025-01-08 11:10:33 -05:00
Peter Tripp
5d35ba34e8 v0.168.x stable 2025-01-08 11:00:58 -05:00
gcp-cherry-pick-bot[bot]
b1b8fc02b8 Fix panic in request_multiple_lsp_locally (cherry-pick #22806) (#22807)
Cherry-picked Fix panic in request_multiple_lsp_locally (#22806)

Release Notes:

- Fix a panic after disconnecting from a remote project

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-01-07 23:38:38 -07:00
gcp-cherry-pick-bot[bot]
a8ef5aa87c Reduce amount of workspace serialization happening (cherry-pick #22730) (#22763)
Cherry-picked Reduce amount of workspace serialization happening
(#22730)

Part of https://github.com/zed-industries/zed/issues/16472

Reduces amount of workspace serialization happening by:
* fixing the deserialization logic: now it does not set panels that are
hidden to active
* cleaning up `active_panel_index` for docks that are closed, to avoid
emitting extra events for such closed docks
* adjusting outline panel to drop active editor subscriptions on
deactivation — this way, `cx.observe` on the dock with outline panel is
not triggered (used to be triggered on every selection change before)
* adjusting workspace dock drag listener to remember previous
coordinates and only resize the dock if those had changed
* adjusting workspace pane event listener to ignore
`pane::Event::UserSavedItem` and `pane::Event::ChangeItemTitle` that
seem to happen relatively frequently but not influence values that are
serialized for the workspace
* not using `cx.observe` on docks, instead explicitly serializing on
panel zoom and size changes

Release Notes:

- Reduced amount of workspace serialization happening

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-01-07 13:40:02 +02:00
Zed Bot
e9f548639c Bump to 0.168.2 for @maxdeviant 2025-01-06 18:54:00 +00:00
gcp-cherry-pick-bot[bot]
37a019ec7a zed: Add timeouts for feature flag resolution in workspace panel initialization (cherry-pick #22715) (#22721)
Cherry-picked zed: Add timeouts for feature flag resolution in workspace
panel initialization (#22715)

This PR adds timeouts when resolving feature flags during workspace
panel initialization so that we don't block indefinitely if Zed is not
connected to the internet.

Right now we wait for 5 seconds, but this value was chosen arbitrarily.

Release Notes:

- N/A

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-06 13:01:45 -05:00
gcp-cherry-pick-bot[bot]
46f0a6860d Return back Rust completion details (cherry-pick #22648) (#22651)
Cherry-picked Return back Rust completion details (#22648)

Closes https://github.com/zed-industries/zed/issues/22642

In Zed, Rust's label generators expected the details to come in ` (use
std.foo.Bar)` form, but recently, r-a started to send these details
without the leading whitespace which broke the code generation.

The PR makes LSP results parsing more lenient to work with both details'
forms.

Release Notes:

- Fixed Rust completion labels not showing the imports

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-01-04 13:49:13 +02:00
gcp-cherry-pick-bot[bot]
74c82e1e1a language_model_selector: Refresh the models when the providers change (cherry-pick #22624) (#22626)
Cherry-picked language_model_selector: Refresh the models when the
providers change (#22624)

This PR fixes an issue introduced in #21939 where the list of models in
the language model selector could be outdated.

Since we're no longer recreating the picker each render, we now need to
make sure we are updating the list of models accordingly when there are
changes to the language model providers.

I noticed it specifically in Assistant1.

Release Notes:

- Fixed a staleness issue with the language model selector.

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-03 15:33:08 -05:00
Peter Tripp
da62407ef0 zed 0.168.1 2025-01-03 11:06:25 -05:00
Agus Zubiaga
7d92b14e85 Fix vertical alignment when jumping from multibuffers (#22613)
Clicking buffer headers and line numbers would sometimes take you to a
disorienting scroll position. This PR improves that so the destination
line is roughly at the same Y position as it appeared in the
multibuffer.



https://github.com/user-attachments/assets/3ad71537-cf26-4136-948f-c5a96df57178


**Note**: The alignment won't always be perfect because the multibuffer
and target buffer might start at a different absolute Y position
(because of open search, breadcrumbs, etc). I wanted to compensate for
that, but that requires a fundamental change that I'd prefer to make
separately.

Release Notes:

- Fix vertical alignment when jumping from multibuffers
2025-01-03 11:01:44 -05:00
Kirill Bulatov
03c261566b Fix tooltips too eager to disappear when there's a gap between the tooltip source and the tooltip itself (#22583)
Follow-up of https://github.com/zed-industries/zed/pull/22548

Release Notes:

- N/A

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-01-03 11:12:17 +02:00
gcp-cherry-pick-bot[bot]
b800ff51c0 Remove stuck tooltips (cherry-pick #22548) (#22550)
Cherry-picked Remove stuck tooltips (#22548)

Closes https://github.com/zed-industries/zed/issues/21657

Follow-up of https://github.com/zed-industries/zed/pull/22488
Previous PR broke git blame tooltips, which are expected to be open when
hovered, even if the mouse cursor is moved away from the actual blame
entry that caused the tooltip to appear.

Current version moves the invalidation logic into `prepaint_tooltip`,
where the new data about the tooltip origin is used to ensure we
invalidate only tooltips that have no mouse cursor in either origin
bounds or tooltip bounds (if it's hoverable).


Release Notes:

- Fixed tooltips getting stuck

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-01-01 21:07:39 +02:00
Peter Tripp
56f288e635 v0.168.x preview 2025-01-01 12:23:34 -05:00
20 changed files with 355 additions and 151 deletions

2
Cargo.lock generated
View File

@@ -16204,7 +16204,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.168.0"
version = "0.168.3"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -993,7 +993,10 @@ pub(crate) struct FocusedBlock {
#[derive(Clone)]
enum JumpData {
MultiBufferRow(MultiBufferRow),
MultiBufferRow {
row: MultiBufferRow,
line_offset_from_top: u32,
},
MultiBufferPoint {
excerpt_id: ExcerptId,
position: Point,
@@ -12487,7 +12490,10 @@ impl Editor {
);
}
}
Some(JumpData::MultiBufferRow(row)) => {
Some(JumpData::MultiBufferRow {
row,
line_offset_from_top,
}) => {
let point = MultiBufferPoint::new(row.0, 0);
if let Some((buffer, buffer_point, _)) =
self.buffer.read(cx).point_to_buffer_point(point, cx)
@@ -12495,7 +12501,7 @@ impl Editor {
let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
new_selections_by_buffer
.entry(buffer)
.or_insert((Vec::new(), None))
.or_insert((Vec::new(), Some(*line_offset_from_top)))
.0
.push(buffer_offset..buffer_offset)
}

View File

@@ -543,8 +543,29 @@ impl EditorElement {
// and run the selection logic.
modifiers.alt = false;
} else {
let scroll_position_row =
position_map.scroll_pixel_position.y / position_map.line_height;
let display_row = (((event.position - gutter_hitbox.bounds.origin).y
+ position_map.scroll_pixel_position.y)
/ position_map.line_height)
as u32;
let multi_buffer_row = position_map
.snapshot
.display_point_to_point(
DisplayPoint::new(DisplayRow(display_row), 0),
Bias::Right,
)
.row;
let line_offset_from_top = display_row - scroll_position_row as u32;
// if double click is made without alt, open the corresponding excerp
editor.open_excerpts(&OpenExcerpts, cx);
editor.open_excerpts_common(
Some(JumpData::MultiBufferRow {
row: MultiBufferRow(multi_buffer_row),
line_offset_from_top,
}),
false,
cx,
);
return;
}
}
@@ -599,8 +620,15 @@ impl EditorElement {
.row;
if let Some((_, Some(hitbox))) = line_numbers.get(&MultiBufferRow(multi_buffer_row)) {
if hitbox.contains(&event.position) {
let scroll_position_row =
position_map.scroll_pixel_position.y / position_map.line_height;
let line_offset_from_top = display_row - scroll_position_row as u32;
editor.open_excerpts_common(
Some(JumpData::MultiBufferRow(MultiBufferRow(multi_buffer_row))),
Some(JumpData::MultiBufferRow {
row: MultiBufferRow(multi_buffer_row),
line_offset_from_top,
}),
modifiers.alt,
cx,
);
@@ -2959,7 +2987,12 @@ impl EditorElement {
selected_buffer_ids: &Vec<BufferId>,
cx: &mut WindowContext,
) -> AnyElement {
let jump_data = header_jump_data(snapshot, DisplayRow(0), FILE_HEADER_HEIGHT, excerpt);
let jump_data = header_jump_data(
snapshot,
DisplayRow(scroll_position as u32),
FILE_HEADER_HEIGHT + MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
excerpt,
);
let editor_bg_color = cx.theme().colors().editor_background;
@@ -5096,13 +5129,12 @@ fn header_jump_data(
let offset_from_excerpt_start = if jump_anchor == excerpt_start {
0
} else {
let excerpt_start_row = language::ToPoint::to_point(&jump_anchor, buffer).row;
let excerpt_start_row = language::ToPoint::to_point(&excerpt_start, buffer).row;
jump_position.row - excerpt_start_row
};
let line_offset_from_top = block_row_start.0
+ height
+ offset_from_excerpt_start.saturating_sub(
let line_offset_from_top = (block_row_start.0 + height + offset_from_excerpt_start)
.saturating_sub(
snapshot
.scroll_anchor
.scroll_position(&snapshot.display_snapshot)

View File

@@ -33,9 +33,9 @@ use util::ResultExt;
use crate::{
current_platform, hash, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
Entity, EventEmitter, FocusHandle, FocusId, ForegroundExecutor, Global, KeyBinding, Keymap,
Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
Asset, AssetSource, BackgroundExecutor, Bounds, ClipboardItem, Context, DispatchPhase,
DisplayId, Entity, EventEmitter, FocusHandle, FocusId, ForegroundExecutor, Global, KeyBinding,
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render,
RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet,
Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window, WindowAppearance,
@@ -1612,6 +1612,12 @@ pub struct AnyTooltip {
/// The absolute position of the mouse when the tooltip was deployed.
pub mouse_position: Point<Pixels>,
/// Whether the tooltitp can be hovered or not.
pub hoverable: bool,
/// Bounds of the element that triggered the tooltip appearance.
pub origin_bounds: Bounds<Pixels>,
}
/// A keystroke event, and potentially the associated action

View File

@@ -1923,6 +1923,7 @@ impl Interactivity {
cx.on_mouse_event({
let active_tooltip = active_tooltip.clone();
let hitbox = hitbox.clone();
let source_bounds = hitbox.bounds;
let tooltip_id = self.tooltip_id;
move |_: &MouseMoveEvent, phase, cx| {
let is_hovered =
@@ -1952,6 +1953,8 @@ impl Interactivity {
tooltip: Some(AnyTooltip {
view: build_tooltip(cx),
mouse_position: cx.mouse_position(),
hoverable: tooltip_is_hoverable,
origin_bounds: source_bounds,
}),
_task: None,
});

View File

@@ -675,6 +675,7 @@ impl Element for InteractiveText {
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
let hitbox = hitbox.clone();
let source_bounds = hitbox.bounds;
let active_tooltip = interactive_state.active_tooltip.clone();
let pending_mouse_down = interactive_state.mouse_down_index.clone();
let text_layout = text_layout.clone();
@@ -708,6 +709,8 @@ impl Element for InteractiveText {
tooltip: Some(AnyTooltip {
view: tooltip,
mouse_position: cx.mouse_position(),
hoverable: true,
origin_bounds: source_bounds,
}),
_task: None,
}

View File

@@ -1586,6 +1586,19 @@ impl<'a> WindowContext<'a> {
}
}
// Element's parent can get hidden (e.g. via the `visible_on_hover` method),
// and element's `paint` won't be called (ergo, mouse listeners also won't be active) to detect that the tooltip has to be removed.
// Ensure it's not stuck around in such cases.
let invalidate_tooltip = !tooltip_request
.tooltip
.origin_bounds
.contains(&self.mouse_position())
&& (!tooltip_request.tooltip.hoverable
|| !tooltip_bounds.contains(&self.mouse_position()));
if invalidate_tooltip {
return None;
}
self.with_absolute_element_offset(tooltip_bounds.origin, |cx| element.prepaint(cx));
self.window.tooltip_bounds = Some(TooltipBounds {

View File

@@ -2,8 +2,8 @@ use std::sync::Arc;
use feature_flags::ZedPro;
use gpui::{
Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Task,
View, WeakView,
Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
Subscription, Task, View, WeakView,
};
use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry};
use picker::{Picker, PickerDelegate};
@@ -17,6 +17,10 @@ type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &AppContext) + 'static>
pub struct LanguageModelSelector {
picker: View<Picker<LanguageModelPickerDelegate>>,
/// The task used to update the picker's matches when there is a change to
/// the language model registry.
update_matches_task: Option<Task<()>>,
_subscriptions: Vec<Subscription>,
}
impl LanguageModelSelector {
@@ -26,7 +30,51 @@ impl LanguageModelSelector {
) -> Self {
let on_model_changed = Arc::new(on_model_changed);
let all_models = LanguageModelRegistry::global(cx)
let all_models = Self::all_models(cx);
let delegate = LanguageModelPickerDelegate {
language_model_selector: cx.view().downgrade(),
on_model_changed: on_model_changed.clone(),
all_models: all_models.clone(),
filtered_models: all_models,
selected_index: 0,
};
let picker =
cx.new_view(|cx| Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into())));
LanguageModelSelector {
picker,
update_matches_task: None,
_subscriptions: vec![cx.subscribe(
&LanguageModelRegistry::global(cx),
Self::handle_language_model_registry_event,
)],
}
}
fn handle_language_model_registry_event(
&mut self,
_registry: Model<LanguageModelRegistry>,
event: &language_model::Event,
cx: &mut ViewContext<Self>,
) {
match event {
language_model::Event::ProviderStateChanged
| language_model::Event::AddedProvider(_)
| language_model::Event::RemovedProvider(_) => {
let task = self.picker.update(cx, |this, cx| {
let query = this.query(cx);
this.delegate.all_models = Self::all_models(cx);
this.delegate.update_matches(query, cx)
});
self.update_matches_task = Some(task);
}
_ => {}
}
}
fn all_models(cx: &AppContext) -> Vec<ModelInfo> {
LanguageModelRegistry::global(cx)
.read(cx)
.providers()
.iter()
@@ -44,20 +92,7 @@ impl LanguageModelSelector {
}
})
})
.collect::<Vec<_>>();
let delegate = LanguageModelPickerDelegate {
language_model_selector: cx.view().downgrade(),
on_model_changed: on_model_changed.clone(),
all_models: all_models.clone(),
filtered_models: all_models,
selected_index: 0,
};
let picker =
cx.new_view(|cx| Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into())));
LanguageModelSelector { picker }
.collect::<Vec<_>>()
}
}
@@ -152,25 +187,25 @@ impl PickerDelegate for LanguageModelPickerDelegate {
let llm_registry = LanguageModelRegistry::global(cx);
let configured_models: Vec<_> = llm_registry
let configured_providers = llm_registry
.read(cx)
.providers()
.iter()
.filter(|provider| provider.is_authenticated(cx))
.map(|provider| provider.id())
.collect();
.collect::<Vec<_>>();
cx.spawn(|this, mut cx| async move {
let filtered_models = cx
.background_executor()
.spawn(async move {
let displayed_models = if configured_models.is_empty() {
let displayed_models = if configured_providers.is_empty() {
all_models
} else {
all_models
.into_iter()
.filter(|model_info| {
configured_models.contains(&model_info.model.provider_id())
configured_providers.contains(&model_info.model.provider_id())
})
.collect::<Vec<_>>()
};

View File

@@ -253,49 +253,51 @@ impl LspAdapter for RustLspAdapter {
.as_ref()
.and_then(|detail| detail.detail.as_ref())
.or(completion.detail.as_ref())
.map(ToOwned::to_owned);
.map(|detail| detail.trim());
let function_signature = completion
.label_details
.as_ref()
.and_then(|detail| detail.description.as_ref())
.or(completion.detail.as_ref())
.map(ToOwned::to_owned);
match completion.kind {
Some(lsp::CompletionItemKind::FIELD) if detail.is_some() => {
.and_then(|detail| detail.description.as_deref())
.or(completion.detail.as_deref());
match (detail, completion.kind) {
(Some(detail), Some(lsp::CompletionItemKind::FIELD)) => {
let name = &completion.label;
let text = format!("{}: {}", name, detail.unwrap());
let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
let runs = language.highlight_text(&source, 11..11 + text.len());
let text = format!("{name}: {detail}");
let prefix = "struct S { ";
let source = Rope::from(format!("{prefix}{text} }}"));
let runs =
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..name.len(),
});
}
Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE)
if detail.is_some()
&& completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) =>
{
(
Some(detail),
Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE),
) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => {
let name = &completion.label;
let text = format!(
"{}: {}",
name,
completion.detail.as_ref().or(detail.as_ref()).unwrap()
completion.detail.as_deref().unwrap_or(detail)
);
let source = Rope::from(format!("let {} = ();", text).as_str());
let runs = language.highlight_text(&source, 4..4 + text.len());
let prefix = "let ";
let source = Rope::from(format!("{prefix}{text} = ();"));
let runs =
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..name.len(),
});
}
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD)
if detail.is_some() =>
{
(
Some(detail),
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD),
) => {
static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("\\(…?\\)").unwrap());
let detail = detail.unwrap();
const FUNCTION_PREFIXES: [&str; 6] = [
"async fn",
"async unsafe fn",
@@ -315,10 +317,11 @@ impl LspAdapter for RustLspAdapter {
// fn keyword should be followed by opening parenthesis.
if let Some((prefix, suffix)) = fn_keyword {
let mut text = REGEX.replace(&completion.label, suffix).to_string();
let source = Rope::from(format!("{prefix} {} {{}}", text).as_str());
let source = Rope::from(format!("{prefix} {text} {{}}"));
let run_start = prefix.len() + 1;
let runs = language.highlight_text(&source, run_start..run_start + text.len());
if detail.starts_with(" (") {
if detail.starts_with("(") {
text.push(' ');
text.push_str(&detail);
}
@@ -342,7 +345,7 @@ impl LspAdapter for RustLspAdapter {
});
}
}
Some(kind) => {
(_, Some(kind)) => {
let highlight_name = match kind {
lsp::CompletionItemKind::STRUCT
| lsp::CompletionItemKind::INTERFACE
@@ -356,9 +359,9 @@ impl LspAdapter for RustLspAdapter {
};
let mut label = completion.label.clone();
if let Some(detail) = detail.filter(|detail| detail.starts_with(" (")) {
use std::fmt::Write;
write!(label, "{detail}").ok()?;
if let Some(detail) = detail.filter(|detail| detail.starts_with("(")) {
label.push(' ');
label.push_str(detail);
}
let mut label = CodeLabel::plain(label, None);
if let Some(highlight_name) = highlight_name {
@@ -883,7 +886,7 @@ mod tests {
kind: Some(lsp::CompletionItemKind::FUNCTION),
label: "hello(…)".to_string(),
label_details: Some(CompletionItemLabelDetails {
detail: Some(" (use crate::foo)".into()),
detail: Some("(use crate::foo)".into()),
description: Some("fn(&mut Option<T>) -> Vec<T>".to_string())
}),
..Default::default()

View File

@@ -212,9 +212,18 @@ impl LspAdapter for TypeScriptLspAdapter {
_ => None,
}?;
let text = match &item.detail {
Some(detail) => format!("{} {}", item.label, detail),
None => item.label.clone(),
let one_line = |s: &str| s.replace(" ", "").replace('\n', " ");
let text = if let Some(description) = item
.label_details
.as_ref()
.and_then(|label_details| label_details.description.as_ref())
{
format!("{} {}", item.label, one_line(description))
} else if let Some(detail) = &item.detail {
format!("{} {}", item.label, one_line(detail))
} else {
item.label.clone()
};
Some(language::CodeLabel {

View File

@@ -4447,18 +4447,27 @@ impl Panel for OutlinePanel {
.update(&mut cx, |outline_panel, cx| {
let old_active = outline_panel.active;
outline_panel.active = active;
if active && old_active != active {
if let Some((active_item, active_editor)) = outline_panel
.workspace
.upgrade()
.and_then(|workspace| workspace_active_editor(workspace.read(cx), cx))
{
if outline_panel.should_replace_active_item(active_item.as_ref()) {
outline_panel.replace_active_editor(active_item, active_editor, cx);
} else {
outline_panel.update_fs_entries(active_editor, None, cx)
if old_active != active {
if active {
if let Some((active_item, active_editor)) =
outline_panel.workspace.upgrade().and_then(|workspace| {
workspace_active_editor(workspace.read(cx), cx)
})
{
if outline_panel.should_replace_active_item(active_item.as_ref()) {
outline_panel.replace_active_editor(
active_item,
active_editor,
cx,
);
} else {
outline_panel.update_fs_entries(active_editor, None, cx)
}
return;
}
} else if !outline_panel.pinned {
}
if !outline_panel.pinned {
outline_panel.clear_previous(cx);
}
}
@@ -4605,9 +4614,11 @@ fn subscribe_for_editor_events(
cx: &mut ViewContext<OutlinePanel>,
) -> Subscription {
let debounce = Some(UPDATE_DEBOUNCE);
cx.subscribe(
editor,
move |outline_panel, editor, e: &EditorEvent, cx| match e {
cx.subscribe(editor, move |outline_panel, editor, e: &EditorEvent, cx| {
if !outline_panel.active {
return;
}
match e {
EditorEvent::SelectionsChanged { local: true } => {
outline_panel.reveal_entry_for_selection(editor, cx);
cx.notify();
@@ -4712,8 +4723,8 @@ fn subscribe_for_editor_events(
outline_panel.update_non_fs_items(cx);
}
_ => {}
},
)
}
})
}
fn empty_icon() -> AnyElement {

View File

@@ -5586,13 +5586,13 @@ impl LspStore {
<R::LspRequest as lsp::request::Request>::Result: Send,
<R::LspRequest as lsp::request::Request>::Params: Send,
{
debug_assert!(self.upstream_client().is_none());
let Some(local) = self.as_local() else {
return Task::ready(Vec::new());
};
let snapshot = buffer.read(cx).snapshot();
let scope = position.and_then(|position| snapshot.language_scope_at(position));
let server_ids = self
.as_local()
.unwrap()
let server_ids = local
.language_servers_for_buffer(buffer.read(cx), cx)
.filter(|(adapter, _)| {
scope

View File

@@ -146,13 +146,16 @@ fn populate_pane_items(
cx: &mut ViewContext<Pane>,
) {
let mut item_index = pane.items_len();
let mut active_item_index = None;
for item in items {
let activate_item = Some(item.item_id().as_u64()) == active_item;
if Some(item.item_id().as_u64()) == active_item {
active_item_index = Some(item_index);
}
pane.add_item(Box::new(item), false, false, None, cx);
item_index += 1;
if activate_item {
pane.activate_item(item_index, false, false, cx);
}
}
if let Some(index) = active_item_index {
pane.activate_item(index, false, false, cx);
}
}

View File

@@ -31,7 +31,7 @@ use ui::{
};
use util::{ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
dock::{DockPosition, Panel, PanelEvent, PanelHandle},
item::SerializableItem,
move_active_item, move_item, pane,
ui::IconName,
@@ -75,6 +75,7 @@ pub struct TerminalPanel {
deferred_tasks: HashMap<TaskId, Task<()>>,
assistant_enabled: bool,
assistant_tab_bar_button: Option<AnyView>,
active: bool,
}
impl TerminalPanel {
@@ -82,7 +83,6 @@ impl TerminalPanel {
let project = workspace.project();
let pane = new_terminal_pane(workspace.weak_handle(), project.clone(), false, cx);
let center = PaneGroup::new(pane.clone());
cx.focus_view(&pane);
let terminal_panel = Self {
center,
active_pane: pane,
@@ -95,6 +95,7 @@ impl TerminalPanel {
deferred_tasks: HashMap::default(),
assistant_enabled: false,
assistant_tab_bar_button: None,
active: false,
};
terminal_panel.apply_tab_bar_buttons(&terminal_panel.active_pane, cx);
terminal_panel
@@ -281,6 +282,25 @@ impl TerminalPanel {
}
}
if let Some(workspace) = workspace.upgrade() {
let should_focus = workspace
.update(&mut cx, |workspace, cx| {
workspace.active_item(cx).is_none()
&& workspace.is_dock_at_position_open(terminal_panel.position(cx), cx)
})
.unwrap_or(false);
if should_focus {
terminal_panel
.update(&mut cx, |panel, cx| {
panel.active_pane.update(cx, |pane, cx| {
pane.focus_active_item(cx);
});
})
.ok();
}
}
Ok(terminal_panel)
}
@@ -1339,7 +1359,9 @@ impl Panel for TerminalPanel {
}
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
if !active || !self.has_no_terminals(cx) {
let old_active = self.active;
self.active = active;
if !active || old_active == active || !self.has_no_terminals(cx) {
return;
}
cx.defer(|this, cx| {

View File

@@ -514,13 +514,15 @@ fn text_object(
let excerpt = snapshot.excerpt_containing(offset..offset)?;
let buffer = excerpt.buffer();
let offset = excerpt.map_offset_to_buffer(offset);
let mut matches: Vec<Range<usize>> = buffer
.text_object_ranges(offset..offset, TreeSitterOptions::default())
.filter_map(|(r, m)| if m == target { Some(r) } else { None })
.collect();
matches.sort_by_key(|r| (r.end - r.start));
if let Some(range) = matches.first() {
if let Some(buffer_range) = matches.first() {
let range = excerpt.map_range_from_buffer(buffer_range.clone());
return Some(range.start.to_display_point(map)..range.end.to_display_point(map));
}
@@ -537,12 +539,14 @@ fn text_object(
.filter_map(|(r, m)| if m == target { Some(r) } else { None })
.collect();
matches.sort_by_key(|r| r.start);
if let Some(range) = matches.first() {
if !range.is_empty() {
if let Some(buffer_range) = matches.first() {
if !buffer_range.is_empty() {
let range = excerpt.map_range_from_buffer(buffer_range.clone());
return Some(range.start.to_display_point(map)..range.end.to_display_point(map));
}
}
return Some(around_range.start.to_display_point(map)..around_range.end.to_display_point(map));
let buffer_range = excerpt.map_range_from_buffer(around_range.clone());
return Some(buffer_range.start.to_display_point(map)..buffer_range.end.to_display_point(map));
}
fn argument(

View File

@@ -170,6 +170,7 @@ impl From<&dyn PanelHandle> for AnyView {
pub struct Dock {
position: DockPosition,
panel_entries: Vec<PanelEntry>,
workspace: WeakView<Workspace>,
is_open: bool,
active_panel_index: Option<usize>,
focus_handle: FocusHandle,
@@ -236,6 +237,7 @@ impl Dock {
});
Self {
position,
workspace: workspace.downgrade(),
panel_entries: Default::default(),
active_panel_index: None,
is_open: false,
@@ -337,6 +339,9 @@ impl Dock {
self.is_open = open;
if let Some(active_panel) = self.active_panel_entry() {
active_panel.panel.set_active(open, cx);
if !open {
self.active_panel_index = None;
}
}
cx.notify();
@@ -354,6 +359,11 @@ impl Dock {
}
}
self.workspace
.update(cx, |workspace, cx| {
workspace.serialize_workspace(cx);
})
.ok();
cx.notify();
}
@@ -484,7 +494,8 @@ impl Dock {
},
);
if !self.restore_state(cx) && panel.read(cx).starts_open(cx) {
self.restore_state(cx);
if panel.read(cx).starts_open(cx) {
self.activate_panel(index, cx);
self.set_open(true, cx);
}
@@ -652,9 +663,14 @@ impl Render for Dock {
)
.on_mouse_up(
MouseButton::Left,
cx.listener(|v, e: &MouseUpEvent, cx| {
cx.listener(|dock, e: &MouseUpEvent, cx| {
if e.click_count == 2 {
v.resize_active_panel(None, cx);
dock.resize_active_panel(None, cx);
dock.workspace
.update(cx, |workspace, cx| {
workspace.serialize_workspace(cx);
})
.ok();
cx.stop_propagation();
}
}),

View File

@@ -743,6 +743,7 @@ pub struct Workspace {
weak_self: WeakView<Self>,
workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
zoomed: Option<AnyWeakView>,
previous_dock_drag_coordinates: Option<Point<Pixels>>,
zoomed_position: Option<DockPosition>,
center: PaneGroup,
left_dock: View<Dock>,
@@ -1020,18 +1021,6 @@ impl Workspace {
ThemeSettings::reload_current_theme(cx);
}),
cx.observe(&left_dock, |this, _, cx| {
this.serialize_workspace(cx);
cx.notify();
}),
cx.observe(&bottom_dock, |this, _, cx| {
this.serialize_workspace(cx);
cx.notify();
}),
cx.observe(&right_dock, |this, _, cx| {
this.serialize_workspace(cx);
cx.notify();
}),
cx.on_release(|this, window, cx| {
this.app_state.workspace_store.update(cx, |store, _| {
let window = window.downcast::<Self>().unwrap();
@@ -1047,6 +1036,7 @@ impl Workspace {
weak_self: weak_handle.clone(),
zoomed: None,
zoomed_position: None,
previous_dock_drag_coordinates: None,
center: PaneGroup::new(center_pane.clone()),
panes: vec![center_pane.clone()],
panes_by_item: Default::default(),
@@ -2284,6 +2274,19 @@ impl Workspace {
}
}
pub fn is_dock_at_position_open(
&self,
position: DockPosition,
cx: &mut ViewContext<Self>,
) -> bool {
let dock = match position {
DockPosition::Left => &self.left_dock,
DockPosition::Bottom => &self.bottom_dock,
DockPosition::Right => &self.right_dock,
};
dock.read(cx).is_open()
}
pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
let dock = match dock_side {
DockPosition::Left => &self.left_dock,
@@ -3068,6 +3071,7 @@ impl Workspace {
event: &pane::Event,
cx: &mut ViewContext<Self>,
) {
let mut serialize_workspace = true;
match event {
pane::Event::AddItem { item } => {
item.added_to_pane(self, pane, cx);
@@ -3078,10 +3082,14 @@ impl Workspace {
pane::Event::Split(direction) => {
self.split_and_clone(pane, *direction, cx);
}
pane::Event::JoinIntoNext => self.join_pane_into_next(pane, cx),
pane::Event::JoinAll => self.join_all_panes(cx),
pane::Event::JoinIntoNext => {
self.join_pane_into_next(pane, cx);
}
pane::Event::JoinAll => {
self.join_all_panes(cx);
}
pane::Event::Remove { focus_on_pane } => {
self.remove_pane(pane, focus_on_pane.clone(), cx)
self.remove_pane(pane, focus_on_pane.clone(), cx);
}
pane::Event::ActivateItem { local } => {
cx.on_next_frame(|_, cx| {
@@ -3099,16 +3107,20 @@ impl Workspace {
self.update_active_view_for_followers(cx);
}
}
pane::Event::UserSavedItem { item, save_intent } => cx.emit(Event::UserSavedItem {
pane: pane.downgrade(),
item: item.boxed_clone(),
save_intent: *save_intent,
}),
pane::Event::UserSavedItem { item, save_intent } => {
cx.emit(Event::UserSavedItem {
pane: pane.downgrade(),
item: item.boxed_clone(),
save_intent: *save_intent,
});
serialize_workspace = false;
}
pane::Event::ChangeItemTitle => {
if pane == self.active_pane {
self.active_item_path_changed(cx);
}
self.update_window_edited(cx);
serialize_workspace = false;
}
pane::Event::RemoveItem { .. } => {}
pane::Event::RemovedItem { item_id } => {
@@ -3147,7 +3159,9 @@ impl Workspace {
}
}
self.serialize_workspace(cx);
if serialize_workspace {
self.serialize_workspace(cx);
}
}
pub fn unfollow_in_pane(
@@ -4908,32 +4922,39 @@ impl Render for Workspace {
})
.when(self.zoomed.is_none(), |this| {
this.on_drag_move(cx.listener(
|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
match e.drag(cx).0 {
DockPosition::Left => {
resize_left_dock(
e.event.position.x
- workspace.bounds.left(),
workspace,
cx,
);
}
DockPosition::Right => {
resize_right_dock(
workspace.bounds.right()
- e.event.position.x,
workspace,
cx,
);
}
DockPosition::Bottom => {
resize_bottom_dock(
workspace.bounds.bottom()
- e.event.position.y,
workspace,
cx,
);
}
move |workspace, e: &DragMoveEvent<DraggedDock>, cx| {
if workspace.previous_dock_drag_coordinates
!= Some(e.event.position)
{
workspace.previous_dock_drag_coordinates =
Some(e.event.position);
match e.drag(cx).0 {
DockPosition::Left => {
resize_left_dock(
e.event.position.x
- workspace.bounds.left(),
workspace,
cx,
);
}
DockPosition::Right => {
resize_right_dock(
workspace.bounds.right()
- e.event.position.x,
workspace,
cx,
);
}
DockPosition::Bottom => {
resize_bottom_dock(
workspace.bounds.bottom()
- e.event.position.y,
workspace,
cx,
);
}
};
workspace.serialize_workspace(cx);
}
},
))

View File

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

View File

@@ -1 +1 @@
dev
stable

View File

@@ -20,6 +20,7 @@ use command_palette_hooks::CommandPaletteFilter;
use editor::ProposedChangesEditorToolbar;
use editor::{scroll::Autoscroll, Editor, MultiBuffer};
use feature_flags::FeatureFlagAppExt;
use futures::FutureExt;
use futures::{channel::mpsc, select_biased, StreamExt};
use gpui::{
actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, MenuItem,
@@ -348,7 +349,16 @@ fn initialize_panels(prompt_builder: Arc<PromptBuilder>, cx: &mut ViewContext<Wo
workspace.add_panel(assistant_panel, cx)
})?;
let git_ui_enabled = git_ui_feature_flag.await;
let git_ui_enabled = {
let mut git_ui_feature_flag = git_ui_feature_flag.fuse();
let mut timeout =
FutureExt::fuse(smol::Timer::after(std::time::Duration::from_secs(5)));
select_biased! {
is_git_ui_enabled = git_ui_feature_flag => is_git_ui_enabled,
_ = timeout => false,
}
};
let git_panel = if git_ui_enabled {
Some(git_ui::git_panel::GitPanel::load(workspace_handle.clone(), cx.clone()).await?)
} else {
@@ -363,7 +373,14 @@ fn initialize_panels(prompt_builder: Arc<PromptBuilder>, cx: &mut ViewContext<Wo
let is_assistant2_enabled = if cfg!(test) {
false
} else {
assistant2_feature_flag.await
let mut assistant2_feature_flag = assistant2_feature_flag.fuse();
let mut timeout =
FutureExt::fuse(smol::Timer::after(std::time::Duration::from_secs(5)));
select_biased! {
is_assistant2_enabled = assistant2_feature_flag => is_assistant2_enabled,
_ = timeout => false,
}
};
let assistant2_panel = if is_assistant2_enabled {
Some(assistant2::AssistantPanel::load(workspace_handle.clone(), cx.clone()).await?)