Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customizable/configurable status line #2434

Merged
merged 24 commits into from
Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
da5a886
feat(statusline): add the file type (language id) to the status line
usagi-flow May 6, 2022
bf691a1
refactor(statusline): move the statusline implementation into an own …
usagi-flow May 8, 2022
73d6f34
refactor(statusline): split the statusline implementation into differ…
usagi-flow May 8, 2022
1b50fbb
refactor(statusline): Append elements using a consistent API
usagi-flow May 8, 2022
c01e5aa
refactor(statusline): implement render_diagnostics()
usagi-flow May 8, 2022
90db789
feat(statusline): make the status line configurable
usagi-flow May 8, 2022
5ecd26f
refactor(statusline): make clippy happy
usagi-flow May 9, 2022
82120df
refactor(statusline): avoid intermediate StatusLineObject
usagi-flow Jun 2, 2022
1ccd1de
fix(statusline): avoid rendering the left elements twice
usagi-flow Jun 3, 2022
1e95439
refactor(statusline): make clippy happy again
usagi-flow Jun 3, 2022
f3513a6
refactor(statusline): rename `buffer` into `parts`
usagi-flow Jun 8, 2022
18435dc
refactor(statusline): ensure the match is exhaustive
usagi-flow Jun 21, 2022
07b2845
fix(statusline): avoid an overflow when calculating the maximal cente…
usagi-flow Jun 22, 2022
36535e7
chore(statusline): Describe the statusline configurability in the book
usagi-flow Jul 1, 2022
2dc4d34
chore(statusline): Correct and add documentation
usagi-flow Jul 1, 2022
5cb4e76
refactor(statusline): refactor some code following the code review
usagi-flow Jul 1, 2022
42654fd
chore(statusline): adjust documentation following the config field re…
usagi-flow Jul 1, 2022
aa8c467
revert(statusline): revert regression introduced by c0a1870
usagi-flow Jul 1, 2022
28fa36f
chore(statusline): slight adjustment in the configuration documentation
usagi-flow Jul 4, 2022
8d28f95
feat(statusline): integrate changes from #2676 after rebasing
usagi-flow Jul 5, 2022
fae2348
refactor(statusline): remove the StatusLine struct
usagi-flow Jul 6, 2022
2cb20ff
fix(statusline): restore the configurability of color modes
usagi-flow Jul 6, 2022
dba88ba
fix(statusline): remove the spinner padding
usagi-flow Jul 17, 2022
7ddb5fe
refactor(statusline): remove unnecessary format!()
usagi-flow Jul 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,43 @@ hidden = false
| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` |
| `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` |

### `[editor.statusline]` Section

Allows configuring the statusline at the bottom of the editor.

The configuration distinguishes between three areas of the status line:

`[ ... ... LEFT ... ... | ... ... ... ... CENTER ... ... ... ... | ... ... RIGHT ... ... ]`

Statusline elements can be defined as follows:

```toml
[editor.statusline]
left = ["mode", "spinner"]
center = ["file-name"]
right = ["diagnostics", "selections", "position", "file-encoding", "file-type"]
```

The following elements can be configured:

| Key | Description |
| ------ | ----------- |
| `mode` | The current editor mode (`NOR`/`INS`/`SEL`) |
| `spinner` | A progress spinner indicating LSP activity |
| `file-name` | The path/name of the opened file |
| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
| `file-type` | The type of the opened file |
| `diagnostics` | The number of warnings and/or errors |
| `selections` | The number of active selections |
| `position` | The cursor position |

### `[editor.lsp]` Section

| Key | Description | Default |
| --- | ----------- | ------- |
| `display-messages` | Display LSP progress messages below statusline[^1] | `false` |

[^1]: A progress spinner is always shown in the statusline beside the file path.
[^1]: By default, a progress spinner is shown in the statusline beside the file path.

### `[editor.cursor-shape]` Section

Expand Down
163 changes: 8 additions & 155 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use crate::{
};

use helix_core::{
coords_at_pos, encoding,
graphemes::{
ensure_grapheme_boundary_next_byte, next_grapheme_boundary, prev_grapheme_boundary,
},
Expand All @@ -17,7 +16,7 @@ use helix_core::{
LineEnding, Position, Range, Selection, Transaction,
};
use helix_view::{
document::{Mode, SCRATCH_BUFFER_NAME},
document::Mode,
editor::{CompleteAction, CursorShapeConfig},
graphics::{Color, CursorKind, Modifier, Rect, Style},
input::KeyEvent,
Expand All @@ -29,6 +28,8 @@ use std::borrow::Cow;
use crossterm::event::{Event, MouseButton, MouseEvent, MouseEventKind};
use tui::buffer::Buffer as Surface;

use super::statusline;

pub struct EditorView {
pub keymaps: Keymaps,
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
Expand Down Expand Up @@ -161,7 +162,11 @@ impl EditorView {
.area
.clip_top(view.area.height.saturating_sub(1))
.clip_bottom(1); // -1 from bottom to remove commandline
self.render_statusline(editor, doc, view, statusline_area, surface, is_focused);

let mut context =
statusline::RenderContext::new(editor, doc, view, is_focused, &self.spinners);

statusline::render(&mut context, statusline_area, surface);
}

pub fn render_rulers(
Expand Down Expand Up @@ -730,158 +735,6 @@ impl EditorView {
}
}

pub fn render_statusline(
&self,
editor: &Editor,
doc: &Document,
view: &View,
viewport: Rect,
surface: &mut Surface,
is_focused: bool,
) {
use tui::text::{Span, Spans};

//-------------------------------
// Left side of the status line.
//-------------------------------

let theme = &editor.theme;
let (mode, mode_style) = match doc.mode() {
Mode::Insert => (" INS ", theme.get("ui.statusline.insert")),
Mode::Select => (" SEL ", theme.get("ui.statusline.select")),
Mode::Normal => (" NOR ", theme.get("ui.statusline.normal")),
};
let progress = doc
.language_server()
.and_then(|srv| {
self.spinners
.get(srv.id())
.and_then(|spinner| spinner.frame())
})
.unwrap_or("");

let base_style = if is_focused {
theme.get("ui.statusline")
} else {
theme.get("ui.statusline.inactive")
};
// statusline
surface.set_style(viewport.with_height(1), base_style);
if is_focused {
let color_modes = editor.config().color_modes;
surface.set_string(
viewport.x,
viewport.y,
mode,
if color_modes { mode_style } else { base_style },
);
}
surface.set_string(viewport.x + 5, viewport.y, progress, base_style);

//-------------------------------
// Right side of the status line.
//-------------------------------

let mut right_side_text = Spans::default();

// Compute the individual info strings and add them to `right_side_text`.

// Diagnostics
let diags = doc.diagnostics().iter().fold((0, 0), |mut counts, diag| {
use helix_core::diagnostic::Severity;
match diag.severity {
Some(Severity::Warning) => counts.0 += 1,
Some(Severity::Error) | None => counts.1 += 1,
_ => {}
}
counts
});
let (warnings, errors) = diags;
let warning_style = theme.get("warning");
let error_style = theme.get("error");
for i in 0..2 {
let (count, style) = match i {
0 => (warnings, warning_style),
1 => (errors, error_style),
_ => unreachable!(),
};
if count == 0 {
continue;
}
let style = base_style.patch(style);
right_side_text.0.push(Span::styled("●", style));
right_side_text
.0
.push(Span::styled(format!(" {} ", count), base_style));
}

// Selections
let sels_count = doc.selection(view.id).len();
right_side_text.0.push(Span::styled(
format!(
" {} sel{} ",
sels_count,
if sels_count == 1 { "" } else { "s" }
),
base_style,
));

// Position
let pos = coords_at_pos(
doc.text().slice(..),
doc.selection(view.id)
.primary()
.cursor(doc.text().slice(..)),
);
right_side_text.0.push(Span::styled(
format!(" {}:{} ", pos.row + 1, pos.col + 1), // Convert to 1-indexing.
base_style,
));

let enc = doc.encoding();
if enc != encoding::UTF_8 {
right_side_text
.0
.push(Span::styled(format!(" {} ", enc.name()), base_style));
}

// Render to the statusline.
surface.set_spans(
viewport.x
+ viewport
.width
.saturating_sub(right_side_text.width() as u16),
viewport.y,
&right_side_text,
right_side_text.width() as u16,
);

//-------------------------------
// Middle / File path / Title
//-------------------------------
let title = {
let rel_path = doc.relative_path();
let path = rel_path
.as_ref()
.map(|p| p.to_string_lossy())
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" })
};

surface.set_string_truncated(
viewport.x + 8, // 8: 1 space + 3 char mode string + 1 space + 1 spinner + 1 space
viewport.y,
&title,
viewport
.width
.saturating_sub(6)
.saturating_sub(right_side_text.width() as u16 + 1) as usize, // "+ 1": a space between the title and the selection info
|_| base_style,
true,
true,
);
}

/// Handle events by looking them up in `self.keymaps`. Returns None
/// if event was handled (a command was executed or a subkeymap was
/// activated). Only KeymapResult::{NotFound, Cancelled} is returned
Expand Down
1 change: 1 addition & 0 deletions helix-term/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod picker;
mod popup;
mod prompt;
mod spinner;
mod statusline;
mod text;

pub use completion::Completion;
Expand Down
Loading