diff --git a/Cargo.lock b/Cargo.lock index a94de5439459062bbf63cc8362d6107d59047ed3..3cdbe5730d4d5c71614796d535939d4173a0b5d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16823,13 +16823,6 @@ dependencies = [ "zed_extension_api 0.1.0", ] -[[package]] -name = "zed_php" -version = "0.2.4" -dependencies = [ - "zed_extension_api 0.1.0", -] - [[package]] name = "zed_proto" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index e0481d632a900f24bd80b9d73e64fb3e5ad446ad..935b05337814c9032d268598c200050a6772e395 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -171,7 +171,6 @@ members = [ "extensions/haskell", "extensions/html", "extensions/lua", - "extensions/php", "extensions/perplexity", "extensions/proto", "extensions/purescript", diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 286440f9896a7f22e2cafed614861f09ee64e921..370d6d1119dfd6324e1d47d0bfbf8e98be52b5b2 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -1255,7 +1255,7 @@ impl InlineAssistant { editor.scroll_manager.set_forbid_vertical_scroll(true); editor.set_show_scrollbars(false, cx); editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.highlight_rows::( Anchor::min()..Anchor::max(), cx.theme().status().deleted_background, diff --git a/crates/assistant2/src/inline_assistant.rs b/crates/assistant2/src/inline_assistant.rs index a59534148161f4d82c2fc6da41c4c77a246f51d5..2158f3279f6a959e13e667e0aa033273d5a5dd76 100644 --- a/crates/assistant2/src/inline_assistant.rs +++ b/crates/assistant2/src/inline_assistant.rs @@ -1345,7 +1345,7 @@ impl InlineAssistant { editor.scroll_manager.set_forbid_vertical_scroll(true); editor.set_show_scrollbars(false, cx); editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.highlight_rows::( Anchor::min()..Anchor::max(), cx.theme().status().deleted_background, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bdf1fd905686049ac33ab68c28edb202de20ee6d..fba395c906bdf04e390aabf0e9fd0b6a975d615f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -190,6 +190,7 @@ pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250); pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1); +pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction"; pub(crate) const EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT: &str = "edit_prediction_requires_modifier"; @@ -497,6 +498,23 @@ struct InlineCompletionState { invalidation_range: Range, } +enum EditPredictionSettings { + Disabled, + Enabled { + show_in_menu: bool, + preview_requires_modifier: bool, + }, +} + +impl EditPredictionSettings { + pub fn is_enabled(&self) -> bool { + match self { + EditPredictionSettings::Disabled => false, + EditPredictionSettings::Enabled { .. } => true, + } + } +} + enum InlineCompletionHighlight {} pub enum MenuInlineCompletionsPolicy { @@ -683,6 +701,7 @@ pub struct Editor { active_inline_completion: Option, /// Used to prevent flickering as the user types while the menu is open stale_inline_completion_in_menu: Option, + edit_prediction_settings: EditPredictionSettings, inline_completions_hidden_for_vim_mode: bool, show_inline_completions_override: Option, menu_inline_completions_policy: MenuInlineCompletionsPolicy, @@ -1395,6 +1414,7 @@ impl Editor { inline_completions_hidden_for_vim_mode: false, show_inline_completions_override: None, menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider, + edit_prediction_settings: EditPredictionSettings::Disabled, custom_context_menu: None, show_git_blame_gutter: false, show_git_blame_inline: false, @@ -1469,13 +1489,13 @@ impl Editor { this } - pub fn mouse_menu_is_focused(&self, window: &mut Window, cx: &mut App) -> bool { + pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool { self.mouse_context_menu .as_ref() .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window)) } - fn key_context(&self, window: &mut Window, cx: &mut Context) -> KeyContext { + fn key_context(&self, window: &Window, cx: &App) -> KeyContext { let mut key_context = KeyContext::new_with_defaults(); key_context.add("Editor"); let mode = match self.mode { @@ -1528,9 +1548,9 @@ impl Editor { if self.has_active_inline_completion() { key_context.add("copilot_suggestion"); - key_context.add("edit_prediction"); + key_context.add(EDIT_PREDICTION_KEY_CONTEXT); - if showing_completions || self.edit_prediction_requires_modifier(cx) { + if showing_completions || self.edit_prediction_requires_modifier() { key_context.add(EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT); } } @@ -1542,6 +1562,22 @@ impl Editor { key_context } + pub fn accept_edit_prediction_keybind( + &self, + window: &Window, + cx: &App, + ) -> AcceptEditPredictionBinding { + let mut context = self.key_context(window, cx); + context.add(EDIT_PREDICTION_KEY_CONTEXT); + + AcceptEditPredictionBinding( + window + .bindings_for_action_in_context(&AcceptEditPrediction, context) + .into_iter() + .next(), + ) + } + pub fn new_file( workspace: &mut Workspace, _: &workspace::NewFile, @@ -1886,23 +1922,14 @@ impl Editor { cx: &mut Context, ) { if self.show_inline_completions_override.is_some() { - self.set_show_inline_completions(None, window, cx); + self.set_show_edit_predictions(None, window, cx); } else { - let cursor = self.selections.newest_anchor().head(); - if let Some((buffer, cursor_buffer_position)) = - self.buffer.read(cx).text_anchor_for_position(cursor, cx) - { - let show_inline_completions = !self.should_show_inline_completions_in_buffer( - &buffer, - cursor_buffer_position, - cx, - ); - self.set_show_inline_completions(Some(show_inline_completions), window, cx); - } + let show_edit_predictions = !self.edit_predictions_enabled(); + self.set_show_edit_predictions(Some(show_edit_predictions), window, cx); } } - pub fn set_show_inline_completions( + pub fn set_show_edit_predictions( &mut self, show_edit_predictions: Option, window: &mut Window, @@ -3019,7 +3046,7 @@ impl Editor { } let trigger_in_words = - this.show_edit_predictions_in_menu(cx) || !had_active_inline_completion; + this.show_edit_predictions_in_menu() || !had_active_inline_completion; this.trigger_completion_on_input(&text, trigger_in_words, window, cx); linked_editing_ranges::refresh_linked_ranges(this, window, cx); this.refresh_inline_completion(true, false, window, cx); @@ -3912,7 +3939,7 @@ impl Editor { *editor.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(menu)); - if editor.show_edit_predictions_in_menu(cx) { + if editor.show_edit_predictions_in_menu() { editor.update_visible_inline_completion(window, cx); } else { editor.discard_inline_completion(false, cx); @@ -3926,7 +3953,7 @@ impl Editor { // If it was already hidden and we don't show inline // completions in the menu, we should also show the // inline-completion when available. - if was_hidden && editor.show_edit_predictions_in_menu(cx) { + if was_hidden && editor.show_edit_predictions_in_menu() { editor.update_visible_inline_completion(window, cx); } } @@ -3976,7 +4003,7 @@ impl Editor { let entries = completions_menu.entries.borrow(); let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?; - if self.show_edit_predictions_in_menu(cx) { + if self.show_edit_predictions_in_menu() { self.discard_inline_completion(true, cx); } let candidate_id = mat.candidate_id; @@ -4668,7 +4695,7 @@ impl Editor { } if !user_requested - && (!self.should_show_inline_completions_in_buffer(&buffer, cursor_buffer_position, cx) + && (!self.should_show_edit_predictions() || !self.is_focused(window) || buffer.read(cx).is_empty()) { @@ -4687,58 +4714,79 @@ impl Editor { Some(()) } - pub fn should_show_inline_completions(&self, cx: &App) -> bool { - let cursor = self.selections.newest_anchor().head(); - if let Some((buffer, cursor_position)) = - self.buffer.read(cx).text_anchor_for_position(cursor, cx) - { - self.should_show_inline_completions_in_buffer(&buffer, cursor_position, cx) - } else { - false + fn show_edit_predictions_in_menu(&self) -> bool { + match self.edit_prediction_settings { + EditPredictionSettings::Disabled => false, + EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu, } } - fn edit_prediction_requires_modifier(&self, cx: &App) -> bool { - let cursor = self.selections.newest_anchor().head(); + pub fn edit_predictions_enabled(&self) -> bool { + match self.edit_prediction_settings { + EditPredictionSettings::Disabled => false, + EditPredictionSettings::Enabled { .. } => true, + } + } - self.buffer - .read(cx) - .text_anchor_for_position(cursor, cx) - .map(|(buffer, _)| { - all_language_settings(buffer.read(cx).file(), cx).inline_completions_preview_mode() - == InlineCompletionPreviewMode::WhenHoldingModifier - }) - .unwrap_or(false) + fn edit_prediction_requires_modifier(&self) -> bool { + match self.edit_prediction_settings { + EditPredictionSettings::Disabled => false, + EditPredictionSettings::Enabled { + preview_requires_modifier, + .. + } => preview_requires_modifier, + } } - fn should_show_inline_completions_in_buffer( + fn edit_prediction_settings_at_position( &self, buffer: &Entity, buffer_position: language::Anchor, cx: &App, - ) -> bool { - if !self.snippet_stack.is_empty() { - return false; + ) -> EditPredictionSettings { + if self.mode != EditorMode::Full + || !self.show_inline_completions_override.unwrap_or(true) + || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx) + { + return EditPredictionSettings::Disabled; } - if self.inline_completions_disabled_in_scope(buffer, buffer_position, cx) { - return false; - } + let buffer = buffer.read(cx); - if let Some(show_inline_completions) = self.show_inline_completions_override { - show_inline_completions - } else { - let buffer = buffer.read(cx); - self.mode == EditorMode::Full - && language_settings( - buffer.language_at(buffer_position).map(|l| l.name()), - buffer.file(), - cx, - ) - .show_edit_predictions + let file = buffer.file(); + + if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions { + return EditPredictionSettings::Disabled; + }; + + let by_provider = matches!( + self.menu_inline_completions_policy, + MenuInlineCompletionsPolicy::ByProvider + ); + + let show_in_menu = by_provider + && EditorSettings::get_global(cx).show_edit_predictions_in_menu + && self + .edit_prediction_provider + .as_ref() + .map_or(false, |provider| { + provider.provider.show_completions_in_menu() + }); + + let preview_requires_modifier = all_language_settings(file, cx) + .inline_completions_preview_mode() + == InlineCompletionPreviewMode::WhenHoldingModifier; + + EditPredictionSettings::Enabled { + show_in_menu, + preview_requires_modifier, } } + fn should_show_edit_predictions(&self) -> bool { + self.snippet_stack.is_empty() && self.edit_predictions_enabled() + } + pub fn inline_completions_enabled(&self, cx: &App) -> bool { let cursor = self.selections.newest_anchor().head(); if let Some((buffer, cursor_position)) = @@ -4781,9 +4829,7 @@ impl Editor { let cursor = self.selections.newest_anchor().head(); let (buffer, cursor_buffer_position) = self.buffer.read(cx).text_anchor_for_position(cursor, cx)?; - if self.inline_completions_hidden_for_vim_mode - || !self.should_show_inline_completions_in_buffer(&buffer, cursor_buffer_position, cx) - { + if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() { return None; } @@ -4889,7 +4935,7 @@ impl Editor { } } - if self.show_edit_predictions_in_menu(cx) { + if self.show_edit_predictions_in_menu() { self.hide_context_menu(window, cx); } @@ -5076,14 +5122,10 @@ impl Editor { /// Returns true when we're displaying the inline completion popover below the cursor /// like we are not previewing and the LSP autocomplete menu is visible /// or we are in `when_holding_modifier` mode. - pub fn inline_completion_visible_in_cursor_popover( - &self, - has_completion: bool, - cx: &App, - ) -> bool { + pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool { if self.previewing_inline_completion - || !self.show_edit_predictions_in_menu(cx) - || !self.should_show_inline_completions(cx) + || !self.show_edit_predictions_in_menu() + || !self.edit_predictions_enabled() { return false; } @@ -5092,7 +5134,7 @@ impl Editor { return true; } - has_completion && self.edit_prediction_requires_modifier(cx) + has_completion && self.edit_prediction_requires_modifier() } fn handle_modifiers_changed( @@ -5102,9 +5144,8 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - if !self.show_edit_predictions_in_menu(cx) { - let accept_binding = - AcceptEditPredictionBinding::resolve(self.focus_handle(cx), window); + if self.show_edit_predictions_in_menu() { + let accept_binding = self.accept_edit_prediction_keybind(window, cx); if let Some(accept_keystroke) = accept_binding.keystroke() { let was_previewing_inline_completion = self.previewing_inline_completion; self.previewing_inline_completion = modifiers == accept_keystroke.modifiers @@ -5140,10 +5181,11 @@ impl Editor { let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer)); let excerpt_id = cursor.excerpt_id; - let show_in_menu = self.show_edit_predictions_in_menu(cx); + let show_in_menu = self.show_edit_predictions_in_menu(); let completions_menu_has_precedence = !show_in_menu && (self.context_menu.borrow().is_some() || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion())); + if completions_menu_has_precedence || !offset_selection.is_empty() || self @@ -5160,11 +5202,22 @@ impl Editor { } self.take_active_inline_completion(cx); - let provider = self.edit_prediction_provider()?; + let Some(provider) = self.edit_prediction_provider() else { + self.edit_prediction_settings = EditPredictionSettings::Disabled; + return None; + }; let (buffer, cursor_buffer_position) = self.buffer.read(cx).text_anchor_for_position(cursor, cx)?; + self.edit_prediction_settings = + self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx); + + if !self.edit_prediction_settings.is_enabled() { + self.discard_inline_completion(false, cx); + return None; + } + let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?; let edits = inline_completion .edits @@ -5223,8 +5276,7 @@ impl Editor { snapshot, } } else { - let show_completions_in_buffer = !self - .inline_completion_visible_in_cursor_popover(true, cx) + let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true) && !self.inline_completions_hidden_for_vim_mode; if show_completions_in_buffer { if edits @@ -5300,19 +5352,6 @@ impl Editor { Some(self.edit_prediction_provider.as_ref()?.provider.clone()) } - fn show_edit_predictions_in_menu(&self, cx: &App) -> bool { - let by_provider = matches!( - self.menu_inline_completions_policy, - MenuInlineCompletionsPolicy::ByProvider - ); - - by_provider - && EditorSettings::get_global(cx).show_edit_predictions_in_menu - && self - .edit_prediction_provider() - .map_or(false, |provider| provider.show_completions_in_menu()) - } - fn render_code_actions_indicator( &self, _style: &EditorStyle, @@ -14385,7 +14424,8 @@ impl Editor { }); supports } - pub fn is_focused(&self, window: &mut Window) -> bool { + + pub fn is_focused(&self, window: &Window) -> bool { self.focus_handle.is_focused(window) } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 784c1d51785458e68551137ae77fd71a8a2b7a2e..ecac1da5b430ca7944b45b33b512cfab367cae58 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -34,12 +34,12 @@ use gpui::{ anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, - Edges, Element, ElementInputHandler, Entity, FocusHandle, Focusable as _, FontId, - GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, KeyBindingContextPredicate, - Keystroke, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, - MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, - SharedString, Size, StatefulInteractiveElement, Style, Styled, Subscription, TextRun, - TextStyleRefinement, WeakEntity, Window, + Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox, + Hsla, InteractiveElement, IntoElement, KeyBindingContextPredicate, Keystroke, Length, + ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, + ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, + StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement, + WeakEntity, Window, }; use itertools::Itertools; use language::{ @@ -3088,10 +3088,9 @@ impl EditorElement { { let editor = self.editor.read(cx); - if editor.inline_completion_visible_in_cursor_popover( - editor.has_active_inline_completion(), - cx, - ) { + if editor + .edit_prediction_visible_in_cursor_popover(editor.has_active_inline_completion()) + { height_above_menu += editor.edit_prediction_cursor_popover_height() + POPOVER_Y_PADDING; edit_prediction_popover_visible = true; @@ -3168,10 +3167,8 @@ impl EditorElement { ); let edit_prediction = if edit_prediction_popover_visible { - let accept_binding = - AcceptEditPredictionBinding::resolve(self.editor.focus_handle(cx), window); - self.editor.update(cx, move |editor, cx| { + let accept_binding = editor.accept_edit_prediction_keybind(window, cx); let mut element = editor.render_edit_prediction_cursor_popover( min_width, max_width, @@ -3557,7 +3554,7 @@ impl EditorElement { let editor = self.editor.read(cx); let active_inline_completion = editor.active_inline_completion.as_ref()?; - if editor.inline_completion_visible_in_cursor_popover(true, cx) { + if editor.edit_prediction_visible_in_cursor_popover(true) { return None; } @@ -3570,7 +3567,7 @@ impl EditorElement { "Jump to Edit", Some(IconName::ArrowUp), previewing, - self.editor.focus_handle(cx), + editor, window, cx, )?; @@ -3583,7 +3580,7 @@ impl EditorElement { "Jump to Edit", Some(IconName::ArrowDown), previewing, - self.editor.focus_handle(cx), + editor, window, cx, )?; @@ -3599,7 +3596,7 @@ impl EditorElement { "Jump to Edit", None, previewing, - self.editor.focus_handle(cx), + editor, window, cx, )?; @@ -3658,26 +3655,23 @@ impl EditorElement { target_display_point.row(), editor_snapshot.line_len(target_display_point.row()), ); - let (previewing_inline_completion, origin) = - self.editor.update(cx, |editor, _cx| { - Some(( + let (mut element, origin) = self.editor.update(cx, |editor, cx| { + Some(( + inline_completion_accept_indicator( + "Accept", + None, editor.previewing_inline_completion, - editor.display_to_pixel_point( - target_line_end, - editor_snapshot, - window, - )?, - )) - })?; - - let mut element = inline_completion_accept_indicator( - "Accept", - None, - previewing_inline_completion, - self.editor.focus_handle(cx), - window, - cx, - )?; + editor, + window, + cx, + )?, + editor.display_to_pixel_point( + target_line_end, + editor_snapshot, + window, + )?, + )) + })?; element.prepaint_as_root( text_bounds.origin + origin + point(PADDING_X, px(0.)), @@ -5676,11 +5670,11 @@ fn inline_completion_accept_indicator( label: impl Into, icon: Option, previewing: bool, - editor_focus_handle: FocusHandle, - window: &Window, + editor: &Editor, + window: &mut Window, cx: &App, ) -> Option { - let accept_binding = AcceptEditPredictionBinding::resolve(editor_focus_handle, window); + let accept_binding = editor.accept_edit_prediction_keybind(window, cx); let accept_keystroke = accept_binding.keystroke()?; let accept_key = h_flex() @@ -5729,18 +5723,9 @@ fn inline_completion_accept_indicator( ) } -pub struct AcceptEditPredictionBinding(Option); +pub struct AcceptEditPredictionBinding(pub(crate) Option); impl AcceptEditPredictionBinding { - pub fn resolve(editor_focus_handle: FocusHandle, window: &Window) -> Self { - AcceptEditPredictionBinding( - window - .bindings_for_action_in(&AcceptEditPrediction, &editor_focus_handle) - .into_iter() - .next(), - ) - } - pub fn keystroke(&self) -> Option<&Keystroke> { if let Some(binding) = self.0.as_ref() { match &binding.keystrokes() { diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 34f8e2fa49967f6e39fd58cd7f8206506b59fa0a..f2b6b464471298b70204e6aca615729fd0999cef 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -191,7 +191,7 @@ impl FeedbackModal { ); editor.set_show_gutter(false, cx); editor.set_show_indent_guides(false, cx); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.set_vertical_scroll_margin(5, cx); editor.set_use_modal_editing(false); editor.set_soft_wrap(); diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index d8a676313c8878bcd79df25f57ae935d94397a39..0de9859dcad53c1e6dda0d66381d151e1b94d9b9 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -176,23 +176,21 @@ pub struct GitPanel { } fn commit_message_editor( - commit_message_buffer: Option>, + commit_message_buffer: Entity, + project: Entity, window: &mut Window, cx: &mut Context<'_, Editor>, ) -> Editor { - let mut commit_editor = if let Some(commit_message_buffer) = commit_message_buffer { - let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx)); - Editor::new( - EditorMode::AutoHeight { max_lines: 6 }, - buffer, - None, - false, - window, - cx, - ) - } else { - Editor::auto_height(6, window, cx) - }; + let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx)); + let mut commit_editor = Editor::new( + EditorMode::AutoHeight { max_lines: 6 }, + buffer, + None, + false, + window, + cx, + ); + commit_editor.set_collaboration_hub(Box::new(project)); commit_editor.set_use_autoclose(false); commit_editor.set_show_gutter(false, cx); commit_editor.set_show_wrap_guides(false, cx); @@ -205,7 +203,6 @@ impl GitPanel { pub fn new( workspace: &mut Workspace, window: &mut Window, - commit_message_buffer: Option>, cx: &mut Context, ) -> Entity { let fs = workspace.app_state().fs.clone(); @@ -222,8 +219,11 @@ impl GitPanel { }) .detach(); + // just to let us render a placeholder editor. + // Once the active git repo is set, this buffer will be replaced. + let temporary_buffer = cx.new(|cx| Buffer::local("", cx)); let commit_editor = - cx.new(|cx| commit_message_editor(commit_message_buffer, window, cx)); + cx.new(|cx| commit_message_editor(temporary_buffer, project.clone(), window, cx)); commit_editor.update(cx, |editor, cx| { editor.clear(window, cx); }); @@ -773,21 +773,35 @@ impl GitPanel { }) .collect::>(); - let new_co_authors = room - .read(cx) - .remote_participants() - .values() - .filter(|participant| participant.can_write()) - .map(|participant| participant.user.as_ref()) - .filter_map(|user| { - let email = user.email.as_deref()?; - let name = user.name.as_deref().unwrap_or(&user.github_login); - Some(format!("{CO_AUTHOR_PREFIX}{name} <{email}>")) - }) - .filter(|co_author| { - !existing_co_authors.contains(co_author.to_ascii_lowercase().as_str()) - }) - .collect::>(); + let project = self.project.read(cx); + let room = room.read(cx); + let mut new_co_authors = Vec::new(); + + for (peer_id, collaborator) in project.collaborators() { + if collaborator.is_host { + continue; + } + + let Some(participant) = room.remote_participant_for_peer_id(*peer_id) else { + continue; + }; + if participant.can_write() && participant.user.email.is_some() { + let email = participant.user.email.clone().unwrap(); + + if !existing_co_authors.contains(&email.as_ref()) { + new_co_authors.push((participant.user.github_login.clone(), email)) + } + } + } + if !project.is_local() && !project.is_read_only(cx) { + if let Some(user) = room.local_participant_user(cx) { + if let Some(email) = user.email.clone() { + if !existing_co_authors.contains(&email.as_ref()) { + new_co_authors.push((user.github_login.clone(), email.clone())) + } + } + } + } if new_co_authors.is_empty() { return; } @@ -798,9 +812,13 @@ impl GitPanel { if !ends_with_co_authors { edit.push('\n'); } - for co_author in new_co_authors { + for (name, email) in new_co_authors { edit.push('\n'); - edit.push_str(&co_author); + edit.push_str(CO_AUTHOR_PREFIX); + edit.push_str(&name); + edit.push_str(" <"); + edit.push_str(&email); + edit.push('>'); } editor.edit(Some((editor_end..editor_end, edit)), cx); @@ -857,8 +875,9 @@ impl GitPanel { .as_ref() != Some(&buffer) { - git_panel.commit_editor = - cx.new(|cx| commit_message_editor(Some(buffer), window, cx)); + git_panel.commit_editor = cx.new(|cx| { + commit_message_editor(buffer, git_panel.project.clone(), window, cx) + }); } }) }) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7639f5e676c25a33aac90083f99f97dfae583625..dbbe42f9846a4ad503aff7610578c91df5c504b7 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3671,6 +3671,18 @@ impl Window { dispatch_tree.bindings_for_action(action, &context_stack) } + /// Returns the key bindings for the given action in the given context. + pub fn bindings_for_action_in_context( + &self, + action: &dyn Action, + context: KeyContext, + ) -> Vec { + let dispatch_tree = &self.rendered_frame.dispatch_tree; + dispatch_tree.bindings_for_action(action, &[context]) + } + + /// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle. + /// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle. pub fn listener_for( &self, diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index 1b9e7309692bbb6fc8a5a89872aa9fa3e0cabc48..244fa8324bbc8b1d2f1a5a0e407c6d3a12fdf6c4 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -611,7 +611,7 @@ impl InlineCompletionButton { .unwrap_or(true), ) }; - self.editor_show_predictions = editor.should_show_inline_completions(cx); + self.editor_show_predictions = editor.edit_predictions_enabled(); self.edit_prediction_provider = editor.edit_prediction_provider(); self.language = language.cloned(); self.file = file; diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 9d4a236f5fb12ec68a1c5806da30ef530e39755b..5241eb210d5e56f472a494641603e932b64839d7 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -708,7 +708,7 @@ impl LspLogView { editor.set_text(log_contents, window, cx); editor.move_to_end(&MoveToEnd, window, cx); editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor }); @@ -751,7 +751,7 @@ impl LspLogView { ); editor.set_text(server_info, window, cx); editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor }); diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index df0c833ab019cd750e0d175e3af5f23600b5c1b6..7fbb781f0f8a12791c8e324c3f1667bee12368d7 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -76,7 +76,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use text::{Anchor, BufferId, LineEnding, OffsetRangeExt}; +use text::{Anchor, BufferId, LineEnding, OffsetRangeExt, TransactionId}; use util::{ debug_panic, defer, maybe, merge_json_value_into, paths::SanitizedPath, post_inc, ResultExt, TryFutureExt as _, @@ -1098,7 +1098,6 @@ impl LocalLspStore { async fn format_locally( lsp_store: WeakEntity, mut buffers: Vec, - target: &LspFormatTarget, push_to_history: bool, trigger: FormatTrigger, mut cx: AsyncApp, @@ -1131,25 +1130,16 @@ impl LocalLspStore { let mut project_transaction = ProjectTransaction::default(); for buffer in &buffers { - let (primary_adapter_and_server, adapters_and_servers) = - lsp_store.update(&mut cx, |lsp_store, cx| { - let buffer = buffer.handle.read(cx); - - let adapters_and_servers = lsp_store - .as_local() - .unwrap() - .language_servers_for_buffer(buffer, cx) - .map(|(adapter, lsp)| (adapter.clone(), lsp.clone())) - .collect::>(); - - let primary_adapter = lsp_store - .as_local() - .unwrap() - .primary_language_server_for_buffer(buffer, cx) - .map(|(adapter, lsp)| (adapter.clone(), lsp.clone())); + let adapters_and_servers = lsp_store.update(&mut cx, |lsp_store, cx| { + let buffer = buffer.handle.read(cx); - (primary_adapter, adapters_and_servers) - })?; + lsp_store + .as_local() + .unwrap() + .language_servers_for_buffer(buffer, cx) + .map(|(adapter, lsp)| (adapter.clone(), lsp.clone())) + .collect::>() + })?; let settings = buffer.handle.update(&mut cx, |buffer, cx| { language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx) @@ -1182,6 +1172,8 @@ impl LocalLspStore { buffer.end_transaction(cx) })?; + let initial_transaction_id = whitespace_transaction_id; + // Apply the `code_actions_on_format` before we run the formatter. let code_actions = deserialize_code_actions(&settings.code_actions_on_format); #[allow(clippy::nonminimal_bool)] @@ -1200,278 +1192,99 @@ impl LocalLspStore { .await?; } - // Apply language-specific formatting using either the primary language server - // or external command. - // Except for code actions, which are applied with all connected language servers. - let primary_language_server = - primary_adapter_and_server.map(|(_adapter, server)| server.clone()); - let primary_server_and_path = primary_language_server - .as_ref() - .zip(buffer.abs_path.as_ref()); - let prettier_settings = buffer.handle.read_with(&cx, |buffer, cx| { language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx) .prettier .clone() })?; - let ranges = match target { - LspFormatTarget::Buffers => None, - LspFormatTarget::Ranges(ranges) => { - let Some(ranges) = ranges.get(&buffer.id) else { - return Err(anyhow!("No format ranges provided for buffer")); - }; - Some(ranges) - } - }; - - let mut format_operations: Vec = vec![]; - { - match trigger { - FormatTrigger::Save => { - match &settings.format_on_save { - FormatOnSave::Off => { - // nothing - } - FormatOnSave::On => { - match &settings.formatter { - SelectedFormatter::Auto => { - // do the auto-format: prefer prettier, fallback to primary language server - let diff = { - if prettier_settings.allowed { - Self::perform_format( - &Formatter::Prettier, - buffer, - ranges, - primary_server_and_path, - lsp_store.clone(), - &settings, - &adapters_and_servers, - push_to_history, - &mut project_transaction, - &mut cx, - ) - .await - } else { - Self::perform_format( - &Formatter::LanguageServer { name: None }, - buffer, - ranges, - primary_server_and_path, - lsp_store.clone(), - &settings, - &adapters_and_servers, - push_to_history, - &mut project_transaction, - &mut cx, - ) - .await - } - }?; - - if let Some(op) = diff { - format_operations.push(op); - } - } - SelectedFormatter::List(formatters) => { - for formatter in formatters.as_ref() { - let diff = Self::perform_format( - formatter, - buffer, - ranges, - primary_server_and_path, - lsp_store.clone(), - &settings, - &adapters_and_servers, - push_to_history, - &mut project_transaction, - &mut cx, - ) - .await?; - if let Some(op) = diff { - format_operations.push(op); - } - - // format with formatter - } - } - } - } - FormatOnSave::List(formatters) => { - for formatter in formatters.as_ref() { - let diff = Self::perform_format( - formatter, - buffer, - ranges, - primary_server_and_path, - lsp_store.clone(), - &settings, - &adapters_and_servers, - push_to_history, - &mut project_transaction, - &mut cx, - ) - .await?; - if let Some(op) = diff { - format_operations.push(op); - } - } - } - } - } - FormatTrigger::Manual => { - match &settings.formatter { - SelectedFormatter::Auto => { - // do the auto-format: prefer prettier, fallback to primary language server - let diff = { - if prettier_settings.allowed { - Self::perform_format( - &Formatter::Prettier, - buffer, - ranges, - primary_server_and_path, - lsp_store.clone(), - &settings, - &adapters_and_servers, - push_to_history, - &mut project_transaction, - &mut cx, - ) - .await - } else { - let formatter = Formatter::LanguageServer { - name: primary_language_server - .as_ref() - .map(|server| server.name().to_string()), - }; - Self::perform_format( - &formatter, - buffer, - ranges, - primary_server_and_path, - lsp_store.clone(), - &settings, - &adapters_and_servers, - push_to_history, - &mut project_transaction, - &mut cx, - ) - .await - } - }?; - - if let Some(op) = diff { - format_operations.push(op) - } - } - SelectedFormatter::List(formatters) => { - for formatter in formatters.as_ref() { - // format with formatter - let diff = Self::perform_format( - formatter, - buffer, - ranges, - primary_server_and_path, - lsp_store.clone(), - &settings, - &adapters_and_servers, - push_to_history, - &mut project_transaction, - &mut cx, - ) - .await?; - if let Some(op) = diff { - format_operations.push(op); - } - } + let formatters = match (trigger, &settings.format_on_save) { + (FormatTrigger::Save, FormatOnSave::Off) => &[], + (FormatTrigger::Save, FormatOnSave::List(formatters)) => formatters.as_ref(), + (FormatTrigger::Manual, _) | (FormatTrigger::Save, FormatOnSave::On) => { + match &settings.formatter { + SelectedFormatter::Auto => { + if prettier_settings.allowed { + std::slice::from_ref(&Formatter::Prettier) + } else { + std::slice::from_ref(&Formatter::LanguageServer { name: None }) } } + SelectedFormatter::List(formatter_list) => formatter_list.as_ref(), } } - } - - buffer.handle.update(&mut cx, |b, cx| { - // If the buffer had its whitespace formatted and was edited while the language-specific - // formatting was being computed, avoid applying the language-specific formatting, because - // it can't be grouped with the whitespace formatting in the undo history. - if let Some(transaction_id) = whitespace_transaction_id { - if b.peek_undo_stack() - .map_or(true, |e| e.transaction_id() != transaction_id) - { - format_operations.clear(); - } - } - - // Apply any language-specific formatting, and group the two formatting operations - // in the buffer's undo history. - for operation in format_operations { - match operation { - FormatOperation::Lsp(edits) => { - b.edit(edits, None, cx); - } - FormatOperation::External(diff) => { - b.apply_diff(diff, cx); - } - FormatOperation::Prettier(diff) => { - b.apply_diff(diff, cx); - } - } - - if let Some(transaction_id) = whitespace_transaction_id { - b.group_until_transaction(transaction_id); - } else if let Some(transaction) = project_transaction.0.get(&buffer.handle) { - b.group_until_transaction(transaction.id) - } - } - - if let Some(transaction) = b.finalize_last_transaction().cloned() { - if !push_to_history { - b.forget_transaction(transaction.id); - } - project_transaction - .0 - .insert(buffer.handle.clone(), transaction); - } - })?; + }; + Self::execute_formatters( + lsp_store.clone(), + formatters, + buffer, + &settings, + &adapters_and_servers, + push_to_history, + initial_transaction_id, + &mut project_transaction, + &mut cx, + ) + .await?; } Ok(project_transaction) } #[allow(clippy::too_many_arguments)] - async fn perform_format( - formatter: &Formatter, - buffer: &FormattableBuffer, - ranges: Option<&Vec>>, - primary_server_and_path: Option<(&Arc, &PathBuf)>, + async fn execute_formatters( lsp_store: WeakEntity, + formatters: &[Formatter], + buffer: &FormattableBuffer, settings: &LanguageSettings, adapters_and_servers: &[(Arc, Arc)], push_to_history: bool, - transaction: &mut ProjectTransaction, + mut initial_transaction_id: Option, + project_transaction: &mut ProjectTransaction, cx: &mut AsyncApp, - ) -> Result, anyhow::Error> { - let result = match formatter { - Formatter::LanguageServer { name } => { - if let Some((language_server, buffer_abs_path)) = primary_server_and_path { + ) -> anyhow::Result<()> { + let mut prev_transaction_id = initial_transaction_id; + + for formatter in formatters { + let operation = match formatter { + Formatter::LanguageServer { name } => { + let Some(language_server) = lsp_store.update(cx, |lsp_store, cx| { + lsp_store + .as_local() + .unwrap() + .primary_language_server_for_buffer(buffer.handle.read(cx), cx) + .map(|(_, lsp)| lsp.clone()) + })? + else { + continue; + }; + let Some(buffer_abs_path) = buffer.abs_path.as_ref() else { + continue; + }; + let language_server = if let Some(name) = name { adapters_and_servers .iter() .find_map(|(adapter, server)| { - adapter.name.0.as_ref().eq(name.as_str()).then_some(server) + adapter + .name + .0 + .as_ref() + .eq(name.as_str()) + .then_some(server.clone()) }) .unwrap_or(language_server) } else { language_server }; - let result = if let Some(ranges) = ranges { + let result = if let Some(ranges) = &buffer.ranges { Self::format_ranges_via_lsp( &lsp_store, - &buffer, + &buffer.handle, ranges, buffer_abs_path, - language_server, + &language_server, settings, cx, ) @@ -1482,7 +1295,7 @@ impl LocalLspStore { &lsp_store, &buffer.handle, buffer_abs_path, - language_server, + &language_server, settings, cx, ) @@ -1491,51 +1304,114 @@ impl LocalLspStore { }; Some(FormatOperation::Lsp(result)) - } else { + } + Formatter::Prettier => { + let prettier = lsp_store.update(cx, |lsp_store, _cx| { + lsp_store.prettier_store().unwrap().downgrade() + })?; + prettier_store::format_with_prettier(&prettier, &buffer.handle, cx) + .await + .transpose()? + } + Formatter::External { command, arguments } => { + Self::format_via_external_command(buffer, command, arguments.as_deref(), cx) + .await + .context(format!( + "failed to format via external command {:?}", + command + ))? + .map(FormatOperation::External) + } + Formatter::CodeActions(code_actions) => { + let code_actions = deserialize_code_actions(code_actions); + if !code_actions.is_empty() { + Self::execute_code_actions_on_servers( + &lsp_store, + adapters_and_servers, + code_actions, + &buffer.handle, + push_to_history, + project_transaction, + cx, + ) + .await?; + let buf_transaction_id = + project_transaction.0.get(&buffer.handle).map(|t| t.id); + // NOTE: same logic as in buffer.handle.update below + if initial_transaction_id.is_none() { + initial_transaction_id = buf_transaction_id; + } + if buf_transaction_id.is_some() { + prev_transaction_id = buf_transaction_id; + } + } None } + }; + let Some(operation) = operation else { + continue; + }; + + let should_continue_formatting = buffer.handle.update(cx, |b, cx| { + // If a previous format succeeded and the buffer was edited while the language-specific + // formatting information for this format was being computed, avoid applying the + // language-specific formatting, because it can't be grouped with the previous formatting + // in the undo history. + let should_continue_formatting = match (prev_transaction_id, b.peek_undo_stack()) { + (Some(prev_transaction_id), Some(last_history_entry)) => { + let last_history_transaction_id = last_history_entry.transaction_id(); + let is_same_as_prev = last_history_transaction_id == prev_transaction_id; + is_same_as_prev + } + (Some(_), None) => false, + (_, _) => true, + }; + + if should_continue_formatting { + // Apply any language-specific formatting, and group the two formatting operations + // in the buffer's undo history. + let this_transaction_id = match operation { + FormatOperation::Lsp(edits) => b.edit(edits, None, cx), + FormatOperation::External(diff) => b.apply_diff(diff, cx), + FormatOperation::Prettier(diff) => b.apply_diff(diff, cx), + }; + if initial_transaction_id.is_none() { + initial_transaction_id = this_transaction_id; + } + if this_transaction_id.is_some() { + prev_transaction_id = this_transaction_id; + } + } + + if let Some(transaction_id) = initial_transaction_id { + b.group_until_transaction(transaction_id); + } else if let Some(transaction) = project_transaction.0.get(&buffer.handle) { + b.group_until_transaction(transaction.id) + } + return should_continue_formatting; + })?; + if !should_continue_formatting { + break; } - Formatter::Prettier => { - let prettier = lsp_store.update(cx, |lsp_store, _cx| { - lsp_store.prettier_store().unwrap().downgrade() - })?; - prettier_store::format_with_prettier(&prettier, &buffer.handle, cx) - .await - .transpose()? - } - Formatter::External { command, arguments } => { - Self::format_via_external_command(buffer, command, arguments.as_deref(), cx) - .await - .context(format!( - "failed to format via external command {:?}", - command - ))? - .map(FormatOperation::External) - } - Formatter::CodeActions(code_actions) => { - let code_actions = deserialize_code_actions(code_actions); - if !code_actions.is_empty() { - Self::execute_code_actions_on_servers( - &lsp_store, - adapters_and_servers, - code_actions, - &buffer.handle, - push_to_history, - transaction, - cx, - ) - .await?; + } + + buffer.handle.update(cx, |b, _cx| { + if let Some(transaction) = b.finalize_last_transaction().cloned() { + if !push_to_history { + b.forget_transaction(transaction.id); + project_transaction + .0 + .insert(buffer.handle.clone(), transaction); } - None } - }; - anyhow::Ok(result) + })?; + return Ok(()); } pub async fn format_ranges_via_lsp( this: &WeakEntity, - buffer: &FormattableBuffer, - ranges: &Vec>, + buffer_handle: &Entity, + ranges: &[Range], abs_path: &Path, language_server: &Arc, settings: &LanguageSettings, @@ -1563,7 +1439,7 @@ impl LocalLspStore { // // TODO: Instead of using current snapshot, should use the latest snapshot sent to // LSP. - let snapshot = buffer.handle.read(cx).snapshot(); + let snapshot = buffer_handle.read(cx).snapshot(); for range in ranges { lsp_ranges.push(range_to_lsp(range.to_point_utf16(&snapshot))?); } @@ -1590,7 +1466,7 @@ impl LocalLspStore { if let Some(lsp_edits) = lsp_edits { this.update(cx, |this, cx| { this.as_local_mut().unwrap().edits_from_lsp( - &buffer.handle, + &buffer_handle, lsp_edits, language_server.server_id(), None, @@ -2779,10 +2655,10 @@ impl LocalLspStore { #[derive(Debug)] pub struct FormattableBuffer { - id: BufferId, handle: Entity, abs_path: Option, env: Option>, + ranges: Option>>, } pub struct RemoteLspStore { @@ -7041,18 +6917,27 @@ impl LspStore { })? .await; + let ranges = match &target { + LspFormatTarget::Buffers => None, + LspFormatTarget::Ranges(ranges) => { + let Some(ranges) = ranges.get(&id) else { + return Err(anyhow!("No format ranges provided for buffer")); + }; + Some(ranges.clone()) + } + }; + formattable_buffers.push(FormattableBuffer { - id, handle, abs_path, env, + ranges, }); } let result = LocalLspStore::format_locally( lsp_store.clone(), formattable_buffers, - &target, push_to_history, trigger, cx.clone(), diff --git a/crates/prompt_library/src/prompt_library.rs b/crates/prompt_library/src/prompt_library.rs index be5e31d6b55f53a26b698e0f2856e67e770a77d5..a37957bfd6bdadf8dbe53ae0a4a35821a9c4002e 100644 --- a/crates/prompt_library/src/prompt_library.rs +++ b/crates/prompt_library/src/prompt_library.rs @@ -563,7 +563,7 @@ impl PromptLibrary { editor.set_text(prompt_metadata.title.unwrap_or_default(), window, cx); if prompt_id.is_built_in() { editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); } editor }); @@ -578,7 +578,7 @@ impl PromptLibrary { let mut editor = Editor::for_buffer(buffer, None, window, cx); if prompt_id.is_built_in() { editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); } editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor.set_show_gutter(false, cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 7fe30db8fec7250d6f6c09c4c683d081c559f0de..9b66e92b9f5ad2d76637504e3baf1da0966cea69 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -406,7 +406,7 @@ fn initialize_panels( workspace.add_panel(chat_panel, window, cx); workspace.add_panel(notification_panel, window, cx); cx.when_flag_enabled::(window, |workspace, window, cx| { - let git_panel = git_ui::git_panel::GitPanel::new(workspace, window, None, cx); + let git_panel = git_ui::git_panel::GitPanel::new(workspace, window, cx); workspace.add_panel(git_panel, window, cx); }); })?; diff --git a/crates/zed/src/zed/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs index 5f2b98d444e3a8f6da2035e3db03498399bbf2b7..3e1b57125c17a97f5771d2ba291fd4f6cdc266dd 100644 --- a/crates/zed/src/zed/quick_action_bar.rs +++ b/crates/zed/src/zed/quick_action_bar.rs @@ -104,7 +104,7 @@ impl Render for QuickActionBar { let git_blame_inline_enabled = editor.git_blame_inline_enabled(); let show_git_blame_gutter = editor.show_git_blame_gutter(); let auto_signature_help_enabled = editor.auto_signature_help_enabled(cx); - let show_inline_completions = editor.should_show_inline_completions(cx); + let show_edit_predictions = editor.edit_predictions_enabled(); let inline_completion_enabled = editor.inline_completions_enabled(cx); ( @@ -114,7 +114,7 @@ impl Render for QuickActionBar { git_blame_inline_enabled, show_git_blame_gutter, auto_signature_help_enabled, - show_inline_completions, + show_edit_predictions, inline_completion_enabled, ) }; diff --git a/crates/zeta/src/rate_completion_modal.rs b/crates/zeta/src/rate_completion_modal.rs index dda838c21b3b927af387a93dbf731e76dead44c6..ed79f41fe8f3b1943770107a714dd6cd235a6563 100644 --- a/crates/zeta/src/rate_completion_modal.rs +++ b/crates/zeta/src/rate_completion_modal.rs @@ -279,7 +279,7 @@ impl RateCompletionModal { editor.set_show_runnables(false, cx); editor.set_show_wrap_guides(false, cx); editor.set_show_indent_guides(false, cx); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.set_placeholder_text("Add your feedback…", cx); if focus { cx.focus_self(window); diff --git a/extensions/EXTRACTION.md b/extensions/EXTRACTION.md index e5ff27bb680d91002d77c71957b83aa31ddf767b..09fed7970d87f2eaf2aad178614d6578a9deaeba 100644 --- a/extensions/EXTRACTION.md +++ b/extensions/EXTRACTION.md @@ -12,41 +12,48 @@ brew install git-filter-repo ## Process -1. Create an expressions.txt file somewhere (e.g. `~/projects/expressions.txt`) - -``` -ruby: ==> -extension: ==> -chore: ==> -zed_extension_api: ==> -regex:(?zed-industries/zed\1 -``` - -This file takes the form of `patern==>replacement`, where the replacement is optional. -Note whitespace matters so `ruby: ==>` is removing the `ruby:` prefix from a commit messages and adding a space after `==> ` means the replacement begins with a space. Regex capture groups are numbered `\1`, `\2`, etc. - -See: [Git Filter Repo Docs](https://htmlpreview.github.io/?https://github.com/newren/git-filter-repo/blob/docs/html/git-filter-repo.html) for more. - -2. Create a clean clone the zed repository, delete tags, delete branches and do the work. +We are going to use a `$LANGNAME` variable for all these steps. Make sure it is set correctly. > **Note** > If you get `zsh: command not found: #` errors, run: > `setopt interactive_comments && echo "setopt interactive_comments" >> ~/.zshrc` +1. Create a clean clone the zed repository, delete tags and delete branches. + ```sh -LANGNAME=ruby +LANGNAME=your_language_name_here + rm -rf $LANGNAME git clone --single-branch --no-tags git@github.com:zed-industries/zed.git $LANGNAME cd $LANGNAME +``` + +2. Create an expressions.txt file somewhere (e.g. `~/projects/$LANGNAME.txt`) + +This file takes the form of `patern==>replacement`, where the replacement is optional. +Note whitespace matters so `ruby: ==>` is removing the `ruby:` prefix from a commit messages and adding a space after `==> ` means the replacement begins with a space. Regex capture groups are numbered `\1`, `\2`, etc. + +See: [Git Filter Repo Docs](https://htmlpreview.github.io/?https://github.com/newren/git-filter-repo/blob/docs/html/git-filter-repo.html) for more. + +```sh +# Create regex mapping for rewriting commit messages (edit as appropriate) +mkdir -p ~/projects +echo "${LANGNAME}: ==> +extension: ==> +chore: ==> +zed_extension_api: ==> +"'regex:(?zed-industries/zed\1' \ + > ~/projects/${LANGNAME}.txt # This removes the LICENSE symlink git filter-repo --invert-paths --path extensions/$LANGNAME/LICENSE-APACHE +# This does the work git filter-repo \ --use-mailmap \ --subdirectory-filter extensions/$LANGNAME/ \ --path LICENSE-APACHE \ - --replace-message ~/projects/expressions.txt + --replace-message ~/projects/${LANGNAME}.txt ``` 3. Review the commits. @@ -100,7 +107,7 @@ git branch --set-upstream-to=origin/main main 7. Publish a new version of the extension. -``` +```sh OLD_VERSION=$(grep '^version = ' extension.toml | cut -d'"' -f2) NEW_VERSION=$(echo "$OLD_VERSION" | awk -F. '{$NF = $NF + 1;} 1' OFS=.) echo $OLD_VERSION $NEW_VERSION @@ -124,7 +131,19 @@ git tag v${NEW_VERSION} git push origin v${NEW_VERSION} ``` -7. In zed repository, `rm -rf extension/langname` and push a PR. +7. In zed repository, remove the old extension and push a PR. + +```sh +rm -rf extensions/$LANGNAME +sed -i '' "/extensions\/$LANGNAME/d" Cargo.toml +cargo check +git checkoout -b remove_$LANGNAME +git add extensions/$LANGNAME +git add Cargo.toml Cargo.lock extensions/$LANGNAME +git commit -m "Migrate to $LANGNAME extension to zed-extensions/$LANGNAME" +git push +gh pr create --web +``` 8. Update extensions repository: @@ -151,4 +170,4 @@ git commit -m "Bump ${LANGNAME} to v${NEW_VERSION}" git push ``` -Create PR and reference the Zed PR with removal from tree. \ No newline at end of file +Create PR and reference the Zed PR with removal from tree. diff --git a/extensions/php/Cargo.toml b/extensions/php/Cargo.toml deleted file mode 100644 index b3f0ad663dd9d8f28ebc261b8523cd0d9211f769..0000000000000000000000000000000000000000 --- a/extensions/php/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "zed_php" -version = "0.2.4" -edition.workspace = true -publish.workspace = true -license = "Apache-2.0" - -[lints] -workspace = true - -[lib] -path = "src/php.rs" -crate-type = ["cdylib"] - -[dependencies] -zed_extension_api = "0.1.0" diff --git a/extensions/php/LICENSE-APACHE b/extensions/php/LICENSE-APACHE deleted file mode 120000 index 1cd601d0a3affae83854be02a0afdec3b7a9ec4d..0000000000000000000000000000000000000000 --- a/extensions/php/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE-APACHE \ No newline at end of file diff --git a/extensions/php/extension.toml b/extensions/php/extension.toml deleted file mode 100644 index 0f89e3f885c1a0544073151695c9bf5f26c0ab8e..0000000000000000000000000000000000000000 --- a/extensions/php/extension.toml +++ /dev/null @@ -1,25 +0,0 @@ -id = "php" -name = "PHP" -description = "PHP support." -version = "0.2.4" -schema_version = 1 -authors = ["Piotr Osiewicz "] -repository = "https://github.com/zed-industries/zed" - -[language_servers.intelephense] -name = "Intelephense" -language = "PHP" -language_ids = { PHP = "php"} - -[language_servers.phpactor] -name = "Phpactor" -language = "PHP" - -[grammars.php] -repository = "https://github.com/tree-sitter/tree-sitter-php" -commit = "8ab93274065cbaf529ea15c24360cfa3348ec9e4" -path = "php" - -[grammars.phpdoc] -repository = "https://github.com/claytonrcarter/tree-sitter-phpdoc" -commit = "1d0e255b37477d0ca46f1c9e9268c8fa76c0b3fc" diff --git a/extensions/php/languages/php/brackets.scm b/extensions/php/languages/php/brackets.scm deleted file mode 100644 index 988602aa8d715a2a771b2298a022d661c7683465..0000000000000000000000000000000000000000 --- a/extensions/php/languages/php/brackets.scm +++ /dev/null @@ -1,4 +0,0 @@ -("{" @open "}" @close) -("(" @open ")" @close) -("[" @open "]" @close) -("\"" @open "\"" @close) diff --git a/extensions/php/languages/php/config.toml b/extensions/php/languages/php/config.toml deleted file mode 100644 index 54b6c2905fe1efbcf8d943c455f33207e5287c09..0000000000000000000000000000000000000000 --- a/extensions/php/languages/php/config.toml +++ /dev/null @@ -1,18 +0,0 @@ -name = "PHP" -grammar = "php" -path_suffixes = ["php"] -first_line_pattern = '^#!.*php' -line_comments = ["// ", "# "] -autoclose_before = ";:.,=}])>" -brackets = [ - { start = "{", end = "}", close = true, newline = true }, - { start = "[", end = "]", close = true, newline = true }, - { start = "(", end = ")", close = true, newline = true }, - { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, - { start = "'", end = "'", close = true, newline = false, not_in = ["string"] }, -] -collapsed_placeholder = "/* ... */" -word_characters = ["$"] -scope_opt_in_language_servers = ["tailwindcss-language-server"] -prettier_parser_name = "php" -prettier_plugins = ["@prettier/plugin-php"] diff --git a/extensions/php/languages/php/embedding.scm b/extensions/php/languages/php/embedding.scm deleted file mode 100644 index db277775b38fe43f5bc3dc981cf468048202dc17..0000000000000000000000000000000000000000 --- a/extensions/php/languages/php/embedding.scm +++ /dev/null @@ -1,36 +0,0 @@ -( - (comment)* @context - . - [ - (function_definition - "function" @name - name: (_) @name - body: (_ - "{" @keep - "}" @keep) @collapse - ) - - (trait_declaration - "trait" @name - name: (_) @name) - - (method_declaration - "function" @name - name: (_) @name - body: (_ - "{" @keep - "}" @keep) @collapse - ) - - (interface_declaration - "interface" @name - name: (_) @name - ) - - (enum_declaration - "enum" @name - name: (_) @name - ) - - ] @item - ) diff --git a/extensions/php/languages/php/highlights.scm b/extensions/php/languages/php/highlights.scm deleted file mode 100644 index 6afeb1090b7a0bf677a25a369fc590a850ecbd04..0000000000000000000000000000000000000000 --- a/extensions/php/languages/php/highlights.scm +++ /dev/null @@ -1,137 +0,0 @@ -(php_tag) @tag -"?>" @tag - -; Types - -(primitive_type) @type.builtin -(cast_type) @type.builtin -(named_type (name) @type) @type -(named_type (qualified_name) @type) @type - -; Functions - -(array_creation_expression "array" @function.builtin) -(list_literal "list" @function.builtin) - -(method_declaration - name: (name) @function.method) - -(function_call_expression - function: [(qualified_name (name)) (name)] @function) - -(scoped_call_expression - name: (name) @function) - -(member_call_expression - name: (name) @function.method) - -(function_definition - name: (name) @function) - -; Member - -(property_element - (variable_name) @property) - -(member_access_expression - name: (variable_name (name)) @property) -(member_access_expression - name: (name) @property) - -; Variables - -(relative_scope) @variable.builtin - -((name) @constant - (#match? @constant "^_?[A-Z][A-Z\\d_]+$")) -((name) @constant.builtin - (#match? @constant.builtin "^__[A-Z][A-Z\d_]+__$")) - -((name) @constructor - (#match? @constructor "^[A-Z]")) - -((name) @variable.builtin - (#eq? @variable.builtin "this")) - -(variable_name) @variable - -; Basic tokens -[ - (string) - (string_value) - (encapsed_string) - (heredoc) - (heredoc_body) - (nowdoc_body) -] @string -(boolean) @constant.builtin -(null) @constant.builtin -(integer) @number -(float) @number -(comment) @comment - -"$" @operator - -; Keywords - -"abstract" @keyword -"and" @keyword -"as" @keyword -"break" @keyword -"callable" @keyword -"case" @keyword -"catch" @keyword -"class" @keyword -"clone" @keyword -"const" @keyword -"continue" @keyword -"declare" @keyword -"default" @keyword -"do" @keyword -"echo" @keyword -"else" @keyword -"elseif" @keyword -"enum" @keyword -"enddeclare" @keyword -"endfor" @keyword -"endforeach" @keyword -"endif" @keyword -"endswitch" @keyword -"endwhile" @keyword -"extends" @keyword -"final" @keyword -"readonly" @keyword -"finally" @keyword -"for" @keyword -"foreach" @keyword -"fn" @keyword -"function" @keyword -"global" @keyword -"goto" @keyword -"if" @keyword -"implements" @keyword -"include_once" @keyword -"include" @keyword -"instanceof" @keyword -"insteadof" @keyword -"interface" @keyword -"match" @keyword -"namespace" @keyword -"new" @keyword -"or" @keyword -"print" @keyword -"private" @keyword -"protected" @keyword -"public" @keyword -"readonly" @keyword -"require_once" @keyword -"require" @keyword -"return" @keyword -"static" @keyword -"switch" @keyword -"throw" @keyword -"trait" @keyword -"try" @keyword -"use" @keyword -"while" @keyword -"xor" @keyword diff --git a/extensions/php/languages/php/indents.scm b/extensions/php/languages/php/indents.scm deleted file mode 100644 index e9754690920500f55e611f981e46d0365560eb4f..0000000000000000000000000000000000000000 --- a/extensions/php/languages/php/indents.scm +++ /dev/null @@ -1 +0,0 @@ -(_ "{" "}" @end) @indent diff --git a/extensions/php/languages/php/injections.scm b/extensions/php/languages/php/injections.scm deleted file mode 100644 index 657145c13f62debfedeaefccbc8cf8acf1adb319..0000000000000000000000000000000000000000 --- a/extensions/php/languages/php/injections.scm +++ /dev/null @@ -1,11 +0,0 @@ -((text) @injection.content - (#set! injection.language "html") - (#set! injection.combined)) - -((comment) @injection.content - (#match? @injection.content "^/\\*\\*[^*]") - (#set! injection.language "phpdoc")) - -((heredoc_body) (heredoc_end) @injection.language) @injection.content - -((nowdoc_body) (heredoc_end) @injection.language) @injection.content diff --git a/extensions/php/languages/php/outline.scm b/extensions/php/languages/php/outline.scm deleted file mode 100644 index 13c11676b9c94828a1202bcd34d361dd3f0a8d2b..0000000000000000000000000000000000000000 --- a/extensions/php/languages/php/outline.scm +++ /dev/null @@ -1,46 +0,0 @@ -(class_declaration - "class" @context - name: (name) @name - ) @item - -(function_definition - "function" @context - name: (_) @name - ) @item - -(method_declaration - "function" @context - name: (_) @name - ) @item - -(interface_declaration - "interface" @context - name: (_) @name - ) @item - -(enum_declaration - "enum" @context - name: (_) @name - ) @item - -(trait_declaration - "trait" @context - name: (_) @name - ) @item - -; Add support for Pest runnable -(function_call_expression - function: (_) @context - (#any-of? @context "it" "test" "describe") - arguments: (arguments - . - (argument - [ - (encapsed_string (string_value) @name) - (string (string_value) @name) - ] - ) - ) -) @item - -(comment) @annotation diff --git a/extensions/php/languages/php/runnables.scm b/extensions/php/languages/php/runnables.scm deleted file mode 100644 index 96c90d2f8ad2757285f1f5ffd2b82d838f9efa38..0000000000000000000000000000000000000000 --- a/extensions/php/languages/php/runnables.scm +++ /dev/null @@ -1,105 +0,0 @@ -; Class that follow the naming convention of PHPUnit test classes -; and that doesn't have the abstract modifier -; and have a method that follow the naming convention of PHPUnit test methods -; and the method is public -( - (class_declaration - modifier: (_)? @_modifier - (#not-eq? @_modifier "abstract") - name: (_) @_name - (#match? @_name ".*Test$") - body: (declaration_list - (method_declaration - (visibility_modifier)? @_visibility - (#eq? @_visibility "public") - name: (_) @run - (#match? @run "^test.*") - ) - ) - ) @_phpunit-test - (#set! tag phpunit-test) -) - -; Class that follow the naming convention of PHPUnit test classes -; and that doesn't have the abstract modifier -; and have a method that has the @test annotation -; and the method is public -( - (class_declaration - modifier: (_)? @_modifier - (#not-eq? @_modifier "abstract") - name: (_) @_name - (#match? @_name ".*Test$") - body: (declaration_list - ((comment) @_comment - (#match? @_comment ".*@test\\b.*") - . - (method_declaration - (visibility_modifier)? @_visibility - (#eq? @_visibility "public") - name: (_) @run - (#not-match? @run "^test.*") - )) - ) - ) @_phpunit-test - (#set! tag phpunit-test) -) - -; Class that follow the naming convention of PHPUnit test classes -; and that doesn't have the abstract modifier -; and have a method that has the #[Test] attribute -; and the method is public -( - (class_declaration - modifier: (_)? @_modifier - (#not-eq? @_modifier "abstract") - name: (_) @_name - (#match? @_name ".*Test$") - body: (declaration_list - (method_declaration - (attribute_list - (attribute_group - (attribute (name) @_attribute) - ) - ) - (#eq? @_attribute "Test") - (visibility_modifier)? @_visibility - (#eq? @_visibility "public") - name: (_) @run - (#not-match? @run "^test.*") - ) - ) - ) @_phpunit-test - (#set! tag phpunit-test) -) - -; Class that follow the naming convention of PHPUnit test classes -; and that doesn't have the abstract modifier -( - (class_declaration - modifier: (_)? @_modifier - (#not-eq? @_modifier "abstract") - name: (_) @run - (#match? @run ".*Test$") - ) @_phpunit-test - (#set! tag phpunit-test) -) - -; Add support for Pest runnable -; Function expression that has `it`, `test` or `describe` as the function name -( - (function_call_expression - function: (_) @_name - (#any-of? @_name "it" "test" "describe") - arguments: (arguments - . - (argument - [ - (encapsed_string (string_value) @run) - (string (string_value) @run) - ] - ) - ) - ) @_pest-test - (#set! tag pest-test) -) diff --git a/extensions/php/languages/php/tags.scm b/extensions/php/languages/php/tags.scm deleted file mode 100644 index 66d594c254748c26623ce40456bab1304305be43..0000000000000000000000000000000000000000 --- a/extensions/php/languages/php/tags.scm +++ /dev/null @@ -1,40 +0,0 @@ -(namespace_definition - name: (namespace_name) @name) @module - -(interface_declaration - name: (name) @name) @definition.interface - -(trait_declaration - name: (name) @name) @definition.interface - -(class_declaration - name: (name) @name) @definition.class - -(class_interface_clause [(name) (qualified_name)] @name) @impl - -(property_declaration - (property_element (variable_name (name) @name))) @definition.field - -(function_definition - name: (name) @name) @definition.function - -(method_declaration - name: (name) @name) @definition.function - -(object_creation_expression - [ - (qualified_name (name) @name) - (variable_name (name) @name) - ]) @reference.class - -(function_call_expression - function: [ - (qualified_name (name) @name) - (variable_name (name)) @name - ]) @reference.call - -(scoped_call_expression - name: (name) @name) @reference.call - -(member_call_expression - name: (name) @name) @reference.call diff --git a/extensions/php/languages/php/tasks.json b/extensions/php/languages/php/tasks.json deleted file mode 100644 index 65800eebe2e5bd5df19551c7beb9b39f22db0336..0000000000000000000000000000000000000000 --- a/extensions/php/languages/php/tasks.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - { - "label": "phpunit test $ZED_SYMBOL", - "command": "./vendor/bin/phpunit", - "args": ["--filter $ZED_SYMBOL $ZED_FILE"], - "tags": ["phpunit-test"] - }, - { - "label": "pest test $ZED_SYMBOL", - "command": "./vendor/bin/pest", - "args": ["--filter \"$ZED_SYMBOL\" $ZED_FILE"], - "tags": ["pest-test"] - }, - { - "label": "execute selection $ZED_SELECTED_TEXT", - "command": "php", - "args": ["-r", "$ZED_SELECTED_TEXT"] - } -] diff --git a/extensions/php/languages/php/textobjects.scm b/extensions/php/languages/php/textobjects.scm deleted file mode 100644 index d86a0c125212d94c0a5313f637868cda7b285f36..0000000000000000000000000000000000000000 --- a/extensions/php/languages/php/textobjects.scm +++ /dev/null @@ -1,45 +0,0 @@ -(function_definition - body: (_ - "{" - (_)* @function.inside - "}" )) @function.around - -(method_declaration - body: (_ - "{" - (_)* @function.inside - "}" )) @function.around - -(method_declaration) @function.around - -(class_declaration - body: (_ - "{" - (_)* @class.inside - "}")) @class.around - -(interface_declaration - body: (_ - "{" - (_)* @class.inside - "}")) @class.around - -(trait_declaration - body: (_ - "{" - (_)* @class.inside - "}")) @class.around - -(enum_declaration - body: (_ - "{" - (_)* @class.inside - "}")) @class.around - -(namespace_definition - body: (_ - "{" - (_)* @class.inside - "}")) @class.around - -(comment)+ @comment.around diff --git a/extensions/php/languages/phpdoc/config.toml b/extensions/php/languages/phpdoc/config.toml deleted file mode 100644 index 78aa5e64aa3be4e45f7b2fbd331d36162c100d8b..0000000000000000000000000000000000000000 --- a/extensions/php/languages/phpdoc/config.toml +++ /dev/null @@ -1,9 +0,0 @@ -name = "PHPDoc" -grammar = "phpdoc" -autoclose_before = "]})>" -brackets = [ - { start = "{", end = "}", close = true, newline = false }, - { start = "[", end = "]", close = true, newline = false }, - { start = "(", end = ")", close = true, newline = false }, - { start = "<", end = ">", close = true, newline = false }, -] diff --git a/extensions/php/languages/phpdoc/highlights.scm b/extensions/php/languages/phpdoc/highlights.scm deleted file mode 100644 index 767d8e62207984d8724894ebb9ff5ec83625c0f9..0000000000000000000000000000000000000000 --- a/extensions/php/languages/phpdoc/highlights.scm +++ /dev/null @@ -1,13 +0,0 @@ -(tag_name) @keyword - -(tag (variable_name) @variable) -(variable_name "$" @operator) - -(tag - (tag_name) @keyword - (#eq? @keyword "@method") - (name) @function.method) - -(primitive_type) @type.builtin -(named_type (name) @type) @type -(named_type (qualified_name) @type) @type diff --git a/extensions/php/src/language_servers.rs b/extensions/php/src/language_servers.rs deleted file mode 100644 index f209d1c00ac2639a9933cc52e64fc23bc75ac971..0000000000000000000000000000000000000000 --- a/extensions/php/src/language_servers.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod intelephense; -mod phpactor; - -pub use intelephense::*; -pub use phpactor::*; diff --git a/extensions/php/src/language_servers/intelephense.rs b/extensions/php/src/language_servers/intelephense.rs deleted file mode 100644 index 23f47ac5c06344b641a88a0c46900fed36a12bfa..0000000000000000000000000000000000000000 --- a/extensions/php/src/language_servers/intelephense.rs +++ /dev/null @@ -1,209 +0,0 @@ -use std::{env, fs}; - -use zed::{CodeLabel, CodeLabelSpan}; -use zed_extension_api::settings::LspSettings; -use zed_extension_api::{self as zed, serde_json, LanguageServerId, Result}; - -const SERVER_PATH: &str = "node_modules/intelephense/lib/intelephense.js"; -const PACKAGE_NAME: &str = "intelephense"; - -pub struct Intelephense { - did_find_server: bool, -} - -impl Intelephense { - pub const LANGUAGE_SERVER_ID: &'static str = "intelephense"; - - pub fn new() -> Self { - Self { - did_find_server: false, - } - } - - pub fn language_server_command( - &mut self, - language_server_id: &LanguageServerId, - worktree: &zed::Worktree, - ) -> Result { - if let Some(path) = worktree.which("intelephense") { - return Ok(zed::Command { - command: path, - args: vec!["--stdio".to_string()], - env: Default::default(), - }); - } - - let server_path = self.server_script_path(language_server_id)?; - Ok(zed::Command { - command: zed::node_binary_path()?, - args: vec![ - env::current_dir() - .unwrap() - .join(&server_path) - .to_string_lossy() - .to_string(), - "--stdio".to_string(), - ], - env: Default::default(), - }) - } - - fn server_exists(&self) -> bool { - fs::metadata(SERVER_PATH).map_or(false, |stat| stat.is_file()) - } - - fn server_script_path(&mut self, language_server_id: &LanguageServerId) -> Result { - let server_exists = self.server_exists(); - if self.did_find_server && server_exists { - return Ok(SERVER_PATH.to_string()); - } - - zed::set_language_server_installation_status( - language_server_id, - &zed::LanguageServerInstallationStatus::CheckingForUpdate, - ); - let version = zed::npm_package_latest_version(PACKAGE_NAME)?; - - if !server_exists - || zed::npm_package_installed_version(PACKAGE_NAME)?.as_ref() != Some(&version) - { - zed::set_language_server_installation_status( - language_server_id, - &zed::LanguageServerInstallationStatus::Downloading, - ); - let result = zed::npm_install_package(PACKAGE_NAME, &version); - match result { - Ok(()) => { - if !self.server_exists() { - Err(format!( - "installed package '{PACKAGE_NAME}' did not contain expected path '{SERVER_PATH}'", - ))?; - } - } - Err(error) => { - if !self.server_exists() { - Err(error)?; - } - } - } - } - - self.did_find_server = true; - Ok(SERVER_PATH.to_string()) - } - - pub fn language_server_workspace_configuration( - &mut self, - worktree: &zed::Worktree, - ) -> Result> { - let settings = LspSettings::for_worktree("intelephense", worktree) - .ok() - .and_then(|lsp_settings| lsp_settings.settings.clone()) - .unwrap_or_default(); - - Ok(Some(serde_json::json!({ - "intelephense": settings - }))) - } - - pub fn label_for_completion(&self, completion: zed::lsp::Completion) -> Option { - let label = &completion.label; - - match completion.kind? { - zed::lsp::CompletionKind::Method => { - // __construct method doesn't have a detail - if let Some(ref detail) = completion.detail { - if detail.is_empty() { - return Some(CodeLabel { - spans: vec![ - CodeLabelSpan::literal(label, Some("function.method".to_string())), - CodeLabelSpan::literal("()", None), - ], - filter_range: (0..label.len()).into(), - code: completion.label, - }); - } - } - - let mut parts = completion.detail.as_ref()?.split(":"); - // E.g., `foo(string $var)` - let name_and_params = parts.next()?; - let return_type = parts.next()?.trim(); - - let (_, params) = name_and_params.split_once("(")?; - let params = params.trim_end_matches(")"); - - Some(CodeLabel { - spans: vec![ - CodeLabelSpan::literal(label, Some("function.method".to_string())), - CodeLabelSpan::literal("(", None), - CodeLabelSpan::literal(params, Some("comment".to_string())), - CodeLabelSpan::literal("): ", None), - CodeLabelSpan::literal(return_type, Some("type".to_string())), - ], - filter_range: (0..label.len()).into(), - code: completion.label, - }) - } - zed::lsp::CompletionKind::Constant | zed::lsp::CompletionKind::EnumMember => { - if let Some(ref detail) = completion.detail { - if !detail.is_empty() { - return Some(CodeLabel { - spans: vec![ - CodeLabelSpan::literal(label, Some("constant".to_string())), - CodeLabelSpan::literal(" ", None), - CodeLabelSpan::literal(detail, Some("comment".to_string())), - ], - filter_range: (0..label.len()).into(), - code: completion.label, - }); - } - } - - Some(CodeLabel { - spans: vec![CodeLabelSpan::literal(label, Some("constant".to_string()))], - filter_range: (0..label.len()).into(), - code: completion.label, - }) - } - zed::lsp::CompletionKind::Property => { - let return_type = completion.detail?; - Some(CodeLabel { - spans: vec![ - CodeLabelSpan::literal(label, Some("attribute".to_string())), - CodeLabelSpan::literal(": ", None), - CodeLabelSpan::literal(return_type, Some("type".to_string())), - ], - filter_range: (0..label.len()).into(), - code: completion.label, - }) - } - zed::lsp::CompletionKind::Variable => { - // See https://www.php.net/manual/en/reserved.variables.php - const SYSTEM_VAR_NAMES: &[&str] = - &["argc", "argv", "php_errormsg", "http_response_header"]; - - let var_name = completion.label.trim_start_matches("$"); - let is_uppercase = var_name - .chars() - .filter(|c| c.is_alphabetic()) - .all(|c| c.is_uppercase()); - let is_system_constant = var_name.starts_with("_"); - let is_reserved = SYSTEM_VAR_NAMES.contains(&var_name); - - let highlight = if is_uppercase || is_system_constant || is_reserved { - Some("comment".to_string()) - } else { - None - }; - - Some(CodeLabel { - spans: vec![CodeLabelSpan::literal(label, highlight)], - filter_range: (0..label.len()).into(), - code: completion.label, - }) - } - _ => None, - } - } -} diff --git a/extensions/php/src/language_servers/phpactor.rs b/extensions/php/src/language_servers/phpactor.rs deleted file mode 100644 index 3ba668b1e82986b19fc116e895e8cd06a4f4a3c7..0000000000000000000000000000000000000000 --- a/extensions/php/src/language_servers/phpactor.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::fs; - -use zed_extension_api::{self as zed, LanguageServerId, Result}; - -pub struct Phpactor { - cached_binary_path: Option, -} - -impl Phpactor { - pub const LANGUAGE_SERVER_ID: &'static str = "phpactor"; - - pub fn new() -> Self { - Self { - cached_binary_path: None, - } - } - - pub fn language_server_binary_path( - &mut self, - language_server_id: &LanguageServerId, - worktree: &zed::Worktree, - ) -> Result { - if let Some(path) = worktree.which("phpactor") { - return Ok(path); - } - - if let Some(path) = &self.cached_binary_path { - if fs::metadata(path).map_or(false, |stat| stat.is_file()) { - return Ok(path.clone()); - } - } - - zed::set_language_server_installation_status( - language_server_id, - &zed::LanguageServerInstallationStatus::CheckingForUpdate, - ); - let release = zed::latest_github_release( - "phpactor/phpactor", - zed::GithubReleaseOptions { - require_assets: true, - pre_release: false, - }, - )?; - - let asset_name = "phpactor.phar"; - let asset = release - .assets - .iter() - .find(|asset| asset.name == asset_name) - .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; - - let version_dir = format!("phpactor-{}", release.version); - fs::create_dir_all(&version_dir).map_err(|e| format!("failed to create directory: {e}"))?; - - let binary_path = format!("{version_dir}/phpactor.phar"); - - if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { - zed::set_language_server_installation_status( - language_server_id, - &zed::LanguageServerInstallationStatus::Downloading, - ); - - zed::download_file( - &asset.download_url, - &binary_path, - zed::DownloadedFileType::Uncompressed, - ) - .map_err(|e| format!("failed to download file: {e}"))?; - - zed::make_file_executable(&binary_path)?; - - let entries = - fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?; - for entry in entries { - let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?; - if entry.file_name().to_str() != Some(&version_dir) { - fs::remove_dir_all(entry.path()).ok(); - } - } - } - - self.cached_binary_path = Some(binary_path.clone()); - Ok(binary_path) - } -} diff --git a/extensions/php/src/php.rs b/extensions/php/src/php.rs deleted file mode 100644 index 53b4c299516241e28cebcd6d8bbd9d423055105b..0000000000000000000000000000000000000000 --- a/extensions/php/src/php.rs +++ /dev/null @@ -1,72 +0,0 @@ -mod language_servers; - -use zed::CodeLabel; -use zed_extension_api::{self as zed, serde_json, LanguageServerId, Result}; - -use crate::language_servers::{Intelephense, Phpactor}; - -struct PhpExtension { - intelephense: Option, - phpactor: Option, -} - -impl zed::Extension for PhpExtension { - fn new() -> Self { - Self { - intelephense: None, - phpactor: None, - } - } - - fn language_server_command( - &mut self, - language_server_id: &LanguageServerId, - worktree: &zed::Worktree, - ) -> Result { - match language_server_id.as_ref() { - Intelephense::LANGUAGE_SERVER_ID => { - let intelephense = self.intelephense.get_or_insert_with(Intelephense::new); - intelephense.language_server_command(language_server_id, worktree) - } - Phpactor::LANGUAGE_SERVER_ID => { - let phpactor = self.phpactor.get_or_insert_with(Phpactor::new); - - Ok(zed::Command { - command: phpactor.language_server_binary_path(language_server_id, worktree)?, - args: vec!["language-server".into()], - env: Default::default(), - }) - } - language_server_id => Err(format!("unknown language server: {language_server_id}")), - } - } - - fn language_server_workspace_configuration( - &mut self, - language_server_id: &LanguageServerId, - worktree: &zed::Worktree, - ) -> Result> { - if language_server_id.as_ref() == Intelephense::LANGUAGE_SERVER_ID { - if let Some(intelephense) = self.intelephense.as_mut() { - return intelephense.language_server_workspace_configuration(worktree); - } - } - - Ok(None) - } - - fn label_for_completion( - &self, - language_server_id: &zed::LanguageServerId, - completion: zed::lsp::Completion, - ) -> Option { - match language_server_id.as_ref() { - Intelephense::LANGUAGE_SERVER_ID => { - self.intelephense.as_ref()?.label_for_completion(completion) - } - _ => None, - } - } -} - -zed::register_extension!(PhpExtension);