Line numbers short mode (#10354)

Piotr Osiewicz created

Followup to #10327 
It can be enabled with the following setting:

"line_indicator_format": "short"

No release note, as the original change didn't go out to Preview yet.

/cc @bartekpacia @0x2CA

Release Notes:

- N/A

Change summary

Cargo.lock                               |  4 +
assets/settings/default.json             |  9 +++
crates/go_to_line/Cargo.toml             |  4 +
crates/go_to_line/src/cursor_position.rs | 57 +++++++++++++++++++++++--
crates/go_to_line/src/go_to_line.rs      |  3 +
5 files changed, 71 insertions(+), 6 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4375,6 +4375,7 @@ dependencies = [
 name = "go_to_line"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "editor",
  "gpui",
  "indoc",
@@ -4382,7 +4383,10 @@ dependencies = [
  "menu",
  "project",
  "rope",
+ "schemars",
+ "serde",
  "serde_json",
+ "settings",
  "text",
  "theme",
  "tree-sitter-rust",

assets/settings/default.json 🔗

@@ -626,5 +626,12 @@
   "task": {
     // Whether to show task status indicator in the status bar. Default: true
     "show_status_indicator": true
-  }
+  },
+  // Whether to show full labels in line indicator or short ones
+  //
+  // Values:
+  //   - `short`: "2 s, 15 l, 32 c"
+  //   - `long`: "2 selections, 15 lines, 32 characters"
+  // Default: long
+  "line_indicator_format": "long"
 }

crates/go_to_line/Cargo.toml 🔗

@@ -13,9 +13,13 @@ path = "src/go_to_line.rs"
 doctest = false
 
 [dependencies]
+anyhow.workspace = true
 editor.workspace = true
 gpui.workspace = true
 menu.workspace = true
+schemars.workspace = true
+serde.workspace = true
+settings.workspace = true
 text.workspace = true
 theme.workspace = true
 ui.workspace = true

crates/go_to_line/src/cursor_position.rs 🔗

@@ -1,5 +1,8 @@
 use editor::{Editor, ToPoint};
-use gpui::{Subscription, View, WeakView};
+use gpui::{AppContext, Subscription, View, WeakView};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use settings::{Settings, SettingsSources};
 use std::fmt::Write;
 use text::{Point, Selection};
 use ui::{
@@ -51,7 +54,10 @@ impl CursorPosition {
         }
         for selection in editor.selections.all::<Point>(cx) {
             if selection.end != selection.start {
-                self.selected_count.lines += (selection.end.row - selection.start.row + 1) as usize;
+                self.selected_count.lines += (selection.end.row - selection.start.row) as usize;
+                if selection.end.column != 0 {
+                    self.selected_count.lines += 1;
+                }
             }
         }
         self.position = last_selection.map(|s| s.head().to_point(&buffer));
@@ -59,7 +65,7 @@ impl CursorPosition {
         cx.notify();
     }
 
-    fn write_position(&self, text: &mut String) {
+    fn write_position(&self, text: &mut String, cx: &AppContext) {
         if self.selected_count
             <= (SelectionStats {
                 selections: 1,
@@ -74,6 +80,8 @@ impl CursorPosition {
             characters,
             selections,
         } = self.selected_count;
+        let format = LineIndicatorFormat::get(None, cx);
+        let is_short_format = format == &LineIndicatorFormat::Short;
         let lines = (lines > 1).then_some((lines, "line"));
         let selections = (selections > 1).then_some((selections, "selection"));
         let characters = (characters > 0).then_some((characters, "character"));
@@ -87,7 +95,12 @@ impl CursorPosition {
             if wrote_once {
                 write!(text, ", ").unwrap();
             }
-            let plural_suffix = if count > 1 { "s" } else { "" };
+            let name = if is_short_format { &name[..1] } else { &name };
+            let plural_suffix = if count > 1 && !is_short_format {
+                "s"
+            } else {
+                ""
+            };
             write!(text, "{count} {name}{plural_suffix}").unwrap();
             wrote_once = true;
         }
@@ -103,7 +116,7 @@ impl Render for CursorPosition {
                 position.row + 1,
                 position.column + 1
             );
-            self.write_position(&mut text);
+            self.write_position(&mut text, cx);
 
             el.child(
                 Button::new("go-to-line-column", text)
@@ -144,3 +157,37 @@ impl StatusItemView for CursorPosition {
         cx.notify();
     }
 }
+
+#[derive(Clone, Copy, Default, PartialEq, JsonSchema, Deserialize, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub(crate) enum LineIndicatorFormat {
+    Short,
+    #[default]
+    Long,
+}
+
+/// Whether or not to automatically check for updates.
+///
+/// Values: short, long
+/// Default: short
+#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)]
+#[serde(transparent)]
+pub(crate) struct LineIndicatorFormatContent(LineIndicatorFormat);
+
+impl Settings for LineIndicatorFormat {
+    const KEY: Option<&'static str> = Some("line_indicator_format");
+
+    type FileContent = Option<LineIndicatorFormatContent>;
+
+    fn load(
+        sources: SettingsSources<Self::FileContent>,
+        _: &mut AppContext,
+    ) -> anyhow::Result<Self> {
+        let format = [sources.release_channel, sources.user]
+            .into_iter()
+            .find_map(|value| value.copied().flatten())
+            .unwrap_or(sources.default.ok_or_else(Self::missing_default)?);
+
+        Ok(format.0)
+    }
+}

crates/go_to_line/src/go_to_line.rs 🔗

@@ -1,10 +1,12 @@
 pub mod cursor_position;
 
+use cursor_position::LineIndicatorFormat;
 use editor::{scroll::Autoscroll, Editor};
 use gpui::{
     actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
     FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
 };
+use settings::Settings;
 use text::{Bias, Point};
 use theme::ActiveTheme;
 use ui::{h_flex, prelude::*, v_flex, Label};
@@ -14,6 +16,7 @@ use workspace::ModalView;
 actions!(go_to_line, [Toggle]);
 
 pub fn init(cx: &mut AppContext) {
+    LineIndicatorFormat::register(cx);
     cx.observe_new_views(GoToLine::register).detach();
 }