first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# GH9816: Configurable code editor line number modes
|
||||
## Summary
|
||||
Add a configurable line numbering mode for Warp code editors so users can choose Absolute or Relative line numbers. The setting is independent of Vim mode, defaults to today’s absolute numbering, and makes Vim-style vertical motion counts easier to read when users choose relative numbering.
|
||||
## Problem
|
||||
Warp code editors currently show only absolute line numbers. Vim users commonly rely on relative-style line numbers to choose motions like `5j` and `12k` without mentally subtracting the current line from nearby line numbers. Because the existing input editor surfaces do not show line-number gutters, this feature should improve the code editor without implying a new gutter in command input.
|
||||
## Goals
|
||||
1. Let users choose how line numbers are displayed in code editor gutters.
|
||||
2. Preserve the current absolute numbering behavior by default.
|
||||
3. Make relative mode update immediately as the active cursor line changes.
|
||||
4. Keep the setting usable by all code editor users, not only users who enable Vim keybindings.
|
||||
5. Avoid adding line numbers to command input editors or rich-text notebook editors as part of this change.
|
||||
## Non-goals
|
||||
1. Do not add line-number gutters to the terminal input editor, AI input editor, or other command-entry surfaces.
|
||||
2. Do not add Vim `:set number`, `:set relativenumber`, or `:set norelativenumber` commands in this iteration.
|
||||
3. Do not change Vim motion behavior, cursor movement, selections, search, find-references, or diff navigation.
|
||||
4. Do not change whether a particular code editor surface chooses to show or hide its gutter; the new setting only affects gutters that already render line numbers.
|
||||
5. Do not redesign the gutter width, diff hunk controls, hidden-section controls, or inline review comment controls beyond the minimum needed to display the selected numbering mode.
|
||||
## Figma
|
||||
Figma: none provided. The feature reuses the existing code editor gutter and settings UI patterns.
|
||||
## Behavior
|
||||
1. Warp exposes a line numbering mode setting for code editors with exactly two choices:
|
||||
- **Absolute**: each line shows its absolute, one-based line number. This is the current behavior and the default for all users.
|
||||
- **Relative**: the active cursor line shows its absolute, one-based line number; every other visible line shows the absolute distance in lines from the active cursor line. Warp does not expose any additional line-number option in this iteration.
|
||||
2. The setting is available from Settings under the Text Editing area, near the existing code/text editing controls. It is not nested under the Vim-mode toggle and remains visible whether Vim keybindings are enabled or disabled.
|
||||
3. The setting persists like other public editor settings and is restored for future Warp windows and sessions. If the user has not chosen a value, Warp behaves exactly as it does today: Absolute mode.
|
||||
4. Changing the setting updates all currently open code editor gutters without requiring the user to reopen files, restart Warp, toggle Vim mode, or refocus the editor.
|
||||
5. Absolute mode is behaviorally identical to the current code editor gutter:
|
||||
- The first file line displays `1`, the second displays `2`, and so on.
|
||||
- Code editor surfaces that start numbering from a caller-provided starting line continue to use that starting line.
|
||||
- Hidden sections, diff hunk controls, and gutter action buttons behave as they do today.
|
||||
6. Relative mode uses the active cursor line as the origin while keeping the active line absolute:
|
||||
- If the cursor is on line 10, line 10 displays `10`, line 9 displays `1`, line 11 displays `1`, line 5 displays `5`, and line 22 displays `12`.
|
||||
- Moving the cursor, clicking another line, selecting text, or using keyboard navigation recomputes the displayed distances immediately.
|
||||
- Non-active relative distances are always positive integers; lines above and below the cursor both show positive distances.
|
||||
- In normal code editor surfaces, Relative mode uses the editor’s current primary selection head even when the editor has just opened or focus temporarily moves elsewhere; losing focus does not force normal code editor gutters back to Absolute mode.
|
||||
7. For multiple cursors or multiple selections, the active cursor line is the primary selection head used by the editor for cursor-position reporting. The gutter uses that single active line as the relative origin until a future design intentionally supports multiple relative origins.
|
||||
8. For visual selections, the active line remains the selection head, not the selection anchor or the full selected range. The displayed numbers update as the selection head moves.
|
||||
9. The line number mode is independent of Vim mode:
|
||||
- Users can choose Relative before enabling Vim keybindings.
|
||||
- Enabling or disabling Vim keybindings does not reset or hide the chosen line numbering mode.
|
||||
- Vim status bar and clipboard settings remain separate from line numbering.
|
||||
10. Code editor line numbers correspond to logical file lines, not soft-wrap rows. A long line that visually wraps still has one line number, and wrapped continuation rows do not introduce extra relative counts.
|
||||
11. Hidden/collapsed code regions keep their existing hidden-section gutter affordances. The hidden section itself does not need to display a relative count for every hidden line.
|
||||
12. Diff and review gutters keep their current affordances:
|
||||
- Diff and review editors that are not currently focused continue to show absolute line numbers, even when the global code editor setting is Relative.
|
||||
- When a diff or review editor is focused and Relative is selected, current-buffer lines across that editor that already display a line number use Relative mode from the active cursor line.
|
||||
- Removed/temporary diff lines that currently omit a line number continue to omit one unless a separate diff design changes that behavior.
|
||||
- Diff hunk buttons, comment buttons, hover hit targets, and collapse/expand interactions continue to work.
|
||||
13. The visual style of line numbers remains consistent with the existing gutter: same font family, size, colors, selection behavior, and alignment unless a small width or alignment adjustment is necessary to prevent relative values from clipping.
|
||||
14. The gutter reserves enough width for the largest value that can appear in the current mode:
|
||||
- Absolute must fit the largest absolute line number for the surface.
|
||||
- Relative must fit both the active line’s absolute number and the largest visible relative distance where practical, and must never overlap editor text or gutter controls.
|
||||
15. Command input editors, terminal prompt editors, AI input editors, and rich-text notebook editors do not show line numbers after this change. Their Vim status indicators and Vim keybindings continue to work as they do today.
|
||||
16. The settings UI is searchable with terms such as `line number`, `relative line`, `vim`, and `gutter`.
|
||||
17. If a settings file contains an invalid line numbering value, Warp falls back to Absolute mode using the existing settings validation/error behavior rather than failing to render editors.
|
||||
## Success criteria
|
||||
1. A new user or upgraded user with no explicit setting sees the same absolute code editor line numbers as before.
|
||||
2. Selecting Relative mode shows the active cursor line’s absolute number and distances on surrounding numbered code editor lines.
|
||||
3. Moving the cursor with mouse, arrow keys, search, Vim motions, or goto-line updates relative gutters immediately.
|
||||
4. The setting is visible and usable when Vim keybindings are disabled.
|
||||
5. Enabling or disabling Vim keybindings does not change the selected line numbering mode.
|
||||
6. Terminal input and AI input surfaces still do not render line-number gutters.
|
||||
7. Diff hunk controls, inline comment buttons, hidden-section controls, and find-references anchoring still work in code editors with each line numbering mode; inactive diff/review editors continue to show absolute line numbers until that editor is focused.
|
||||
## Validation
|
||||
1. Manually verify a multi-line file in the code editor with Absolute and Relative modes selected.
|
||||
2. In Relative mode, move the cursor above and below visible lines using mouse, arrow keys, goto-line, and Vim motions; verify displayed values update correctly.
|
||||
3. Verify the setting persists after closing and reopening Warp or reloading settings.
|
||||
4. Verify the setting remains visible when Vim mode is disabled and that toggling Vim mode does not reset it.
|
||||
5. Verify terminal command input, AI input, and rich-text notebook editors do not gain line-number gutters.
|
||||
6. Verify code review/diff editors still show diff decorations and gutter buttons correctly in both modes, show absolute line numbers while the diff/review editor is not focused, and apply Relative numbering across the focused editor.
|
||||
@@ -0,0 +1,122 @@
|
||||
# GH9816: Tech Spec — Configurable code editor line number modes
|
||||
## Context
|
||||
The product behavior is specified in `specs/GH9816/product.md`. The implementation should add a persistent editor setting and apply it to code editor gutters only.
|
||||
- `app/src/settings/editor.rs:132` defines `AppEditorSettings`, including `vim_mode`, `vim_unnamed_system_clipboard`, and `vim_status_bar` under `text_editing.*`. This is the right settings group for an independent code/text editing line-number mode.
|
||||
- `app/src/settings/init.rs:53` registers `AppEditorSettings`, so adding a field to that settings group automatically participates in normal startup registration.
|
||||
- `app/src/settings_view/features_page.rs (2491-2690)` builds the Text Editing category. Today it includes `AutocompleteSymbolsWidget` and conditionally `VimModeWidget`.
|
||||
- `app/src/settings_view/features_page.rs (5763-5962)` renders `VimModeWidget` and its nested Vim-only subsettings. The new line number mode should not be nested in this widget because the maintainer explicitly called for a setting independent of Vim settings.
|
||||
- `app/src/code/editor/view.rs (46-245)` defines `CodeEditorViewDisplayOptions`, including `show_line_numbers` and `starting_line_number`.
|
||||
- `app/src/code/editor/view.rs (1041-1239)` builds `LineNumberConfig` from appearance settings and passes it when line numbers are enabled.
|
||||
- `app/src/code/editor/view.rs (2068-2267)` creates `EditorWrapper` with `line_number_config`, diff status, saved comments, and gutter behavior.
|
||||
- `app/src/code/editor/element.rs (277-476)` defines `LineNumberConfig` and `EditorWrapper`.
|
||||
- `app/src/code/editor/element.rs (500-790)` builds gutter elements from visible editor blocks. Current absolute display is computed with `line_count.as_usize() + line_number_config.starting_line_number.unwrap_or(1)`.
|
||||
- `app/src/code/editor/element.rs (1048-1247)` renders the final gutter text in `render_gutter_element`.
|
||||
- `app/src/code/editor/view.rs (1525-1724)` exposes cursor helpers such as `cursor_lsp_position`, `cursor_head_offset`, and offset-to-position conversion that can be used to determine the active cursor line.
|
||||
- `app/src/terminal/input/common.rs:44`, `app/src/terminal/input/classic.rs (1-220)`, `app/src/terminal/input/universal.rs (1-200)`, and `app/src/editor/view/mod.rs (8546-8745)` show terminal input editors render Vim status and editor content but no line-number gutter. They should remain untouched except for regression testing.
|
||||
- `app/src/notebooks/editor/view.rs (2466-2664)` renders the rich-text notebook editor with `RichTextElement` and explicitly does not support Vim; it also does not use the code editor gutter.
|
||||
## Proposed changes
|
||||
### 1. Add a persisted line number mode setting
|
||||
In `app/src/settings/editor.rs`, add a new enum near the existing cursor/Vim editor enums:
|
||||
- `CodeEditorLineNumberMode::Absolute`
|
||||
- `CodeEditorLineNumberMode::Relative`
|
||||
Derive the same traits used by nearby public settings enums: `Clone`, `Copy`, `Debug`, `Default`, `Eq`, `PartialEq`, `Deserialize`, `Serialize`, `Sequence`, `schemars::JsonSchema`, and `settings_value::SettingsValue`. Use `#[schemars(rename_all = "snake_case")]` and make `Absolute` the default.
|
||||
Add a setting to `define_settings_group!(AppEditorSettings, settings: [...])`:
|
||||
- field name: `code_editor_line_number_mode`
|
||||
- type: `CodeEditorLineNumberMode`
|
||||
- default: `CodeEditorLineNumberMode::default()`
|
||||
- supported platforms: `SupportedPlatforms::ALL`
|
||||
- sync: `SyncToCloud::Globally(RespectUserSyncSetting::Yes)`
|
||||
- private: `false`
|
||||
- TOML path: `text_editing.code_editor_line_number_mode`
|
||||
- description: `How line numbers are displayed in code editors.`
|
||||
Add small helpers on the enum:
|
||||
- `dropdown_item_label(&self) -> &'static str` returning `Absolute` and `Relative`
|
||||
- optional `search_terms()` or a widget-level search string that covers `line number relative vim gutter`
|
||||
### 2. Add the settings UI dropdown
|
||||
In `app/src/settings_view/features_page.rs`:
|
||||
1. Import `CodeEditorLineNumberMode` and the generated setting type, likely `CodeEditorLineNumberModeSetting` or the actual generated name from `define_settings_group!`.
|
||||
2. Add `SetCodeEditorLineNumberMode(CodeEditorLineNumberMode)` to `FeaturesPageAction`.
|
||||
3. No new dedicated telemetry event is required for this setting in this iteration; the settings action should use the existing `FeaturesPageAction` telemetry path like other setters.
|
||||
4. Add action handling that writes the setting:
|
||||
- `AppEditorSettings::handle(ctx).update(ctx, |settings, ctx| report_if_error!(settings.code_editor_line_number_mode.set_value(*mode, ctx)))`
|
||||
- Notify after the write so settings UI and open editors repaint.
|
||||
5. Add a `code_editor_line_number_mode_dropdown: ViewHandle<Dropdown<FeaturesPageAction>>` field to `FeaturesPageView`.
|
||||
6. Initialize it with `ctx.add_typed_action_view(Dropdown::new)` and call a helper such as `Self::update_code_editor_line_number_mode_dropdown(...)`.
|
||||
7. Subscribe to `AppEditorSettings::handle(ctx)` changes or update the dropdown in the existing AppEditorSettings subscription if one is added. The selected item must stay in sync when settings change outside the dropdown, such as through `settings.toml`.
|
||||
8. Add a `CodeEditorLineNumberModeWidget` to the Text Editing category in `build_page`, adjacent to `AutocompleteSymbolsWidget` and before/after `VimModeWidget`. This ensures it is not conditional on `vim_mode`.
|
||||
9. Render the widget with `render_dropdown_item`, label it `Code editor line numbers:` or `Line numbering:`, pass the local-only/sync indicator for the generated setting, and point it at `view.code_editor_line_number_mode_dropdown`.
|
||||
### 3. Pass the selected mode into code editor line-number rendering
|
||||
Extend `LineNumberConfig` in `app/src/code/editor/element.rs`:
|
||||
- add `mode: CodeEditorLineNumberMode`
|
||||
- add `active_line_number: Option<LineCount>` or `active_line_index: Option<usize>`
|
||||
In `CodeEditorView::line_number_config` (`app/src/code/editor/view.rs (1041-1239)`):
|
||||
1. Read `let editor_settings = AppEditorSettings::as_ref(ctx)`.
|
||||
2. Set `mode: *editor_settings.code_editor_line_number_mode.value()`.
|
||||
3. Compute the active cursor line from the primary selection head so normal code editors can keep using Relative mode immediately after opening and after temporary focus changes:
|
||||
- Use `self.model.as_ref(ctx).selections(ctx).first().head`.
|
||||
- Convert the head to a buffer point with the code editor buffer.
|
||||
- Convert that row to the same `LineCount` convention used by `model.start_line_index(&**block)`.
|
||||
- Prefer keeping this conversion in a helper on `CodeEditorView` or `CodeEditorModel`, such as `active_cursor_line_for_line_numbers(&self, ctx) -> Option<LineCount>`, to avoid duplicating offset/index assumptions in the wrapper.
|
||||
4. Also pass whether the editor is currently focused into `LineNumberConfig`. Normal code editors should not require focus to display Relative mode, but diff/review editors should still require editor focus before applying relative line numbers.
|
||||
5. Keep returning `None` when `show_line_numbers` is false.
|
||||
### 4. Compute the displayed gutter value per line
|
||||
In `EditorWrapper::gutter_elements` (`app/src/code/editor/element.rs (500-790)`), replace the current absolute-only `current_line` computation with a helper:
|
||||
```
|
||||
fn display_line_number(
|
||||
line_count: LineCount,
|
||||
config: &LineNumberConfig,
|
||||
) -> usize
|
||||
```
|
||||
The helper should implement:
|
||||
- `absolute = line_count.as_usize() + config.starting_line_number.unwrap_or(1)`
|
||||
- `relative = config.active_line_number.map(|active| active.as_usize().abs_diff(line_count.as_usize()))`
|
||||
- Absolute mode returns `absolute`.
|
||||
- Relative mode returns `absolute` when `Some(line_count) == active_line_number`, otherwise `relative.unwrap_or(absolute)`, so editors without an active cursor fall back gracefully.
|
||||
Use the returned value as the `current_line` passed into `render_gutter_element`.
|
||||
Important indexing detail: the current code’s absolute calculation implies `line_count` is zero-based for display purposes. The implementation must verify the active cursor conversion uses the same convention. A small unit test should cover this directly to avoid off-by-one bugs.
|
||||
### 5. Keep non-number gutter elements unchanged
|
||||
Do not display relative numbers for:
|
||||
- temporary removed diff blocks, which currently pass `None` to `render_gutter_element`
|
||||
- hidden-section controls, which use `construct_expand_hidden_section_gutter_element`
|
||||
- surfaces where `line_number_config` is `None`
|
||||
For diff and review editors, preserve absolute numbering unless the editor is focused and Relative is selected. When focused, numbered current-buffer lines across the editor should apply the selected Relative display from the active cursor line so review context outside the changed hunk uses the same relative origin. Normal code editor surfaces without diff status should use the retained primary selection head as the relative origin even if the editor is not currently focused. Diff hunk and comment interactions should continue to use `EditorLineLocation` and `line_range` exactly as they do today; only the text shown inside eligible numbered gutter elements changes.
|
||||
### 6. Width and alignment
|
||||
The existing `GUTTER_WIDTH` is fixed and currently supports absolute numbers plus gutter controls. Do not change it unless testing shows three-digit or larger relative values clip in common cases. Relative mode still shows the active line’s absolute number, so any width calculation must account for both absolute active-line values and relative non-active-line distances. If adjustment is needed, prefer the smallest safe change within `app/src/code/editor/element.rs`, and verify diff/comment buttons still fit.
|
||||
### 7. Do not wire terminal input or notebook editors
|
||||
No changes are needed in `app/src/terminal/input/*`, `app/src/editor/view/mod.rs`, or `app/src/notebooks/editor/view.rs` to render line numbers. The new setting can live in shared editor settings, but only `CodeEditorView` should consume it.
|
||||
## End-to-end flow
|
||||
1. User selects `Relative` from Settings > Text Editing > line numbering.
|
||||
2. `FeaturesPageAction::SetCodeEditorLineNumberMode(Relative)` writes `AppEditorSettings.code_editor_line_number_mode`.
|
||||
3. Open `CodeEditorView` instances observe settings changes and re-render.
|
||||
4. `CodeEditorView::line_number_config` includes the selected mode and active cursor line.
|
||||
5. `EditorWrapper::gutter_elements` computes each visible current-buffer line’s displayed number from the mode.
|
||||
6. Cursor movement emits the existing selection/content events, causing the view to notify and repaint; relative gutter values update on the next render.
|
||||
## Risks and mitigations
|
||||
1. **Off-by-one errors between buffer rows and gutter `LineCount`.** Mitigate with focused tests for cursor on first, middle, and last lines in Relative mode, and with a code comment documenting the chosen convention.
|
||||
2. **Settings UI accidentally scopes the setting under Vim.** Mitigate by implementing a separate Text Editing widget rather than adding it to `VimModeWidget`’s conditional subgroup.
|
||||
3. **Open editors may not repaint when the setting changes.** `CodeEditorView::new` already subscribes to appearance and font settings; add or reuse an `AppEditorSettings` observation/subscription if necessary so setting changes notify code editor views.
|
||||
4. **Diff/review gutter regression.** The implementation touches the shared code editor wrapper used by code review surfaces. Mitigate with manual testing in a diff editor, keeping `EditorLineLocation` unchanged, and verifying inactive diff/review editors keep absolute numbering while focused editors apply Relative numbering across numbered current-buffer lines.
|
||||
5. **Multi-cursor ambiguity.** The product spec defines the primary selection head as the relative origin. Mitigate by using `selections(ctx).first().head`, which matches existing cursor-position helpers.
|
||||
## Testing and validation
|
||||
1. Add or update code editor view/element tests to cover the number calculation helper:
|
||||
- Absolute mode returns the same values as today.
|
||||
- Relative mode returns absolute on the active line and positive distances for lines above/below.
|
||||
- Missing active cursor line falls back to absolute values.
|
||||
- `starting_line_number` still affects absolute and relative active-line display without affecting non-active relative distances.
|
||||
2. Add settings tests, if the existing settings test harness supports them, to verify `text_editing.code_editor_line_number_mode = "relative"` deserializes and invalid values fall back through normal settings validation.
|
||||
3. Manually verify product invariants from `specs/GH9816/product.md`:
|
||||
- Behavior 1-7 in a normal code editor.
|
||||
- Behavior 8-10 with Vim disabled/enabled, visual selections, and soft-wrapped lines.
|
||||
- Behavior 11 with hidden/collapsed code regions.
|
||||
- Behavior 12 in code review/diff views with focused and unfocused sections, hidden sections, and inline comments.
|
||||
- Behavior 15 in terminal input, AI input, and notebook editors.
|
||||
4. Run the repository’s standard formatting/check flow for touched Rust files. At minimum, run targeted Rust tests for settings and code editor modules; if feasible, run the broader app test command used by the repository before the implementation PR.
|
||||
## Parallelization
|
||||
After the settings enum name is settled, implementation can split across two agents:
|
||||
1. Settings/UI agent: adds the `AppEditorSettings` enum/field, settings dropdown, action handling, and settings tests.
|
||||
2. Editor rendering agent: adds `LineNumberConfig` mode/origin support, display calculation helper, code editor tests, and manual diff-editor validation.
|
||||
These streams should coordinate on the exact enum and field names before parallel edits to avoid merge conflicts.
|
||||
## Follow-ups
|
||||
1. Consider Vim command support (`:set number`, `:set relativenumber`) only after the settings-based behavior ships.
|
||||
2. Consider adding line-number mode telemetry only if product analytics need to measure adoption; this spec does not require new telemetry.
|
||||
3. Revisit gutter width if future designs add more gutter affordances or larger inline controls.
|
||||
Reference in New Issue
Block a user