Compare commits

..

1 Commits

Author SHA1 Message Date
Conrad Irwin
b69a2ea200 Tag stack 2 2025-11-05 00:24:12 -07:00
39 changed files with 578 additions and 438 deletions

View File

@@ -839,7 +839,7 @@ ui_input = { codegen-units = 1 }
zed_actions = { codegen-units = 1 }
[profile.release]
debug = "full"
debug = "limited"
lto = "thin"
codegen-units = 1

View File

@@ -67,6 +67,7 @@
"ctrl-o": "pane::GoBack",
"ctrl-i": "pane::GoForward",
"ctrl-]": "editor::GoToDefinition",
"ctrl-t": "pane::GoToOlderTag",
"escape": "vim::SwitchToNormalMode",
"ctrl-[": "vim::SwitchToNormalMode",
"v": "vim::ToggleVisual",

View File

@@ -2051,15 +2051,6 @@ impl AcpThreadView {
.into_any(),
};
let needs_confirmation = if let AgentThreadEntry::ToolCall(tool_call) = entry {
matches!(
tool_call.status,
ToolCallStatus::WaitingForConfirmation { .. }
)
} else {
false
};
let Some(thread) = self.thread() else {
return primary;
};
@@ -2068,13 +2059,7 @@ impl AcpThreadView {
v_flex()
.w_full()
.child(primary)
.map(|this| {
if needs_confirmation {
this.child(self.render_generating(true))
} else {
this.child(self.render_thread_controls(&thread, cx))
}
})
.child(self.render_thread_controls(&thread, cx))
.when_some(
self.thread_feedback.comments_editor.clone(),
|this, editor| this.child(Self::render_feedback_feedback_editor(editor, cx)),
@@ -4844,31 +4829,6 @@ impl AcpThreadView {
}
}
fn render_generating(&self, confirmation: bool) -> impl IntoElement {
h_flex()
.id("generating-spinner")
.py_2()
.px(rems_from_px(22.))
.map(|this| {
if confirmation {
this.gap_2()
.child(
h_flex()
.w_2()
.child(SpinnerLabel::sand().size(LabelSize::Small)),
)
.child(
LoadingLabel::new("Waiting Confirmation")
.size(LabelSize::Small)
.color(Color::Muted),
)
} else {
this.child(SpinnerLabel::new().size(LabelSize::Small))
}
})
.into_any_element()
}
fn render_thread_controls(
&self,
thread: &Entity<AcpThread>,
@@ -4876,7 +4836,12 @@ impl AcpThreadView {
) -> impl IntoElement {
let is_generating = matches!(thread.read(cx).status(), ThreadStatus::Generating);
if is_generating {
return self.render_generating(false).into_any_element();
return h_flex().id("thread-controls-container").child(
div()
.py_2()
.px(rems_from_px(22.))
.child(SpinnerLabel::new().size(LabelSize::Small)),
);
}
let open_as_markdown = IconButton::new("open-as-markdown", IconName::FileMarkdown)
@@ -4964,10 +4929,7 @@ impl AcpThreadView {
);
}
container
.child(open_as_markdown)
.child(scroll_to_top)
.into_any_element()
container.child(open_as_markdown).child(scroll_to_top)
}
fn render_feedback_feedback_editor(editor: Entity<Editor>, cx: &Context<Self>) -> Div {

View File

@@ -1013,7 +1013,7 @@ impl AgentConfiguration {
.child(Divider::horizontal().color(DividerColor::BorderFaded))
.child(self.render_agent_server(
AgentIcon::Name(IconName::AiOpenAi),
"Codex CLI",
"Codex",
false,
))
.child(Divider::horizontal().color(DividerColor::BorderFaded))

View File

@@ -25,6 +25,7 @@ use std::{
any::{Any, TypeId},
collections::hash_map::Entry,
ops::Range,
rc::Rc,
sync::Arc,
};
use ui::{CommonAnimationExt, IconButtonShape, KeyBinding, Tooltip, prelude::*, vertical_divider};
@@ -516,12 +517,7 @@ impl Item for AgentDiffPane {
.update(cx, |editor, cx| editor.deactivated(window, cx));
}
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
fn navigate(&mut self, data: Rc<dyn Any>, window: &mut Window, cx: &mut Context<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}

View File

@@ -1880,12 +1880,7 @@ impl AgentPanel {
{
let focus_handle = focus_handle.clone();
move |_window, cx| {
Tooltip::for_action_in(
"New Thread…",
&ToggleNewThreadMenu,
&focus_handle,
cx,
)
Tooltip::for_action_in("New…", &ToggleNewThreadMenu, &focus_handle, cx)
}
},
)
@@ -1983,7 +1978,7 @@ impl AgentPanel {
.separator()
.header("External Agents")
.item(
ContextMenuEntry::new("New Claude Code")
ContextMenuEntry::new("New Claude Code Thread")
.icon(IconName::AiClaude)
.disabled(is_via_collab)
.icon_color(Color::Muted)
@@ -2009,7 +2004,7 @@ impl AgentPanel {
}),
)
.item(
ContextMenuEntry::new("New Codex CLI")
ContextMenuEntry::new("New Codex Thread")
.icon(IconName::AiOpenAi)
.disabled(is_via_collab)
.icon_color(Color::Muted)
@@ -2035,7 +2030,7 @@ impl AgentPanel {
}),
)
.item(
ContextMenuEntry::new("New Gemini CLI")
ContextMenuEntry::new("New Gemini CLI Thread")
.icon(IconName::AiGemini)
.icon_color(Color::Muted)
.disabled(is_via_collab)
@@ -2079,7 +2074,7 @@ impl AgentPanel {
for agent_name in agent_names {
let icon_path = agent_server_store_read.agent_icon(&agent_name);
let mut entry =
ContextMenuEntry::new(format!("New {}", agent_name));
ContextMenuEntry::new(format!("New {} Thread", agent_name));
if let Some(icon_path) = icon_path {
entry = entry.custom_icon_path(icon_path);
} else {

View File

@@ -2530,7 +2530,7 @@ impl Item for TextThreadEditor {
fn navigate(
&mut self,
data: Box<dyn std::any::Any>,
data: Rc<dyn std::any::Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {

View File

@@ -18,6 +18,7 @@ use project::Project;
use rpc::proto::ChannelVisibility;
use std::{
any::{Any, TypeId},
rc::Rc,
sync::Arc,
};
use ui::prelude::*;
@@ -515,12 +516,7 @@ impl Item for ChannelView {
})))
}
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
fn navigate(&mut self, data: Rc<dyn Any>, window: &mut Window, cx: &mut Context<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}

View File

@@ -1,4 +1,7 @@
use std::any::{Any, TypeId};
use std::{
any::{Any, TypeId},
rc::Rc,
};
use collections::HashMap;
use dap::StackFrameId;
@@ -331,12 +334,7 @@ impl Item for StackTraceView {
.update(cx, |editor, cx| editor.deactivated(window, cx));
}
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
fn navigate(&mut self, data: Rc<dyn Any>, window: &mut Window, cx: &mut Context<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}

View File

@@ -24,6 +24,7 @@ use settings::Settings;
use std::{
any::{Any, TypeId},
cmp::Ordering,
rc::Rc,
sync::Arc,
};
use text::{Anchor, BufferSnapshot, OffsetRangeExt};
@@ -734,12 +735,7 @@ impl Item for BufferDiagnosticsEditor {
self.multibuffer.read(cx).is_dirty(cx)
}
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
fn navigate(&mut self, data: Rc<dyn Any>, window: &mut Window, cx: &mut Context<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}

View File

@@ -35,6 +35,7 @@ use std::{
any::{Any, TypeId},
cmp,
ops::{Range, RangeInclusive},
rc::Rc,
sync::Arc,
time::Duration,
};
@@ -728,12 +729,7 @@ impl Item for ProjectDiagnosticsEditor {
.update(cx, |editor, cx| editor.deactivated(window, cx));
}
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
fn navigate(&mut self, data: Rc<dyn Any>, window: &mut Window, cx: &mut Context<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}

View File

@@ -7599,17 +7599,18 @@ impl Editor {
)
}
fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
match EditorSettings::get_global(cx).multi_cursor_modifier {
MultiCursorModifier::Alt => modifiers.secondary(),
MultiCursorModifier::CmdOrCtrl => modifiers.alt,
}
}
fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
match EditorSettings::get_global(cx).multi_cursor_modifier {
MultiCursorModifier::Alt => modifiers.alt,
MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
if invert {
match multi_cursor_setting {
MultiCursorModifier::Alt => modifiers.alt,
MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
}
} else {
match multi_cursor_setting {
MultiCursorModifier::Alt => modifiers.secondary(),
MultiCursorModifier::CmdOrCtrl => modifiers.alt,
}
}
}
@@ -7618,9 +7619,9 @@ impl Editor {
cx: &mut Context<Self>,
) -> Option<ColumnarMode> {
if modifiers.shift && modifiers.number_of_modifiers() == 2 {
if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
if Self::multi_cursor_modifier(false, modifiers, cx) {
Some(ColumnarMode::FromMouse)
} else if Self::is_alt_pressed(modifiers, cx) {
} else if Self::multi_cursor_modifier(true, modifiers, cx) {
Some(ColumnarMode::FromSelection)
} else {
None
@@ -14037,6 +14038,27 @@ impl Editor {
);
}
fn finish_tag_jump(&mut self, point: Point, cx: &mut Context<Self>) {
let Some(nav_history) = self.nav_history.as_mut() else {
return;
};
let snapshot = self.buffer.read(cx).snapshot(cx);
let cursor_anchor = snapshot.anchor_after(point);
let cursor_position = cursor_anchor.to_point(&snapshot);
let scroll_state = self.scroll_manager.anchor();
let scroll_top_row = scroll_state.top_row(&snapshot);
dbg!("finish tag jump", cursor_position);
nav_history.finish_tag_jump(
Some(NavigationData {
cursor_anchor,
cursor_position,
scroll_anchor: scroll_state,
scroll_top_row,
}),
cx,
);
}
fn push_to_nav_history(
&mut self,
cursor_anchor: Anchor,
@@ -16436,18 +16458,37 @@ impl Editor {
let Some(provider) = self.semantics_provider.clone() else {
return Task::ready(Ok(Navigated::No));
};
let head = self
let cursor = self
.selections
.newest::<usize>(&self.display_snapshot(cx))
.head();
let buffer = self.buffer.read(cx);
let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
let multi_buffer = self.buffer.read(cx);
let Some((buffer, head)) = multi_buffer.text_anchor_for_position(cursor, cx) else {
return Task::ready(Ok(Navigated::No));
};
let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
return Task::ready(Ok(Navigated::No));
};
if let Some(nav_history) = self.nav_history.as_mut() {
let snapshot = self.buffer.read(cx).snapshot(cx);
let cursor_anchor = snapshot.anchor_after(cursor);
let cursor_position = snapshot.offset_to_point(cursor);
let scroll_anchor = self.scroll_manager.anchor();
let scroll_top_row = scroll_anchor.top_row(&snapshot);
dbg!("start tag jump", cursor_position);
nav_history.start_tag_jump(
Some(NavigationData {
cursor_anchor,
cursor_position,
scroll_anchor,
scroll_top_row,
}),
cx,
);
}
cx.spawn_in(window, async move |editor, cx| {
let Some(definitions) = definitions.await? else {
return Ok(Navigated::No);
@@ -16692,6 +16733,7 @@ impl Editor {
if !split
&& Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
{
editor.finish_tag_jump(range.start, cx);
editor.go_to_singleton_buffer_range(range, window, cx);
} else {
let pane = workspace.read(cx).active_pane().clone();
@@ -16717,6 +16759,7 @@ impl Editor {
// When selecting a definition in a different buffer, disable the nav history
// to avoid creating a history entry at the previous cursor location.
pane.update(cx, |pane, _| pane.disable_history());
target_editor.finish_tag_jump(range.start, cx);
target_editor.go_to_singleton_buffer_range(range, window, cx);
pane.update(cx, |pane, _| pane.enable_history());
});
@@ -17038,6 +17081,7 @@ impl Editor {
multibuffer.with_title(title)
});
let first_range = ranges.first().cloned();
let existing = workspace.active_pane().update(cx, |pane, cx| {
pane.items()
.filter_map(|item| item.downcast::<Editor>())
@@ -17090,6 +17134,21 @@ impl Editor {
});
}
});
cx.defer({
let editor = editor.clone();
move |cx| {
let Some(range) = first_range else { return };
editor.update(cx, |editor, cx| {
let point = editor
.buffer()
.read(cx)
.snapshot(cx)
.summary_for_anchor(&range.start);
editor.finish_tag_jump(point, cx)
})
}
});
let item = Box::new(editor);
let item_id = item.item_id();

View File

@@ -911,7 +911,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
let invalid_point = Point::new(9999, 0);
editor.navigate(
Box::new(NavigationData {
Rc::new(NavigationData {
cursor_anchor: invalid_anchor,
cursor_position: invalid_point,
scroll_anchor: ScrollAnchor {

View File

@@ -820,7 +820,7 @@ impl EditorElement {
editor.select(
SelectPhase::Begin {
position,
add: Editor::is_alt_pressed(&modifiers, cx),
add: Editor::multi_cursor_modifier(true, &modifiers, cx),
click_count,
},
window,
@@ -1004,7 +1004,7 @@ impl EditorElement {
let text_hitbox = &position_map.text_hitbox;
let pending_nonempty_selections = editor.has_pending_nonempty_selection();
let hovered_link_modifier = Editor::is_cmd_or_ctrl_pressed(&event.modifiers(), cx);
let hovered_link_modifier = Editor::multi_cursor_modifier(false, &event.modifiers(), cx);
if let Some(mouse_position) = event.mouse_position()
&& !pending_nonempty_selections

View File

@@ -116,7 +116,7 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) {
let hovered_link_modifier = Editor::is_cmd_or_ctrl_pressed(&modifiers, cx);
let hovered_link_modifier = Editor::multi_cursor_modifier(false, &modifiers, cx);
if !hovered_link_modifier || self.has_pending_selection() {
self.hide_hovered_link(cx);
return;
@@ -241,8 +241,21 @@ impl Editor {
}
})
.collect();
let split = Self::is_alt_pressed(&modifiers, cx);
let navigate_task = self.navigate_to_hover_links(None, links, split, window, cx);
// todo!()
// if let Some(nav_history) = self.nav_history.as_mut() {
// nav_history.start_tag_jump(
// Some(NavigationData {
// cursor_anchor,
// cursor_position,
// scroll_anchor: scroll_state,
// scroll_top_row,
// }),
// cx,
// );
// }
let navigate_task =
self.navigate_to_hover_links(None, links, modifiers.alt, window, cx);
self.select(SelectPhase::End, window, cx);
return navigate_task;
}
@@ -261,8 +274,7 @@ impl Editor {
);
let navigate_task = if point.as_valid().is_some() {
let split = Self::is_alt_pressed(&modifiers, cx);
match (modifiers.shift, split) {
match (modifiers.shift, modifiers.alt) {
(true, true) => {
self.go_to_type_definition_split(&GoToTypeDefinitionSplit, window, cx)
}

View File

@@ -34,6 +34,7 @@ use std::{
iter,
ops::Range,
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
};
use text::{BufferId, BufferSnapshot, Selection};
@@ -589,7 +590,7 @@ impl Item for Editor {
fn navigate(
&mut self,
data: Box<dyn std::any::Any>,
data: Rc<dyn std::any::Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {

View File

@@ -17,6 +17,7 @@ use std::{
any::{Any, TypeId},
fmt::Write as _,
path::PathBuf,
rc::Rc,
sync::Arc,
};
use ui::{
@@ -527,12 +528,7 @@ impl Item for CommitView {
});
}
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
fn navigate(&mut self, data: Rc<dyn Any>, window: &mut Window, cx: &mut Context<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}

View File

@@ -14,6 +14,7 @@ use std::{
any::{Any, TypeId},
path::PathBuf,
pin::pin,
rc::Rc,
sync::Arc,
time::Duration,
};
@@ -301,12 +302,7 @@ impl Item for FileDiffView {
});
}
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
fn navigate(&mut self, data: Rc<dyn Any>, window: &mut Window, cx: &mut Context<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}

View File

@@ -32,9 +32,12 @@ use project::{
},
};
use settings::{Settings, SettingsStore};
use std::{any::{Any, TypeId}, time::Duration};
use std::ops::Range;
use std::sync::Arc;
use std::{
any::{Any, TypeId},
rc::Rc,
};
use theme::ActiveTheme;
use ui::{KeyBinding, Tooltip, prelude::*, vertical_divider};
use util::{ResultExt as _, rel_path::RelPath};
@@ -590,10 +593,6 @@ impl ProjectDiff {
.ok();
})?;
}
cx.background_executor().timer(Duration::from_millis(5)).await;
this.update(cx, |_, cx| {
cx.notify();
})?;
}
this.update(cx, |this, cx| {
this.pending_scroll.take();
@@ -653,12 +652,7 @@ impl Item for ProjectDiff {
.update(cx, |editor, cx| editor.deactivated(window, cx));
}
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
fn navigate(&mut self, data: Rc<dyn Any>, window: &mut Window, cx: &mut Context<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}

View File

@@ -15,6 +15,7 @@ use std::{
cmp,
ops::Range,
pin::pin,
rc::Rc,
sync::Arc,
time::Duration,
};
@@ -362,12 +363,7 @@ impl Item for TextDiffView {
});
}
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
fn navigate(&mut self, data: Rc<dyn Any>, window: &mut Window, cx: &mut Context<Self>) -> bool {
self.diff_editor
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}

View File

@@ -26,8 +26,10 @@ pub struct KeyDownEvent {
/// Whether the key is currently held down.
pub is_held: bool,
/// Whether to prefer character input over keybindings for this keystroke.
/// In some cases, like AltGr on Windows, modifiers are significant for character input.
/// Whether the modifiers are excessive for producing this character.
/// When false, the modifiers are essential for character input (e.g., AltGr),
/// and character input should be prioritized over keybindings.
/// When true, the modifiers are for keybindings (e.g., Ctrl+A).
pub prefer_character_input: bool,
}

View File

@@ -1387,8 +1387,6 @@ fn should_prefer_character_input(vkey: VIRTUAL_KEY, scan_code: u16) -> bool {
return false;
}
// Workaround for some bug that makes the compiler think keyboard_state is still zeroed out
let keyboard_state = std::hint::black_box(keyboard_state);
let ctrl_down = (keyboard_state[VK_CONTROL.0 as usize] & 0x80) != 0;
let alt_down = (keyboard_state[VK_MENU.0 as usize] & 0x80) != 0;
let win_down = (keyboard_state[VK_LWIN.0 as usize] & 0x80) != 0

View File

@@ -969,7 +969,6 @@ impl Buffer {
/// Builds a [`Buffer`] with the given underlying [`TextBuffer`], diff base, [`File`] and [`Capability`].
pub fn build(buffer: TextBuffer, file: Option<Arc<dyn File>>, capability: Capability) -> Self {
log::info!("file: {:?}", file.as_ref().map(|f| f.path()));
let saved_mtime = file.as_ref().and_then(|file| file.disk_state().mtime());
let snapshot = buffer.snapshot();
let syntax_map = Mutex::new(SyntaxMap::new(&snapshot));
@@ -3367,19 +3366,7 @@ impl BufferSnapshot {
pub fn syntax_layer_at<D: ToOffset>(&self, position: D) -> Option<SyntaxLayer<'_>> {
let offset = position.to_offset(self);
self.syntax_layers_for_range(offset..offset, false)
.filter(|l| {
if let Some(ranges) = l.included_sub_ranges {
ranges.iter().any(|range| {
let start = range.start.to_offset(self);
start <= offset && {
let end = range.end.to_offset(self);
offset < end
}
})
} else {
l.node().start_byte() <= offset && l.node().end_byte() > offset
}
})
.filter(|l| l.node().end_byte() > offset)
.last()
}

View File

@@ -2633,7 +2633,7 @@ fn test_language_scope_at_with_combined_injections(cx: &mut App) {
buffer.set_language_registry(language_registry.clone());
buffer.set_language(
language_registry
.language_for_name("HTML+ERB")
.language_for_name("ERB")
.now_or_never()
.unwrap()
.ok(),
@@ -2753,50 +2753,6 @@ fn test_language_at_for_markdown_code_block(cx: &mut App) {
});
}
#[gpui::test]
fn test_syntax_layer_at_for_injected_languages(cx: &mut App) {
init_settings(cx, |_| {});
cx.new(|cx| {
let text = r#"
```html+erb
<div>Hello</div>
<%= link_to "Some", "https://zed.dev" %>
```
"#
.unindent();
let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
language_registry.add(Arc::new(erb_lang()));
language_registry.add(Arc::new(html_lang()));
language_registry.add(Arc::new(ruby_lang()));
let mut buffer = Buffer::local(text, cx);
buffer.set_language_registry(language_registry.clone());
buffer.set_language(
language_registry
.language_for_name("HTML+ERB")
.now_or_never()
.unwrap()
.ok(),
cx,
);
let snapshot = buffer.snapshot();
// Test points in the code line
let html_point = Point::new(1, 4);
let language = snapshot.language_at(html_point).unwrap();
assert_eq!(language.name().as_ref(), "HTML");
let ruby_point = Point::new(2, 6);
let language = snapshot.language_at(ruby_point).unwrap();
assert_eq!(language.name().as_ref(), "Ruby");
buffer
});
}
#[gpui::test]
fn test_serialization(cx: &mut gpui::App) {
let mut now = Instant::now();
@@ -3699,7 +3655,7 @@ fn html_lang() -> Language {
fn erb_lang() -> Language {
Language::new(
LanguageConfig {
name: "HTML+ERB".into(),
name: "ERB".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["erb".to_string()],
..Default::default()
@@ -3717,15 +3673,15 @@ fn erb_lang() -> Language {
.with_injection_query(
r#"
(
(code) @content
(#set! "language" "ruby")
(#set! "combined")
(code) @injection.content
(#set! injection.language "ruby")
(#set! injection.combined)
)
(
(content) @content
(#set! "language" "html")
(#set! "combined")
(content) @injection.content
(#set! injection.language "html")
(#set! injection.combined)
)
"#,
)

View File

@@ -587,8 +587,6 @@ impl SyntaxSnapshot {
let changed_ranges;
let mut included_ranges = step.included_ranges;
let is_combined = matches!(step.mode, ParseMode::Combined { .. });
for range in &mut included_ranges {
range.start_byte -= step_start_byte;
range.end_byte -= step_start_byte;
@@ -751,20 +749,16 @@ impl SyntaxSnapshot {
);
}
let included_sub_ranges: Option<Vec<Range<Anchor>>> = if is_combined {
Some(
let included_sub_ranges: Option<Vec<Range<Anchor>>> =
(included_ranges.len() > 1).then_some(
included_ranges
.into_iter()
.filter(|r| r.start_byte < r.end_byte)
.map(|r| {
text.anchor_before(r.start_byte + step_start_byte)
..text.anchor_after(r.end_byte + step_start_byte)
})
.collect(),
)
} else {
None
};
);
SyntaxLayerContent::Parsed {
tree,
language,

View File

@@ -538,27 +538,27 @@ impl OpenAiEventMapper {
return events;
};
if let Some(delta) = choice.delta.as_ref() {
if let Some(content) = delta.content.clone() {
if let Some(content) = choice.delta.content.clone() {
if !content.is_empty() {
events.push(Ok(LanguageModelCompletionEvent::Text(content)));
}
}
if let Some(tool_calls) = delta.tool_calls.as_ref() {
for tool_call in tool_calls {
let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();
if let Some(tool_calls) = choice.delta.tool_calls.as_ref() {
for tool_call in tool_calls {
let entry = self.tool_calls_by_index.entry(tool_call.index).or_default();
if let Some(tool_id) = tool_call.id.clone() {
entry.id = tool_id;
if let Some(tool_id) = tool_call.id.clone() {
entry.id = tool_id;
}
if let Some(function) = tool_call.function.as_ref() {
if let Some(name) = function.name.clone() {
entry.name = name;
}
if let Some(function) = tool_call.function.as_ref() {
if let Some(name) = function.name.clone() {
entry.name = name;
}
if let Some(arguments) = function.arguments.clone() {
entry.arguments.push_str(&arguments);
}
if let Some(arguments) = function.arguments.clone() {
entry.arguments.push_str(&arguments);
}
}
}

View File

@@ -420,7 +420,7 @@ pub struct Usage {
#[derive(Serialize, Deserialize, Debug)]
pub struct ChoiceDelta {
pub index: u32,
pub delta: Option<ResponseMessageDelta>,
pub delta: ResponseMessageDelta,
pub finish_reason: Option<String>,
}

View File

@@ -319,8 +319,6 @@ impl BranchDiff {
});
}
});
// let names: Vec<_> = output.iter().map(|o| o.repo_path.as_unix_str()).collect();
// eprintln!("OUTPUT IS *********************************: {names:?}");
output
}

View File

@@ -12349,7 +12349,10 @@ impl LspStore {
.update(cx, |buffer, _| buffer.wait_for_version(version))?
.await?;
lsp_store.update(cx, |lsp_store, cx| {
let lsp_data = lsp_store.latest_lsp_data(&buffer, cx);
let lsp_data = lsp_store
.lsp_data
.entry(buffer_id)
.or_insert_with(|| BufferLspData::new(&buffer, cx));
let chunks_queried_for = lsp_data
.inlay_hints
.applicable_chunks(&[range])

View File

@@ -1070,21 +1070,14 @@ impl SshSocket {
}
async fn shell(&self) -> String {
let default_shell = "sh";
match self
.run_command(ShellKind::Posix, "sh", &["-c", "echo $SHELL"])
.await
{
Ok(shell) => match shell.trim() {
"" => {
log::error!("$SHELL is not set, falling back to {default_shell}");
default_shell.to_owned()
}
shell => shell.to_owned(),
},
Ok(shell) => shell.trim().to_owned(),
Err(e) => {
log::error!("Failed to get shell: {e}");
default_shell.to_owned()
"sh".to_owned()
}
}
}

View File

@@ -36,6 +36,7 @@ use std::{
mem,
ops::{Not, Range},
pin::pin,
rc::Rc,
sync::Arc,
};
use ui::{IconButtonShape, KeyBinding, Toggleable, Tooltip, prelude::*, utils::SearchInputWidth};
@@ -633,12 +634,7 @@ impl Item for ProjectSearchView {
});
}
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
fn navigate(&mut self, data: Rc<dyn Any>, window: &mut Window, cx: &mut Context<Self>) -> bool {
self.results_editor
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}

View File

@@ -1,5 +1,5 @@
use crate::prelude::*;
use gpui::{Animation, AnimationExt, FontWeight};
use gpui::{Animation, AnimationExt, FontWeight, pulsating_between};
use std::time::Duration;
#[derive(IntoElement)]
@@ -84,29 +84,38 @@ impl RenderOnce for LoadingLabel {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let text = self.text.clone();
self.base.color(Color::Muted).with_animations(
"loading_label",
vec![
Animation::new(Duration::from_secs(1)),
Animation::new(Duration::from_secs(1)).repeat(),
],
move |mut label, animation_ix, delta| {
match animation_ix {
0 => {
let chars_to_show = (delta * text.len() as f32).ceil() as usize;
let text = SharedString::from(text[0..chars_to_show].to_string());
label.set_text(text);
self.base
.color(Color::Muted)
.with_animations(
"loading_label",
vec![
Animation::new(Duration::from_secs(1)),
Animation::new(Duration::from_secs(1)).repeat(),
],
move |mut label, animation_ix, delta| {
match animation_ix {
0 => {
let chars_to_show = (delta * text.len() as f32).ceil() as usize;
let text = SharedString::from(text[0..chars_to_show].to_string());
label.set_text(text);
}
1 => match delta {
d if d < 0.25 => label.set_text(text.clone()),
d if d < 0.5 => label.set_text(format!("{}.", text)),
d if d < 0.75 => label.set_text(format!("{}..", text)),
_ => label.set_text(format!("{}...", text)),
},
_ => {}
}
1 => match delta {
d if d < 0.25 => label.set_text(text.clone()),
d if d < 0.5 => label.set_text(format!("{}.", text)),
d if d < 0.75 => label.set_text(format!("{}..", text)),
_ => label.set_text(format!("{}...", text)),
},
_ => {}
}
label
},
)
label
},
)
.with_animation(
"pulsating-label",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(0.6, 1.)),
|label, delta| label.map_element(|label| label.alpha(delta)),
)
}
}

View File

@@ -8,7 +8,6 @@ pub enum SpinnerVariant {
#[default]
Dots,
DotsVariant,
Sand,
}
/// A spinner indication, based on the label component, that loops through
@@ -42,11 +41,6 @@ impl SpinnerVariant {
match self {
SpinnerVariant::Dots => vec!["", "", "", "", "", "", "", "", "", ""],
SpinnerVariant::DotsVariant => vec!["", "", "", "", "", "", "", ""],
SpinnerVariant::Sand => vec![
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "",
],
}
}
@@ -54,7 +48,6 @@ impl SpinnerVariant {
match self {
SpinnerVariant::Dots => Duration::from_millis(1000),
SpinnerVariant::DotsVariant => Duration::from_millis(1000),
SpinnerVariant::Sand => Duration::from_millis(2000),
}
}
@@ -62,7 +55,6 @@ impl SpinnerVariant {
match self {
SpinnerVariant::Dots => "spinner_label_dots",
SpinnerVariant::DotsVariant => "spinner_label_dots_variant",
SpinnerVariant::Sand => "spinner_label_dots_variant_2",
}
}
}
@@ -91,10 +83,6 @@ impl SpinnerLabel {
pub fn dots_variant() -> Self {
Self::with_variant(SpinnerVariant::DotsVariant)
}
pub fn sand() -> Self {
Self::with_variant(SpinnerVariant::Sand)
}
}
impl LabelCommon for SpinnerLabel {
@@ -197,7 +185,6 @@ impl Component for SpinnerLabel {
"Dots Variant",
SpinnerLabel::dots_variant().into_any_element(),
),
single_example("Sand Variant", SpinnerLabel::sand().into_any_element()),
];
Some(example_group(examples).vertical().into_any_element())

View File

@@ -194,7 +194,7 @@ pub trait Item: Focusable + EventEmitter<Self::Event> + Render + Sized {
fn discarded(&self, _project: Entity<Project>, _window: &mut Window, _cx: &mut Context<Self>) {}
fn on_removed(&self, _cx: &App) {}
fn workspace_deactivated(&mut self, _window: &mut Window, _: &mut Context<Self>) {}
fn navigate(&mut self, _: Box<dyn Any>, _window: &mut Window, _: &mut Context<Self>) -> bool {
fn navigate(&mut self, _: Rc<dyn Any>, _window: &mut Window, _: &mut Context<Self>) -> bool {
false
}
@@ -449,7 +449,7 @@ pub trait ItemHandle: 'static + Send {
fn deactivated(&self, window: &mut Window, cx: &mut App);
fn on_removed(&self, cx: &App);
fn workspace_deactivated(&self, window: &mut Window, cx: &mut App);
fn navigate(&self, data: Box<dyn Any>, window: &mut Window, cx: &mut App) -> bool;
fn navigate(&self, data: Rc<dyn Any>, window: &mut Window, cx: &mut App) -> bool;
fn item_id(&self) -> EntityId;
fn to_any(&self) -> AnyView;
fn is_dirty(&self, cx: &App) -> bool;
@@ -900,7 +900,7 @@ impl<T: Item> ItemHandle for Entity<T> {
self.update(cx, |this, cx| this.workspace_deactivated(window, cx));
}
fn navigate(&self, data: Box<dyn Any>, window: &mut Window, cx: &mut App) -> bool {
fn navigate(&self, data: Rc<dyn Any>, window: &mut Window, cx: &mut App) -> bool {
self.update(cx, |this, cx| this.navigate(data, window, cx))
}
@@ -1277,7 +1277,7 @@ pub mod test {
InteractiveElement, IntoElement, Render, SharedString, Task, WeakEntity, Window,
};
use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
use std::{any::Any, cell::Cell};
use std::{any::Any, cell::Cell, rc::Rc};
use util::rel_path::rel_path;
pub struct TestProjectItem {
@@ -1510,11 +1510,14 @@ pub mod test {
fn navigate(
&mut self,
state: Box<dyn Any>,
state: Rc<dyn Any>,
_window: &mut Window,
_: &mut Context<Self>,
) -> bool {
let state = *state.downcast::<String>().unwrap_or_default();
let state = state
.downcast_ref::<String>()
.map(|s| s.to_string())
.unwrap_or_default();
if state != self.state {
self.state = state;
true

View File

@@ -211,6 +211,10 @@ actions!(
GoBack,
/// Navigates forward in history.
GoForward,
/// Navigates back in the tag stack.
GoToOlderTag,
/// Navigates forward in the tag stack.
GoToNewerTag,
/// Joins this pane into the next pane.
JoinIntoNext,
/// Joins all panes into one.
@@ -417,6 +421,9 @@ struct NavHistoryState {
backward_stack: VecDeque<NavigationEntry>,
forward_stack: VecDeque<NavigationEntry>,
closed_stack: VecDeque<NavigationEntry>,
tag_stack: VecDeque<(NavigationEntry, NavigationEntry)>,
tag_stack_pos: usize,
pending_tag_source: Option<NavigationEntry>,
paths_by_item: HashMap<EntityId, (ProjectPath, Option<PathBuf>)>,
pane: WeakEntity<Pane>,
next_timestamp: Arc<AtomicUsize>,
@@ -438,9 +445,10 @@ impl Default for NavigationMode {
}
}
#[derive(Clone)]
pub struct NavigationEntry {
pub item: Arc<dyn WeakItemHandle>,
pub data: Option<Box<dyn Any + Send>>,
pub data: Option<Rc<dyn Any + Send>>,
pub timestamp: usize,
pub is_preview: bool,
}
@@ -513,6 +521,9 @@ impl Pane {
backward_stack: Default::default(),
forward_stack: Default::default(),
closed_stack: Default::default(),
tag_stack: Default::default(),
tag_stack_pos: 0,
pending_tag_source: None,
paths_by_item: Default::default(),
pane: handle,
next_timestamp,
@@ -850,6 +861,24 @@ impl Pane {
}
}
pub fn go_to_older_tag(
&mut self,
_: &GoToOlderTag,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(workspace) = self.workspace.upgrade() {
let pane = cx.entity().downgrade();
window.defer(cx, move |window, cx| {
workspace.update(cx, |workspace, cx| {
workspace
.navigate_tag_history(pane, window, cx)
.detach_and_log_err(cx)
})
})
}
}
fn history_updated(&mut self, cx: &mut Context<Self>) {
self.toolbar.update(cx, |_, cx| cx.notify());
}
@@ -3756,6 +3785,7 @@ impl Render for Pane {
.on_action(cx.listener(Pane::toggle_zoom))
.on_action(cx.listener(Self::navigate_backward))
.on_action(cx.listener(Self::navigate_forward))
.on_action(cx.listener(Self::go_to_older_tag))
.on_action(
cx.listener(|pane: &mut Pane, action: &ActivateItem, window, cx| {
pane.activate_item(
@@ -3996,8 +4026,40 @@ impl ItemNavHistory {
self.history.pop(NavigationMode::GoingBack, cx)
}
pub fn pop_forward(&mut self, cx: &mut App) -> Option<NavigationEntry> {
self.history.pop(NavigationMode::GoingForward, cx)
pub fn start_tag_jump<D>(&mut self, data: Option<D>, cx: &mut App)
where
D: 'static + Any + Send,
{
if self
.item
.upgrade()
.is_some_and(|item| item.include_in_nav_history())
{
self.history.start_tag_jump(
data.map(|data| Rc::new(data) as Rc<dyn Any + Send>),
self.item.clone(),
self.is_preview,
cx,
);
}
}
pub fn finish_tag_jump<D>(&mut self, data: Option<D>, cx: &mut App)
where
D: 'static + Any + Send,
{
if self
.item
.upgrade()
.is_some_and(|item| item.include_in_nav_history())
{
self.history.finish_tag_jump(
data.map(|data| Rc::new(data) as Rc<dyn Any + Send>),
self.item.clone(),
self.is_preview,
cx,
);
}
}
}
@@ -4075,7 +4137,7 @@ impl NavHistory {
}
state.backward_stack.push_back(NavigationEntry {
item,
data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
data: data.map(|data| Rc::new(data) as Rc<dyn Any + Send>),
timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
is_preview,
});
@@ -4087,7 +4149,7 @@ impl NavHistory {
}
state.forward_stack.push_back(NavigationEntry {
item,
data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
data: data.map(|data| Rc::new(data) as Rc<dyn Any + Send>),
timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
is_preview,
});
@@ -4098,7 +4160,7 @@ impl NavHistory {
}
state.backward_stack.push_back(NavigationEntry {
item,
data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
data: data.map(|data| Rc::new(data) as Rc<dyn Any + Send>),
timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
is_preview,
});
@@ -4109,7 +4171,7 @@ impl NavHistory {
}
state.closed_stack.push_back(NavigationEntry {
item,
data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
data: data.map(|data| Rc::new(data) as Rc<dyn Any + Send>),
timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
is_preview,
});
@@ -4135,6 +4197,55 @@ impl NavHistory {
pub fn path_for_item(&self, item_id: EntityId) -> Option<(ProjectPath, Option<PathBuf>)> {
self.0.lock().paths_by_item.get(&item_id).cloned()
}
pub fn start_tag_jump(
&mut self,
data: Option<Rc<dyn Any + Send>>,
item: Arc<dyn WeakItemHandle>,
is_preview: bool,
_cx: &mut App,
) {
self.0.lock().pending_tag_source.replace(NavigationEntry {
item,
data,
timestamp: 0,
is_preview,
});
}
pub fn finish_tag_jump(
&mut self,
data: Option<Rc<dyn Any + Send>>,
item: Arc<dyn WeakItemHandle>,
is_preview: bool,
_cx: &mut App,
) {
let mut state = self.0.lock();
let Some(source) = state.pending_tag_source.take() else {
debug_panic!("Finished tag jump without starting one?");
return;
};
let dest = NavigationEntry {
item,
data,
timestamp: 0,
is_preview,
};
let truncate_to = state.tag_stack_pos;
state.tag_stack.truncate(truncate_to);
state.tag_stack.push_back((source, dest));
state.tag_stack_pos += 1;
}
pub fn tag_stack_back(&mut self) -> Option<NavigationEntry> {
let mut state = self.0.lock();
if state.tag_stack_pos > 0 {
state.tag_stack_pos -= 1;
Some(state.tag_stack[state.tag_stack_pos].0.clone())
} else {
None
}
}
}
impl NavHistoryState {

View File

@@ -1927,6 +1927,127 @@ impl Workspace {
.collect()
}
fn navigate_tag_history(
&mut self,
pane: WeakEntity<Pane>,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Task<Result<()>> {
let to_load = if let Some(pane) = pane.upgrade() {
pane.update(cx, |pane, cx| {
window.focus(&pane.focus_handle(cx));
loop {
// Retrieve the weak item handle from the history.
let entry = pane.nav_history_mut().tag_stack_back()?;
// If the item is still present in this pane, then activate it.
if let Some(index) = entry
.item
.upgrade()
.and_then(|v| pane.index_for_item(v.as_ref()))
{
let prev_active_item_index = pane.active_item_index();
pane.activate_item(index, true, true, window, cx);
let mut navigated = prev_active_item_index != pane.active_item_index();
if let Some(data) = entry.data {
navigated |= pane.active_item()?.navigate(data, window, cx);
}
if navigated {
break None;
}
} else {
// If the item is no longer present in this pane, then retrieve its
// path info in order to reopen it.
break pane
.nav_history()
.path_for_item(entry.item.id())
.map(|(project_path, abs_path)| (project_path, abs_path, entry));
}
}
})
} else {
None
};
if let Some((project_path, abs_path, entry)) = to_load {
// If the item was no longer present, then load it again from its previous path, first try the local path
let open_by_project_path = self.load_path(project_path.clone(), window, cx);
cx.spawn_in(window, async move |workspace, cx| {
let open_by_project_path = open_by_project_path.await;
let mut navigated = false;
match open_by_project_path
.with_context(|| format!("Navigating to {project_path:?}"))
{
Ok((project_entry_id, build_item)) => {
let prev_active_item_id = pane.update(cx, |pane, _| {
pane.active_item().map(|p| p.item_id())
})?;
pane.update_in(cx, |pane, window, cx| {
let item = pane.open_item(
project_entry_id,
project_path,
true,
entry.is_preview,
true,
None,
window, cx,
build_item,
);
navigated |= Some(item.item_id()) != prev_active_item_id;
if let Some(data) = entry.data {
navigated |= item.navigate(data, window, cx);
}
})?;
}
Err(open_by_project_path_e) => {
// Fall back to opening by abs path, in case an external file was opened and closed,
// and its worktree is now dropped
if let Some(abs_path) = abs_path {
let prev_active_item_id = pane.update(cx, |pane, _| {
pane.active_item().map(|p| p.item_id())
})?;
let open_by_abs_path = workspace.update_in(cx, |workspace, window, cx| {
workspace.open_abs_path(abs_path.clone(), OpenOptions { visible: Some(OpenVisible::None), ..Default::default() }, window, cx)
})?;
match open_by_abs_path
.await
.with_context(|| format!("Navigating to {abs_path:?}"))
{
Ok(item) => {
pane.update_in(cx, |pane, window, cx| {
navigated |= Some(item.item_id()) != prev_active_item_id;
if let Some(data) = entry.data {
navigated |= item.navigate(data, window, cx);
}
})?;
}
Err(open_by_abs_path_e) => {
log::error!("Failed to navigate history: {open_by_project_path_e:#} and {open_by_abs_path_e:#}");
}
}
}
}
}
if !navigated {
workspace
.update_in(cx, |workspace, window, cx| {
Self::navigate_tag_history(workspace, pane, window, cx)
})?
.await?;
}
Ok(())
})
} else {
Task::ready(Ok(()))
}
}
fn navigate_history(
&mut self,
pane: WeakEntity<Pane>,

View File

@@ -17,7 +17,6 @@ To debug code written in a specific language, Zed needs to find a debug adapter
- [C](./languages/c.md#debugging) (built-in)
- [C++](./languages/cpp.md#debugging) (built-in)
- [Go](./languages/go.md#debugging) (built-in)
- [Java](./languages/java.md#debugging) (provided by extension)
- [JavaScript](./languages/javascript.md#debugging) (built-in)
- [PHP](./languages/php.md#debugging) (built-in)
- [Python](./languages/python.md#debugging) (built-in)

View File

@@ -19,149 +19,150 @@ Or manually download and install [OpenJDK 23](https://jdk.java.net/23/).
## Extension Install
You can install by opening {#action zed::Extensions}({#kb zed::Extensions}) and searching for `java`.
You can install either by opening {#action zed::Extensions}({#kb zed::Extensions}) and searching for `java`.
## Quick start and configuration
## Settings / Initialization Options
For the majority of users, Java support should work out of the box.
The extension will automatically download the language server, see: [Manual JDTLS Install](#manual-jdts-install) below if you'd prefer to manage that yourself.
- It is generally recommended to open projects with the Zed-project root at the Java project root folder (where you would commonly have your `pom.xml` or `build.gradle` file).
For available `initialization_options` please see the [Initialize Request section of the Eclipse.jdt.ls Wiki](https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request).
- By default the extension will download and run the latest official version of JDTLS for you, but this requires Java version 21 to be available on your system via either the `$JAVA_HOME` environment variable or as a `java(.exe)` executable on your `$PATH`. If your project requires a lower Java version in the environment, you can specify a different JDK to use for running JDTLS via the `java_home` configuration option.
You can add these customizations to your Zed Settings by launching {#action zed::OpenSettings}({#kb zed::OpenSettings}) or by using a `.zed/setting.json` inside your project.
- You can provide a **custom launch script for JDTLS**, by adding an executable named `jdtls` (or `jdtls.bat` on Windows) to your `$PATH` environment variable. If this is present, the extension will skip downloading and launching a managed instance and use the one from the environment.
### Zed Java Settings
- To support [Lombok](https://projectlombok.org/), the lombok-jar must be downloaded and registered as a Java-Agent when launching JDTLS. By default the extension automatically takes care of that, but in case you don't want that you can set the `lombok_support` configuration-option to `false`.
Here is a common `settings.json` including the above mentioned configurations:
```jsonc
```json [settings]
{
"lsp": {
"jdtls": {
"settings": {
"java_home": "/path/to/your/JDK21+",
"lombok_support": true,
},
},
},
"initialization_options": {}
}
}
}
```
## Debugging
## Example Configs
Debug support is enabled via our [Fork of Java Debug](https://github.com/zed-industries/java-debug), which the extension will automatically download and start for you. Please refer to the [Debugger Documentation](https://zed.dev/docs/debugger#getting-started) for general information about how debugging works in Zed.
### JDTLS Binary
To get started with Java, click the `edit debug.json` button in the Debug menu, and replace the contents of the file with the following:
By default, zed will look in your `PATH` for a `jdtls` binary, if you wish to specify an explicit binary you can do so via settings:
```jsonc
[
{
"adapter": "Java",
"request": "launch",
"label": "Launch Debugger",
// if your project has multiple entry points, specify the one to use:
// "mainClass": "com.myorganization.myproject.MyMainClass",
//
// this effectively sets a breakpoint at your program entry:
"stopOnEntry": true,
// the working directory for the debug process
"cwd": "$ZED_WORKTREE_ROOT",
},
]
```json [settings]
"lsp": {
"jdtls": {
"binary": {
"path": "/path/to/java/bin/jdtls",
// "arguments": [],
// "env": {},
"ignore_system_version": true
}
}
}
```
You should then be able to start a new Debug Session with the "Launch Debugger" scenario from the debug menu.
### Zed Java Initialization Options
## Launch Scripts (aka Tasks) in Windows
There are also many more options you can pass directly to the language server, for example:
This extension provides tasks for running your application and tests from within Zed via little play buttons next to tests/entry points. However, due to current limitations of Zed's extension interface, we can not provide scripts that will work across Maven and Gradle on both Windows and Unix-compatible systems, so out of the box the launch scripts only work on Mac and Linux.
There is a fairly straightforward fix that you can apply to make it work on Windows by supplying your own task scripts. Please see [this Issue](https://github.com/zed-extensions/java/issues/94) for information on how to do that and read the [Tasks section in Zeds documentation](https://zed.dev/docs/tasks) for more information.
## Advanced Configuration/JDTLS initialization Options
JDTLS provides many configuration options that can be passed via the `initialize` LSP-request. The extension will pass the JSON-object from `lsp.jdtls.settings.initialization_options` in your settings on to JDTLS. Please refer to the [JDTLS Configuration Wiki Page](https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request) for the available options and values. Below is an example `settings.json` that would pass on the example configuration from the above wiki page to JDTLS:
```jsonc
```json [settings]
{
"lsp": {
"jdtls": {
"settings": {
// this will be sent to JDTLS as initializationOptions:
"initialization_options": {
"bundles": [],
// use this if your zed project root folder is not the same as the java project root:
"workspaceFolders": ["file:///home/snjeza/Project"],
"settings": {
"java": {
"home": "/usr/local/jdk-9.0.1",
"errors": {
"incompleteClasspath": {
"severity": "warning",
},
},
"configuration": {
"updateBuildConfiguration": "interactive",
"maven": {
"userSettings": null,
},
},
"import": {
"gradle": {
"enabled": true,
},
"maven": {
"enabled": true,
},
"exclusions": [
"**/node_modules/**",
"**/.metadata/**",
"**/archetype-resources/**",
"**/META-INF/maven/**",
"/**/test/**",
],
},
"referencesCodeLens": {
"enabled": false,
},
"signatureHelp": {
"enabled": false,
},
"implementationCodeLens": "all",
"format": {
"enabled": true,
},
"saveActions": {
"organizeImports": false,
},
"contentProvider": {
"preferred": null,
},
"autobuild": {
"enabled": false,
},
"completion": {
"favoriteStaticMembers": [
"org.junit.Assert.*",
"org.junit.Assume.*",
"org.junit.jupiter.api.Assertions.*",
"org.junit.jupiter.api.Assumptions.*",
"org.junit.jupiter.api.DynamicContainer.*",
"org.junit.jupiter.api.DynamicTest.*",
],
"importOrder": ["java", "javax", "com", "org"],
},
"initialization_options": {
"bundles": [],
"workspaceFolders": ["file:///home/snjeza/Project"],
"settings": {
"java": {
"home": "/usr/local/jdk-9.0.1",
"errors": {
"incompleteClasspath": {
"severity": "warning"
}
},
},
},
},
},
},
"configuration": {
"updateBuildConfiguration": "interactive",
"maven": {
"userSettings": null
}
},
"trace": {
"server": "verbose"
},
"import": {
"gradle": {
"enabled": true
},
"maven": {
"enabled": true
},
"exclusions": [
"**/node_modules/**",
"**/.metadata/**",
"**/archetype-resources/**",
"**/META-INF/maven/**",
"/**/test/**"
]
},
"jdt": {
"ls": {
"lombokSupport": {
"enabled": false // Set this to true to enable lombok support
}
}
},
"referencesCodeLens": {
"enabled": false
},
"signatureHelp": {
"enabled": false
},
"implementationsCodeLens": {
"enabled": false
},
"format": {
"enabled": true
},
"saveActions": {
"organizeImports": false
},
"contentProvider": {
"preferred": null
},
"autobuild": {
"enabled": false
},
"completion": {
"favoriteStaticMembers": [
"org.junit.Assert.*",
"org.junit.Assume.*",
"org.junit.jupiter.api.Assertions.*",
"org.junit.jupiter.api.Assumptions.*",
"org.junit.jupiter.api.DynamicContainer.*",
"org.junit.jupiter.api.DynamicTest.*"
],
"importOrder": ["java", "javax", "com", "org"]
}
}
}
}
}
}
}
```
## Manual JDTLS Install
If you prefer, you can install JDTLS yourself and the extension can be configured to use that instead.
- macOS: `brew install jdtls`
- Arch: [`jdtls` from AUR](https://aur.archlinux.org/packages/jdtls)
Or manually download install:
- [JDTLS Milestone Builds](http://download.eclipse.org/jdtls/milestones/) (updated every two weeks)
- [JDTLS Snapshot Builds](https://download.eclipse.org/jdtls/snapshots/) (frequent updates)
## See also
[Zed Java Repo](https://github.com/zed-extensions/java)
[Eclipse JDTLS Repo](https://github.com/eclipse-jdtls/eclipse.jdt.ls)
- [Zed Java Repo](https://github.com/zed-extensions/java)
- [Zed Java Issues](https://github.com/zed-extensions/java/issues)

View File

@@ -71,18 +71,6 @@ To switch to `ruby-lsp`, add the following to your `settings.json`:
"languages": {
"Ruby": {
"language_servers": ["ruby-lsp", "!solargraph", "!rubocop", "..."]
},
// Enable herb and ruby-lsp for *.html.erb files
"HTML+ERB": {
"language_servers": ["herb", "ruby-lsp", "..."]
},
// Enable ruby-lsp for *.js.erb files
"JS+ERB": {
"language_servers": ["ruby-lsp", "..."]
},
// Enable ruby-lsp for *.yaml.erb files
"YAML+ERB": {
"language_servers": ["ruby-lsp", "..."]
}
}
}