Skip to content

Commit f7a0971

Browse files
authored
Add line endings indicator in status bar (#39609)
Closes #5294 This PR adds a line ending indicator to the status bar, hidden by default as discussed in #5294. ### Changes - 8b063a2 add the indicator and `status_bar.line_endings_button` setting. - ~~9926237b709dd4e25ce58d558fd385d63b405f3b changes `status_bar.line_endings_button` from a boolean to an enum:~~ <details> <summary> show details </summary> - `always` Always show line endings indicator. - `non_native` Indicate when line endings do not match the current platform. - `lf_only` Indicate when using unix-style (LF) line endings only. - `crlf_only` Indicate when using windows-style (CRLF) line endings only. - `never` Do not show line endings indicator. I know this many options might be overdoing it, but I was torn between the pleasant default of `non_native` and the simplicity of `lf_only` / `crlf_only`. My thinking was if one is developing on a project which exclusively uses one line-ending style or the other, it would be nice to be able to configure no-indicator-in-the-happy-case behavior regardless of the platform zed is running on. But I'm not really familiar with any projects that use exclusively CRLF line endings in practice. Is this a scenario worth supporting or just something I dreamed up? </details> - 0117419 rename the action context for `line ending: Toggle` -> `line ending selector: Toggle`. When running the action in the command palette with the old name I felt surprised to be greeted with an additional menu, with the new name it feels more predictable (plus now it matches `language_selector::Toggle`!) ### Future work Hidden status bar items still get padding, creating inconsistent spacing (and it kind of stands out where I placed the line-endings button): <img alt="the gap after the indicator is larger than for other buttons" src="https://github.com/user-attachments/assets/24a346d4-3ff6-4f7f-bd87-64d453c2441a" /> I started a new follow-up PR to address that: #39992 Release Notes: - Added line ending indicator to the status bar (disabled by default; enabled by setting `status_bar.line_endings_button` to `true`)
1 parent ed82233 commit f7a0971

File tree

9 files changed

+101
-8
lines changed

9 files changed

+101
-8
lines changed

assets/settings/default.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1350,7 +1350,9 @@
13501350
// Whether to show the active language button in the status bar.
13511351
"active_language_button": true,
13521352
// Whether to show the cursor position button in the status bar.
1353-
"cursor_position_button": true
1353+
"cursor_position_button": true,
1354+
// Whether to show active line endings button in the status bar.
1355+
"line_endings_button": false
13541356
},
13551357
// Settings specific to the terminal
13561358
"terminal": {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use editor::Editor;
2+
use gpui::{Entity, Subscription, WeakEntity};
3+
use language::LineEnding;
4+
use ui::{Tooltip, prelude::*};
5+
use workspace::{StatusBarSettings, StatusItemView, item::ItemHandle, item::Settings};
6+
7+
use crate::{LineEndingSelector, Toggle};
8+
9+
#[derive(Default)]
10+
pub struct LineEndingIndicator {
11+
line_ending: Option<LineEnding>,
12+
active_editor: Option<WeakEntity<Editor>>,
13+
_observe_active_editor: Option<Subscription>,
14+
}
15+
16+
impl LineEndingIndicator {
17+
fn update(&mut self, editor: Entity<Editor>, _: &mut Window, cx: &mut Context<Self>) {
18+
self.line_ending = None;
19+
self.active_editor = None;
20+
21+
if let Some((_, buffer, _)) = editor.read(cx).active_excerpt(cx) {
22+
let line_ending = buffer.read(cx).line_ending();
23+
self.line_ending = Some(line_ending);
24+
self.active_editor = Some(editor.downgrade());
25+
}
26+
27+
cx.notify();
28+
}
29+
}
30+
31+
impl Render for LineEndingIndicator {
32+
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
33+
if !StatusBarSettings::get_global(cx).line_endings_button {
34+
return div();
35+
}
36+
37+
div().when_some(self.line_ending.as_ref(), |el, line_ending| {
38+
el.child(
39+
Button::new("change-line-ending", line_ending.label())
40+
.label_size(LabelSize::Small)
41+
.on_click(cx.listener(|this, _, window, cx| {
42+
if let Some(editor) = this.active_editor.as_ref() {
43+
LineEndingSelector::toggle(editor, window, cx);
44+
}
45+
}))
46+
.tooltip(|window, cx| {
47+
Tooltip::for_action("Select Line Ending", &Toggle, window, cx)
48+
}),
49+
)
50+
})
51+
}
52+
}
53+
54+
impl StatusItemView for LineEndingIndicator {
55+
fn set_active_pane_item(
56+
&mut self,
57+
active_pane_item: Option<&dyn ItemHandle>,
58+
window: &mut Window,
59+
cx: &mut Context<Self>,
60+
) {
61+
if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
62+
self._observe_active_editor = Some(cx.observe_in(&editor, window, Self::update));
63+
self.update(editor, window, cx);
64+
} else {
65+
self.line_ending = None;
66+
self._observe_active_editor = None;
67+
}
68+
cx.notify();
69+
}
70+
}

