Support relative line number on wrapped lines (#39268)

**Problem:** Current relative line numbering creates a mismatch with
vim-style navigation when soft wrap is enabled. Users must mentally
calculate whether target lines are wrapped segments or logical lines,
making `<n>j/k` navigation unreliable and cognitively demanding.

**How things work today:**
- Real line navigation (`j/k` moves by logical lines): Requires
determining if visible lines are wrapped segments before jumping. Can't
jump to wrapped lines directly.
- Display line navigation (`j/k` moves by display rows): Line numbers
don't correspond to actual row distances for multi-line jumps.

**Proposed solution:** Count and number each display line (including
wrapped segments) for relative numbering. This creates direct
visual-to-navigational correspondence where the relative number shown
always matches the `<n>j/k` distance needed.

**Benefits:**
- Eliminates mental overhead of distinguishing wrapped vs. logical lines
- Makes relative line numbers consistently actionable regardless of wrap
state
- Preserves intuitive "what you see is what you navigate" principle
- Maintains vim workflow efficiency in narrow window scenarios

Also explained an discussed in
https://github.com/zed-industries/zed/discussions/25733.

Release Notes:

Release Notes:

- Added support for counting wrapped lines as relative lines and for
displaying line numbers for wrapped segments. Changes
`relative_line_numbers` from a boolean to an enum: `enabled`,
`disabled`, or `wrapped`.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Thomas Heartman
2025-10-27 20:20:45 -06:00
committed by GitHub
co-authored by Conrad Irwin
parent 1c4923e1c8
commit d5e297147f
16 changed files with 332 additions and 122 deletions
+39 -2
View File
@@ -97,9 +97,11 @@ pub struct EditorSettingsContent {
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub fast_scroll_sensitivity: Option<f32>,
/// Whether the line numbers on editors gutter are relative or not.
/// When "enabled" shows relative number of buffer lines, when "wrapped" shows
/// relative number of display lines.
///
/// Default: false
pub relative_line_numbers: Option<bool>,
/// Default: "disabled"
pub relative_line_numbers: Option<RelativeLineNumbers>,
/// When to populate a new search's query based on the text under the cursor.
///
/// Default: always
@@ -199,6 +201,41 @@ pub struct EditorSettingsContent {
pub lsp_document_colors: Option<DocumentColorsRenderMode>,
}
#[derive(
Debug,
Clone,
Copy,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
PartialEq,
Eq,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum RelativeLineNumbers {
Disabled,
Enabled,
Wrapped,
}
impl RelativeLineNumbers {
pub fn enabled(&self) -> bool {
match self {
RelativeLineNumbers::Enabled | RelativeLineNumbers::Wrapped => true,
RelativeLineNumbers::Disabled => false,
}
}
pub fn wrapped(&self) -> bool {
match self {
RelativeLineNumbers::Enabled | RelativeLineNumbers::Disabled => false,
RelativeLineNumbers::Wrapped => true,
}
}
}
// Toolbar related settings
#[skip_serializing_none]
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]
+1 -1
View File
@@ -275,7 +275,7 @@ impl VsCodeSettings {
}),
redact_private_values: None,
relative_line_numbers: self.read_enum("editor.lineNumbers", |s| match s {
"relative" => Some(true),
"relative" => Some(RelativeLineNumbers::Enabled),
_ => None,
}),
rounded_selection: self.read_bool("editor.roundedSelection"),