Detailed changes
@@ -213,6 +213,8 @@
// Whether to show the signature help after completion or a bracket pair inserted.
// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": false,
+ // Whether to show code action button at start of buffer line.
+ "inline_code_actions": true,
// What to do when go to definition yields no results.
//
// 1. Do nothing: `none`
@@ -324,7 +326,7 @@
// Whether to show agent review buttons in the editor toolbar.
"agent_review": true,
// Whether to show code action buttons in the editor toolbar.
- "code_actions": true
+ "code_actions": false
},
// Titlebar related settings
"title_bar": {
@@ -1072,6 +1072,7 @@ pub struct EditorSnapshot {
show_gutter: bool,
show_line_numbers: Option<bool>,
show_git_diff_gutter: Option<bool>,
+ show_code_actions: Option<bool>,
show_runnables: Option<bool>,
show_breakpoints: Option<bool>,
git_blame_gutter_max_author_length: Option<usize>,
@@ -2307,6 +2308,7 @@ impl Editor {
show_gutter: self.show_gutter,
show_line_numbers: self.show_line_numbers,
show_git_diff_gutter: self.show_git_diff_gutter,
+ show_code_actions: self.show_code_actions,
show_runnables: self.show_runnables,
show_breakpoints: self.show_breakpoints,
git_blame_gutter_max_author_length,
@@ -5755,7 +5757,7 @@ impl Editor {
self.refresh_code_actions(window, cx);
}
- pub fn code_actions_enabled(&self, cx: &App) -> bool {
+ pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
!self.code_action_providers.is_empty()
&& EditorSettings::get_global(cx).toolbar.code_actions
}
@@ -5766,6 +5768,53 @@ impl Editor {
.is_some_and(|(_, actions)| !actions.is_empty())
}
+ fn render_inline_code_actions(
+ &self,
+ icon_size: ui::IconSize,
+ display_row: DisplayRow,
+ is_active: bool,
+ cx: &mut Context<Self>,
+ ) -> AnyElement {
+ let show_tooltip = !self.context_menu_visible();
+ IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
+ .icon_size(icon_size)
+ .shape(ui::IconButtonShape::Square)
+ .style(ButtonStyle::Transparent)
+ .icon_color(ui::Color::Hidden)
+ .toggle_state(is_active)
+ .when(show_tooltip, |this| {
+ this.tooltip({
+ let focus_handle = self.focus_handle.clone();
+ move |window, cx| {
+ Tooltip::for_action_in(
+ "Toggle Code Actions",
+ &ToggleCodeActions {
+ deployed_from: None,
+ quick_launch: false,
+ },
+ &focus_handle,
+ window,
+ cx,
+ )
+ }
+ })
+ })
+ .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
+ window.focus(&editor.focus_handle(cx));
+ editor.toggle_code_actions(
+ &crate::actions::ToggleCodeActions {
+ deployed_from: Some(crate::actions::CodeActionSource::Indicator(
+ display_row,
+ )),
+ quick_launch: false,
+ },
+ window,
+ cx,
+ );
+ }))
+ .into_any_element()
+ }
+
pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
&self.context_menu
}
@@ -47,6 +47,7 @@ pub struct EditorSettings {
pub snippet_sort_order: SnippetSortOrder,
#[serde(default)]
pub diagnostics_max_severity: Option<DiagnosticSeverity>,
+ pub inline_code_actions: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -482,6 +483,11 @@ pub struct EditorSettingsContent {
/// Default: warning
#[serde(default)]
pub diagnostics_max_severity: Option<DiagnosticSeverity>,
+
+ /// Whether to show code action button at start of buffer line.
+ ///
+ /// Default: true
+ pub inline_code_actions: Option<bool>,
}
// Toolbar related settings
@@ -506,7 +512,7 @@ pub struct ToolbarContent {
pub agent_review: Option<bool>,
/// Whether to display code action buttons in the editor toolbar.
///
- /// Default: true
+ /// Default: false
pub code_actions: Option<bool>,
}
@@ -1937,6 +1937,159 @@ impl EditorElement {
elements
}
+ fn layout_inline_code_actions(
+ &self,
+ display_point: DisplayPoint,
+ content_origin: gpui::Point<Pixels>,
+ scroll_pixel_position: gpui::Point<Pixels>,
+ line_height: Pixels,
+ snapshot: &EditorSnapshot,
+ window: &mut Window,
+ cx: &mut App,
+ ) -> Option<AnyElement> {
+ if !snapshot
+ .show_code_actions
+ .unwrap_or(EditorSettings::get_global(cx).inline_code_actions)
+ {
+ return None;
+ }
+
+ let icon_size = ui::IconSize::XSmall;
+ let mut button = self.editor.update(cx, |editor, cx| {
+ editor.available_code_actions.as_ref()?;
+ let active = editor
+ .context_menu
+ .borrow()
+ .as_ref()
+ .and_then(|menu| {
+ if let crate::CodeContextMenu::CodeActions(CodeActionsMenu {
+ deployed_from,
+ ..
+ }) = menu
+ {
+ deployed_from.as_ref()
+ } else {
+ None
+ }
+ })
+ .map_or(false, |source| {
+ matches!(source, CodeActionSource::Indicator(..))
+ });
+ Some(editor.render_inline_code_actions(icon_size, display_point.row(), active, cx))
+ })?;
+
+ let buffer_point = display_point.to_point(&snapshot.display_snapshot);
+
+ // do not show code action for folded line
+ if snapshot.is_line_folded(MultiBufferRow(buffer_point.row)) {
+ return None;
+ }
+
+ // do not show code action for blank line with cursor
+ let line_indent = snapshot
+ .display_snapshot
+ .buffer_snapshot
+ .line_indent_for_row(MultiBufferRow(buffer_point.row));
+ if line_indent.is_line_blank() {
+ return None;
+ }
+
+ const INLINE_SLOT_CHAR_LIMIT: u32 = 4;
+ const MAX_ALTERNATE_DISTANCE: u32 = 8;
+
+ let excerpt_id = snapshot
+ .display_snapshot
+ .buffer_snapshot
+ .excerpt_containing(buffer_point..buffer_point)
+ .map(|excerpt| excerpt.id());
+
+ let is_valid_row = |row_candidate: u32| -> bool {
+ // move to other row if folded row
+ if snapshot.is_line_folded(MultiBufferRow(row_candidate)) {
+ return false;
+ }
+ if buffer_point.row == row_candidate {
+ // move to other row if cursor is in slot
+ if buffer_point.column < INLINE_SLOT_CHAR_LIMIT {
+ return false;
+ }
+ } else {
+ let candidate_point = MultiBufferPoint {
+ row: row_candidate,
+ column: 0,
+ };
+ let candidate_excerpt_id = snapshot
+ .display_snapshot
+ .buffer_snapshot
+ .excerpt_containing(candidate_point..candidate_point)
+ .map(|excerpt| excerpt.id());
+ // move to other row if different excerpt
+ if excerpt_id != candidate_excerpt_id {
+ return false;
+ }
+ }
+ let line_indent = snapshot
+ .display_snapshot
+ .buffer_snapshot
+ .line_indent_for_row(MultiBufferRow(row_candidate));
+ // use this row if it's blank
+ if line_indent.is_line_blank() {
+ true
+ } else {
+ // use this row if code starts after slot
+ let indent_size = snapshot
+ .display_snapshot
+ .buffer_snapshot
+ .indent_size_for_line(MultiBufferRow(row_candidate));
+ indent_size.len >= INLINE_SLOT_CHAR_LIMIT
+ }
+ };
+
+ let new_buffer_row = if is_valid_row(buffer_point.row) {
+ Some(buffer_point.row)
+ } else {
+ let max_row = snapshot.display_snapshot.buffer_snapshot.max_point().row;
+ (1..=MAX_ALTERNATE_DISTANCE).find_map(|offset| {
+ let row_above = buffer_point.row.saturating_sub(offset);
+ let row_below = buffer_point.row + offset;
+ if row_above != buffer_point.row && is_valid_row(row_above) {
+ Some(row_above)
+ } else if row_below <= max_row && is_valid_row(row_below) {
+ Some(row_below)
+ } else {
+ None
+ }
+ })
+ }?;
+
+ let new_display_row = snapshot
+ .display_snapshot
+ .point_to_display_point(
+ Point {
+ row: new_buffer_row,
+ column: buffer_point.column,
+ },
+ text::Bias::Left,
+ )
+ .row();
+
+ let start_y = content_origin.y
+ + ((new_display_row.as_f32() - (scroll_pixel_position.y / line_height)) * line_height)
+ + (line_height / 2.0)
+ - (icon_size.square(window, cx) / 2.);
+ let start_x = content_origin.x - scroll_pixel_position.x + (window.rem_size() * 0.1);
+
+ let absolute_offset = gpui::point(start_x, start_y);
+ button.layout_as_root(gpui::AvailableSpace::min_size(), window, cx);
+ button.prepaint_as_root(
+ absolute_offset,
+ gpui::AvailableSpace::min_size(),
+ window,
+ cx,
+ );
+ Some(button)
+ }
+
fn layout_inline_blame(
&self,
display_row: DisplayRow,
@@ -5304,6 +5457,7 @@ impl EditorElement {
self.paint_cursors(layout, window, cx);
self.paint_inline_diagnostics(layout, window, cx);
self.paint_inline_blame(layout, window, cx);
+ self.paint_inline_code_actions(layout, window, cx);
self.paint_diff_hunk_controls(layout, window, cx);
window.with_element_namespace("crease_trailers", |window| {
for trailer in layout.crease_trailers.iter_mut().flatten() {
@@ -5929,6 +6083,19 @@ impl EditorElement {
}
}
+ fn paint_inline_code_actions(
+ &mut self,
+ layout: &mut EditorLayout,
+ window: &mut Window,
+ cx: &mut App,
+ ) {
+ if let Some(mut inline_code_actions) = layout.inline_code_actions.take() {
+ window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
+ inline_code_actions.paint(window, cx);
+ })
+ }
+ }
+
fn paint_diff_hunk_controls(
&mut self,
layout: &mut EditorLayout,
@@ -7984,15 +8151,27 @@ impl Element for EditorElement {
);
let mut inline_blame = None;
+ let mut inline_code_actions = None;
if let Some(newest_selection_head) = newest_selection_head {
let display_row = newest_selection_head.row();
if (start_row..end_row).contains(&display_row)
&& !row_block_types.contains_key(&display_row)
{
+ inline_code_actions = self.layout_inline_code_actions(
+ newest_selection_head,
+ content_origin,
+ scroll_pixel_position,
+ line_height,
+ &snapshot,
+ window,
+ cx,
+ );
+
let line_ix = display_row.minus(start_row) as usize;
let row_info = &row_infos[line_ix];
let line_layout = &line_layouts[line_ix];
let crease_trailer_layout = crease_trailers[line_ix].as_ref();
+
inline_blame = self.layout_inline_blame(
display_row,
row_info,
@@ -8336,6 +8515,7 @@ impl Element for EditorElement {
blamed_display_rows,
inline_diagnostics,
inline_blame,
+ inline_code_actions,
blocks,
cursors,
visible_cursors,
@@ -8516,6 +8696,7 @@ pub struct EditorLayout {
blamed_display_rows: Option<Vec<AnyElement>>,
inline_diagnostics: HashMap<DisplayRow, AnyElement>,
inline_blame: Option<AnyElement>,
+ inline_code_actions: Option<AnyElement>,
blocks: Vec<BlockLayout>,
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
@@ -111,7 +111,7 @@ impl Render for QuickActionBar {
let supports_minimap = editor_value.supports_minimap(cx);
let minimap_enabled = supports_minimap && editor_value.minimap().is_some();
let has_available_code_actions = editor_value.has_available_code_actions();
- let code_action_enabled = editor_value.code_actions_enabled(cx);
+ let code_action_enabled = editor_value.code_actions_enabled_for_toolbar(cx);
let focus_handle = editor_value.focus_handle(cx);
let search_button = editor.is_singleton(cx).then(|| {
@@ -147,17 +147,16 @@ impl Render for QuickActionBar {
let code_actions_dropdown = code_action_enabled.then(|| {
let focus = editor.focus_handle(cx);
- let (code_action_menu_active, is_deployed_from_quick_action) = {
+ let is_deployed = {
let menu_ref = editor.read(cx).context_menu().borrow();
let code_action_menu = menu_ref
.as_ref()
.filter(|menu| matches!(menu, CodeContextMenu::CodeActions(..)));
- let is_deployed = code_action_menu.as_ref().map_or(false, |menu| {
+ code_action_menu.as_ref().map_or(false, |menu| {
matches!(menu.origin(), ContextMenuOrigin::QuickActionBar)
- });
- (code_action_menu.is_some(), is_deployed)
+ })
};
- let code_action_element = if is_deployed_from_quick_action {
+ let code_action_element = if is_deployed {
editor.update(cx, |editor, cx| {
if let Some(style) = editor.style() {
editor.render_context_menu(&style, MAX_CODE_ACTION_MENU_LINES, window, cx)
@@ -174,8 +173,8 @@ impl Render for QuickActionBar {
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.disabled(!has_available_code_actions)
- .toggle_state(code_action_menu_active)
- .when(!code_action_menu_active, |this| {
+ .toggle_state(is_deployed)
+ .when(!is_deployed, |this| {
this.when(has_available_code_actions, |this| {
this.tooltip(Tooltip::for_action_title(
"Code Actions",
@@ -1203,6 +1203,16 @@ or
}
```
+### Show Inline Code Actions
+
+- Description: Whether to show code action button at start of buffer line.
+- Setting: `inline_code_actions`
+- Default: `true`
+
+**Options**
+
+`boolean` values
+
## Editor Toolbar
- Description: Whether or not to show various elements in the editor toolbar.
@@ -1215,7 +1225,7 @@ or
"quick_actions": true,
"selections_menu": true,
"agent_review": true,
- "code_actions": true
+ "code_actions": false
},
```