crates/line_ending_selector/src/line_ending_selector.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
mod line_ending_indicator;
2+
13
use editor::Editor;
24
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity, actions};
35
use language::{Buffer, LineEnding};
6+
pub use line_ending_indicator::LineEndingIndicator;
47
use picker::{Picker, PickerDelegate};
58
use project::Project;
69
use std::sync::Arc;
@@ -9,7 +12,7 @@ use util::ResultExt;
912
use workspace::ModalView;
1013

1114
actions!(
12-
line_ending,
15+
line_ending_selector,
1316
[
1417
/// Toggles the line ending selector modal.
1518
Toggle
@@ -172,10 +175,7 @@ impl PickerDelegate for LineEndingSelectorDelegate {
172175
_: &mut Context<Picker<Self>>,
173176
) -> Option<Self::ListItem> {
174177
let line_ending = self.matches.get(ix)?;
175-
let label = match line_ending {
176-
LineEnding::Unix => "LF",
177-
LineEnding::Windows => "CRLF",
178-
};
178+
let label = line_ending.label();
179179

180180
let mut list_item = ListItem::new(ix)
181181
.inset(true)

crates/settings/src/settings_content/workspace.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,10 @@ pub struct StatusBarSettingsContent {
380380
///
381381
/// Default: true
382382
pub cursor_position_button: Option<bool>,
383+
/// Whether to show active line endings button in the status bar.
384+
///
385+
/// Default: false
386+
pub line_endings_button: Option<bool>,
383387
}
384388

385389
#[derive(

crates/text/src/text.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3267,6 +3267,13 @@ impl LineEnding {
32673267
}
32683268
}
32693269

3270+
pub fn label(&self) -> &'static str {
3271+
match self {
3272+
LineEnding::Unix => "LF",
3273+
LineEnding::Windows => "CRLF",
3274+
}
3275+
}
3276+
32703277
pub fn detect(text: &str) -> Self {
32713278
let mut max_ix = cmp::min(text.len(), 1000);
32723279
while !text.is_char_boundary(max_ix) {

crates/workspace/src/workspace_settings.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ pub struct StatusBarSettings {
126126
pub show: bool,
127127
pub active_language_button: bool,
128128
pub cursor_position_button: bool,
129+
pub line_endings_button: bool,
129130
}
130131

131132
impl Settings for StatusBarSettings {
@@ -135,6 +136,7 @@ impl Settings for StatusBarSettings {
135136
show: status_bar.show.unwrap(),
136137
active_language_button: status_bar.active_language_button.unwrap(),
137138
cursor_position_button: status_bar.cursor_position_button.unwrap(),
139+
line_endings_button: status_bar.line_endings_button.unwrap(),
138140
}
139141
}
140142
}

crates/zed/src/zed.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@ pub fn initialize_workspace(
417417

418418
let cursor_position =
419419
cx.new(|_| go_to_line::cursor_position::CursorPosition::new(workspace));
420+
let line_ending_indicator =
421+
cx.new(|_| line_ending_selector::LineEndingIndicator::default());
420422
workspace.status_bar().update(cx, |status_bar, cx| {
421423
status_bar.add_left_item(search_button, window, cx);
422424
status_bar.add_left_item(lsp_button, window, cx);
@@ -425,6 +427,7 @@ pub fn initialize_workspace(
425427
status_bar.add_right_item(edit_prediction_button, window, cx);
426428
status_bar.add_right_item(active_buffer_language, window, cx);
427429
status_bar.add_right_item(active_toolchain_language, window, cx);
430+
status_bar.add_right_item(line_ending_indicator, window, cx);
428431
status_bar.add_right_item(vim_mode_indicator, window, cx);
429432
status_bar.add_right_item(cursor_position, window, cx);
430433
status_bar.add_right_item(image_info, window, cx);
@@ -4669,7 +4672,7 @@ mod tests {
46694672
"keymap_editor",
46704673
"keystroke_input",
46714674
"language_selector",
4672-
"line_ending",
4675+
"line_ending_selector",
46734676
"lsp_tool",
46744677
"markdown",
46754678
"menu",

docs/src/configuring-zed.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1498,7 +1498,8 @@ Positive `integer` value between 1 and 32. Values outside of this range will be
14981498
```json [settings]
14991499
"status_bar": {
15001500
"active_language_button": true,
1501-
"cursor_position_button": true
1501+
"cursor_position_button": true,
1502+
"line_endings_button": false
15021503
},
15031504
```
15041505

docs/src/visual-customization.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ TBD: Centered layout related settings
319319
// Clicking the button brings up an input for jumping to a line and column.
320320
// Defaults to true.
321321
"cursor_position_button": true,
322+
// Show/hide a button that displays the buffer's line-ending mode.
323+
// Clicking the button brings up the line-ending selector.
324+
// Defaults to false.
325+
"line_endings_button": false
322326
},
323327
"global_lsp_settings": {
324328
// Show/hide the LSP button in the status bar.

0 commit comments

Comments
 (0)