Detailed changes
@@ -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"
@@ -171,7 +171,6 @@ members = [
"extensions/haskell",
"extensions/html",
"extensions/lua",
- "extensions/php",
"extensions/perplexity",
"extensions/proto",
"extensions/purescript",
@@ -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::<DeletedLines>(
Anchor::min()..Anchor::max(),
cx.theme().status().deleted_background,
@@ -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::<DeletedLines>(
Anchor::min()..Anchor::max(),
cx.theme().status().deleted_background,
@@ -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<Anchor>,
}
+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<InlineCompletionState>,
/// Used to prevent flickering as the user types while the menu is open
stale_inline_completion_in_menu: Option<InlineCompletionState>,
+ edit_prediction_settings: EditPredictionSettings,
inline_completions_hidden_for_vim_mode: bool,
show_inline_completions_override: Option<bool>,
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<Self>) -> 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<Self>,
) {
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<bool>,
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>,
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<Self>,
) {
- 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)
}
@@ -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<SharedString>,
icon: Option<IconName>,
previewing: bool,
- editor_focus_handle: FocusHandle,
- window: &Window,
+ editor: &Editor,
+ window: &mut Window,
cx: &App,
) -> Option<AnyElement> {
- 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<gpui::KeyBinding>);
+pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
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() {
@@ -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();
@@ -176,23 +176,21 @@ pub struct GitPanel {
}
fn commit_message_editor(
- commit_message_buffer: Option<Entity<Buffer>>,
+ commit_message_buffer: Entity<Buffer>,
+ project: Entity<Project>,
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<Entity<Buffer>>,
cx: &mut Context<Workspace>,
) -> Entity<Self> {
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::<HashSet<_>>();
- 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::<Vec<_>>();
+ 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)
+ });
}
})
})
@@ -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<KeyBinding> {
+ 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<V: Render, E>(
&self,
@@ -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;
@@ -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
});
@@ -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<LspStore>,
mut buffers: Vec<FormattableBuffer>,
- 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::<Vec<_>>();
-
- 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::<Vec<_>>()
+ })?;
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<FormatOperation> = 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<Range<Anchor>>>,
- primary_server_and_path: Option<(&Arc<LanguageServer>, &PathBuf)>,
+ async fn execute_formatters(
lsp_store: WeakEntity<LspStore>,
+ formatters: &[Formatter],
+ buffer: &FormattableBuffer,
settings: &LanguageSettings,
adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
push_to_history: bool,
- transaction: &mut ProjectTransaction,
+ mut initial_transaction_id: Option<TransactionId>,
+ project_transaction: &mut ProjectTransaction,
cx: &mut AsyncApp,
- ) -> Result<Option<FormatOperation>, 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<LspStore>,
- buffer: &FormattableBuffer,
- ranges: &Vec<Range<Anchor>>,
+ buffer_handle: &Entity<Buffer>,
+ ranges: &[Range<Anchor>],
abs_path: &Path,
language_server: &Arc<LanguageServer>,
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<Buffer>,
abs_path: Option<PathBuf>,
env: Option<HashMap<String, String>>,
+ ranges: Option<Vec<Range<Anchor>>>,
}
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(),
@@ -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);
@@ -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::<GitUiFeatureFlag>(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);
});
})?;
@@ -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,
)
};
@@ -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);
@@ -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:(?<![\[a-zA-Z0-9])(#[0-9]{3,5})==>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:(?<![\[a-zA-Z0-9])(#[0-9]{3,5})==>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.
+Create PR and reference the Zed PR with removal from tree.
@@ -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"
@@ -1 +0,0 @@
-../../LICENSE-APACHE
@@ -1,25 +0,0 @@
-id = "php"
-name = "PHP"
-description = "PHP support."
-version = "0.2.4"
-schema_version = 1
-authors = ["Piotr Osiewicz <piotr@zed.dev>"]
-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"
@@ -1,4 +0,0 @@
-("{" @open "}" @close)
-("(" @open ")" @close)
-("[" @open "]" @close)
-("\"" @open "\"" @close)
@@ -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"]
@@ -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
- )
@@ -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
@@ -1 +0,0 @@
-(_ "{" "}" @end) @indent
@@ -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
@@ -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
@@ -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)
-)
@@ -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
@@ -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"]
- }
-]
@@ -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
@@ -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 },
-]
@@ -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
@@ -1,5 +0,0 @@
-mod intelephense;
-mod phpactor;
-
-pub use intelephense::*;
-pub use phpactor::*;
@@ -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<zed::Command> {
- 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<String> {
- 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<Option<serde_json::Value>> {
- 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<CodeLabel> {
- 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,
- }
- }
-}
@@ -1,85 +0,0 @@
-use std::fs;
-
-use zed_extension_api::{self as zed, LanguageServerId, Result};
-
-pub struct Phpactor {
- cached_binary_path: Option<String>,
-}
-
-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<String> {
- 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)
- }
-}
@@ -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<Intelephense>,
- phpactor: Option<Phpactor>,
-}
-
-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<zed::Command> {
- 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<Option<serde_json::Value>> {
- 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<CodeLabel> {
- match language_server_id.as_ref() {
- Intelephense::LANGUAGE_SERVER_ID => {
- self.intelephense.as_ref()?.label_for_completion(completion)
- }
- _ => None,
- }
- }
-}
-
-zed::register_extension!(PhpExtension);