feat: add config option to make statusline unobtrusive

This commit is contained in:
Nikita Revenco 2024-11-07 12:20:41 +00:00
parent b53dafe326
commit d9727082b3
12 changed files with 242 additions and 124 deletions

View File

@ -105,6 +105,7 @@ pub fn new(args: Args, config: Config, lang_loader: syntax::Loader) -> Result<Se
use helix_view::editor::Action;
let unobtrusive_statusline = config.editor.statusline.unobtrusive;
let mut theme_parent_dirs = vec![helix_loader::config_dir()];
theme_parent_dirs.extend(helix_loader::runtime_dirs().iter().cloned());
let theme_loader = std::sync::Arc::new(theme::Loader::new(&theme_parent_dirs));
@ -221,7 +222,7 @@ pub fn new(args: Args, config: Config, lang_loader: syntax::Loader) -> Result<Se
// align the view to center after all files are loaded,
// does not affect views without pos since it is at the top
let (view, doc) = current!(editor);
align_view(doc, view, Align::Center);
align_view(doc, view, Align::Center, unobtrusive_statusline);
}
} else {
editor.new_file(Action::VerticalSplit);
@ -396,10 +397,12 @@ pub fn handle_config_events(&mut self, config_event: ConfigEvent) {
self.editor.refresh_config();
// reset view position in case softwrap was enabled/disabled
let scrolloff = self.editor.config().scrolloff;
let config = self.editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
for (view, _) in self.editor.tree.views() {
let doc = doc_mut!(self.editor, &view.doc);
view.ensure_cursor_in_view(doc, scrolloff);
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline);
}
}
@ -1171,6 +1174,8 @@ fn handle_show_document(
}
};
let config = &self.editor.config();
let unobtrusive_statusline = config.statusline.unobtrusive;
let doc = doc_mut!(self.editor, &doc_id);
if let Some(range) = selection {
// TODO: convert inside server
@ -1181,7 +1186,7 @@ fn handle_show_document(
// (for example start of the function).
doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
if action.align_view(view, doc.id()) {
align_view(doc, view, Align::Center);
align_view(doc, view, Align::Center, unobtrusive_statusline);
}
} else {
log::warn!("lsp position out of bounds - {:?}", range);

View File

@ -676,9 +676,12 @@ fn no_op(_cx: &mut Context) {}
fn move_impl(cx: &mut Context, move_fn: MoveFn, dir: Direction, behaviour: Movement) {
let count = cx.count();
let config = cx.editor.config();
let (view, doc) = current!(cx.editor);
let unobtrusive_statusline = config.statusline.unobtrusive;
let text = doc.text().slice(..);
let text_fmt = doc.text_format(view.inner_area(doc).width, None);
let text_fmt = doc.text_format(view.inner_area(doc, unobtrusive_statusline).width, None);
let mut annotations = view.text_annotations(doc, None);
let selection = doc.selection(view.id).clone().transform(|range| {
@ -1079,10 +1082,11 @@ fn align_selections(cx: &mut Context) {
fn goto_window(cx: &mut Context, align: Align) {
let count = cx.count() - 1;
let config = cx.editor.config();
let unobtrusive_statusline = config.statusline.unobtrusive;
let (view, doc) = current!(cx.editor);
let view_offset = doc.view_offset(view.id);
let height = view.inner_height();
let height = view.inner_height(unobtrusive_statusline);
// respect user given count if any
// - 1 so we have at least one gap in the middle.
@ -1090,7 +1094,7 @@ fn goto_window(cx: &mut Context, align: Align) {
// as we type
let scrolloff = config.scrolloff.min(height.saturating_sub(1) / 2);
let last_visual_line = view.last_visual_line(doc);
let last_visual_line = view.last_visual_line(doc, unobtrusive_statusline);
let visual_line = match align {
Align::Top => view_offset.vertical_offset + scrolloff + count,
@ -1752,7 +1756,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor
let text = doc.text().slice(..);
let cursor = range.cursor(text);
let height = view.inner_height();
let height = view.inner_height(config.statusline.unobtrusive);
let scrolloff = config.scrolloff.min(height.saturating_sub(1) / 2);
let offset = match direction {
@ -1761,7 +1765,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor
};
let doc_text = doc.text().slice(..);
let viewport = view.inner_area(doc);
let viewport = view.inner_area(doc, config.statusline.unobtrusive);
let text_fmt = doc.text_format(viewport.width, None);
(view_offset.anchor, view_offset.vertical_offset) = char_idx_at_visual_offset(
doc_text,
@ -1854,49 +1858,49 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor
fn page_up(cx: &mut Context) {
let view = view!(cx.editor);
let offset = view.inner_height();
let offset = view.inner_height(cx.editor.config().statusline.unobtrusive);
scroll(cx, offset, Direction::Backward, false);
}
fn page_down(cx: &mut Context) {
let view = view!(cx.editor);
let offset = view.inner_height();
let offset = view.inner_height(cx.editor.config().statusline.unobtrusive);
scroll(cx, offset, Direction::Forward, false);
}
fn half_page_up(cx: &mut Context) {
let view = view!(cx.editor);
let offset = view.inner_height() / 2;
let offset = view.inner_height(cx.editor.config().statusline.unobtrusive) / 2;
scroll(cx, offset, Direction::Backward, false);
}
fn half_page_down(cx: &mut Context) {
let view = view!(cx.editor);
let offset = view.inner_height() / 2;
let offset = view.inner_height(cx.editor.config().statusline.unobtrusive) / 2;
scroll(cx, offset, Direction::Forward, false);
}
fn page_cursor_up(cx: &mut Context) {
let view = view!(cx.editor);
let offset = view.inner_height();
let offset = view.inner_height(cx.editor.config().statusline.unobtrusive);
scroll(cx, offset, Direction::Backward, true);
}
fn page_cursor_down(cx: &mut Context) {
let view = view!(cx.editor);
let offset = view.inner_height();
let offset = view.inner_height(cx.editor.config().statusline.unobtrusive);
scroll(cx, offset, Direction::Forward, true);
}
fn page_cursor_half_up(cx: &mut Context) {
let view = view!(cx.editor);
let offset = view.inner_height() / 2;
let offset = view.inner_height(cx.editor.config().statusline.unobtrusive) / 2;
scroll(cx, offset, Direction::Backward, true);
}
fn page_cursor_half_down(cx: &mut Context) {
let view = view!(cx.editor);
let offset = view.inner_height() / 2;
let offset = view.inner_height(cx.editor.config().statusline.unobtrusive) / 2;
scroll(cx, offset, Direction::Forward, true);
}
@ -2073,6 +2077,7 @@ fn search_impl(
scrolloff: usize,
wrap_around: bool,
show_warnings: bool,
unobtrusive_statusline: bool,
) {
let (view, doc) = current!(editor);
let text = doc.text().slice(..);
@ -2143,7 +2148,7 @@ fn search_impl(
};
doc.set_selection(view.id, selection);
view.ensure_cursor_in_view_center(doc, scrolloff);
view.ensure_cursor_in_view_center(doc, scrolloff, unobtrusive_statusline);
};
}
@ -2168,6 +2173,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
let reg = cx.register.unwrap_or('/');
let config = cx.editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let wrap_around = config.search.wrap_around;
let movement = if cx.editor.mode() == Mode::Select {
Movement::Extend
@ -2203,6 +2209,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
scrolloff,
wrap_around,
false,
unobtrusive_statusline,
);
},
);
@ -2215,6 +2222,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
.unwrap_or(cx.editor.registers.last_search_register);
let config = cx.editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
if let Some(query) = cx.editor.registers.first(register, cx.editor) {
let search_config = &config.search;
let case_insensitive = if search_config.smart_case {
@ -2240,6 +2248,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
scrolloff,
wrap_around,
true,
unobtrusive_statusline,
);
}
} else {
@ -2498,6 +2507,7 @@ struct GlobalSearchConfig {
[],
config,
move |cx, FileResult { path, line_num, .. }, action| {
let config = cx.editor.config();
let doc = match cx.editor.open(path, action) {
Ok(id) => doc_mut!(cx.editor, &id),
Err(e) => {
@ -2508,6 +2518,7 @@ struct GlobalSearchConfig {
};
let line_num = *line_num;
let unobtrusive_statusline = config.statusline.unobtrusive;
let view = view_mut!(cx.editor);
let text = doc.text();
if line_num >= text.len_lines() {
@ -2521,7 +2532,7 @@ struct GlobalSearchConfig {
doc.set_selection(view.id, Selection::single(start, end));
if action.align_view(view, doc.id()) {
align_view(doc, view, Align::Center);
align_view(doc, view, Align::Center, unobtrusive_statusline);
}
},
)
@ -3095,7 +3106,7 @@ struct JumpMeta {
let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
doc.set_selection(view.id, meta.selection.clone());
if action.align_view(view, doc.id()) {
view.ensure_cursor_in_view_center(doc, config.scrolloff);
view.ensure_cursor_in_view_center(doc, config.scrolloff, config.statusline.unobtrusive);
}
},
)
@ -3271,7 +3282,7 @@ pub fn command_palette(cx: &mut Context) {
let view = view_mut!(ctx.editor, focus);
let doc = doc_mut!(ctx.editor, &view.doc);
view.ensure_cursor_in_view(doc, config.scrolloff);
view.ensure_cursor_in_view(doc, config.scrolloff, config.statusline.unobtrusive);
if mode != Mode::Insert {
doc.append_changes_to_history(view);
@ -3397,7 +3408,9 @@ async fn make_format_callback(
return;
}
let scrolloff = editor.config().scrolloff;
let config = editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let doc = doc_mut!(editor, &doc_id);
let view = view_mut!(editor, view_id);
@ -3406,7 +3419,7 @@ async fn make_format_callback(
doc.apply(&format, view.id);
doc.append_changes_to_history(view);
doc.detect_indent_and_line_ending();
view.ensure_cursor_in_view(doc, scrolloff);
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline);
} else {
log::info!("discarded formatting changes because the document changed");
}
@ -4451,7 +4464,10 @@ fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
return;
};
let values: Vec<_> = values.map(|value| value.to_string()).collect();
let scrolloff = editor.config().scrolloff;
let config = editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let (view, doc) = current!(editor);
let repeat = std::iter::repeat(
@ -4475,7 +4491,7 @@ fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
view.ensure_cursor_in_view(doc, scrolloff);
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline);
}
fn replace_selections_with_clipboard(cx: &mut Context) {
@ -5210,7 +5226,7 @@ fn jump_forward(cx: &mut Context) {
doc.set_selection(view.id, selection);
// Document we switch to might not have been opened in the view before
doc.ensure_view_init(view.id);
view.ensure_cursor_in_view_center(doc, config.scrolloff);
view.ensure_cursor_in_view_center(doc, config.scrolloff, config.statusline.unobtrusive);
};
}
@ -5232,7 +5248,7 @@ fn jump_backward(cx: &mut Context) {
doc.set_selection(view.id, selection);
// Document we switch to might not have been opened in the view before
doc.ensure_view_init(view.id);
view.ensure_cursor_in_view_center(doc, config.scrolloff);
view.ensure_cursor_in_view_center(doc, config.scrolloff, config.statusline.unobtrusive);
};
}
@ -5374,21 +5390,25 @@ fn insert_register(cx: &mut Context) {
}
fn align_view_top(cx: &mut Context) {
let unobtrusive_statusline = cx.editor.config().statusline.unobtrusive;
let (view, doc) = current!(cx.editor);
align_view(doc, view, Align::Top);
align_view(doc, view, Align::Top, unobtrusive_statusline);
}
fn align_view_center(cx: &mut Context) {
let unobtrusive_statusline = cx.editor.config().statusline.unobtrusive;
let (view, doc) = current!(cx.editor);
align_view(doc, view, Align::Center);
align_view(doc, view, Align::Center, unobtrusive_statusline);
}
fn align_view_bottom(cx: &mut Context) {
let unobtrusive_statusline = cx.editor.config().statusline.unobtrusive;
let (view, doc) = current!(cx.editor);
align_view(doc, view, Align::Bottom);
align_view(doc, view, Align::Bottom, unobtrusive_statusline);
}
fn align_view_middle(cx: &mut Context) {
let config = cx.editor.config();
let (view, doc) = current!(cx.editor);
let inner_width = view.inner_width(doc);
let text_fmt = doc.text_format(inner_width, None);
@ -5408,9 +5428,10 @@ fn align_view_middle(cx: &mut Context) {
.0;
let mut offset = doc.view_offset(view.id);
let unobtrusive_statusline = config.statusline.unobtrusive;
offset.horizontal_offset = pos
.col
.saturating_sub((view.inner_area(doc).width as usize) / 2);
.saturating_sub((view.inner_area(doc, unobtrusive_statusline).width as usize) / 2);
doc.set_view_offset(view.id, offset);
}
@ -5975,7 +5996,7 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
// after replace cursor may be out of bounds, do this to
// make sure cursor is in view and update scroll as well
view.ensure_cursor_in_view(doc, config.scrolloff);
view.ensure_cursor_in_view(doc, config.scrolloff, config.statusline.unobtrusive);
}
fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
@ -6282,7 +6303,9 @@ fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
fn jump_to_word(cx: &mut Context, behaviour: Movement) {
// Calculate the jump candidates: ranges for any visible words with two or
// more characters.
let alphabet = &cx.editor.config().jump_label_alphabet;
let config = cx.editor.config();
let alphabet = &config.jump_label_alphabet;
let unobtrusive_statusline = config.statusline.unobtrusive;
let jump_label_limit = alphabet.len() * alphabet.len();
let mut words = Vec::with_capacity(jump_label_limit);
let (view, doc) = current_ref!(cx.editor);
@ -6291,7 +6314,7 @@ fn jump_to_word(cx: &mut Context, behaviour: Movement) {
// This is not necessarily exact if there is virtual text like soft wrap.
// It's ok though because the extra jump labels will not be rendered.
let start = text.line_to_char(text.char_to_line(doc.view_offset(view.id).anchor));
let end = text.line_to_char(view.estimate_last_doc_line(doc) + 1);
let end = text.line_to_char(view.estimate_last_doc_line(doc, unobtrusive_statusline) + 1);
let primary_selection = doc.selection(view.id).primary();
let cursor = primary_selection.cursor(text);

View File

@ -134,6 +134,7 @@ fn jump_to_position(
offset_encoding: OffsetEncoding,
action: Action,
) {
let config = &editor.config();
let doc = match editor.open(path, action) {
Ok(id) => doc_mut!(editor, &id),
Err(err) => {
@ -154,8 +155,9 @@ fn jump_to_position(
// we flip the range so that the cursor sits on the start of the symbol
// (for example start of the function).
doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
let unobtrusive_statusline = config.statusline.unobtrusive;
if action.align_view(view, doc.id()) {
align_view(doc, view, Align::Center);
align_view(doc, view, Align::Center, unobtrusive_statusline);
}
}
@ -1239,7 +1241,9 @@ pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
}
pub fn compute_inlay_hints_for_all_views(editor: &mut Editor, jobs: &mut crate::job::Jobs) {
if !editor.config().lsp.display_inlay_hints {
let config = editor.config();
let unobtrusive_statusline = config.statusline.unobtrusive;
if !config.lsp.display_inlay_hints {
return;
}
@ -1248,7 +1252,7 @@ pub fn compute_inlay_hints_for_all_views(editor: &mut Editor, jobs: &mut crate::
Some(doc) => doc,
None => continue,
};
if let Some(callback) = compute_inlay_hints_for_view(view, doc) {
if let Some(callback) = compute_inlay_hints_for_view(view, doc, unobtrusive_statusline) {
jobs.callback(callback);
}
}
@ -1257,6 +1261,7 @@ pub fn compute_inlay_hints_for_all_views(editor: &mut Editor, jobs: &mut crate::
fn compute_inlay_hints_for_view(
view: &View,
doc: &Document,
unobtrusive_statusline: bool,
) -> Option<std::pin::Pin<Box<impl Future<Output = Result<crate::job::Callback, anyhow::Error>>>>> {
let view_id = view.id;
let doc_id = view.doc;
@ -1272,7 +1277,7 @@ fn compute_inlay_hints_for_view(
// will not show half the view with hints and half without while still being faster
// than computing all the hints for the full file (which could be dozens of time
// longer than the view is).
let view_height = view.inner_height();
let view_height = view.inner_height(unobtrusive_statusline);
let first_visible_line =
doc_text.char_to_line(doc.view_offset(view_id).anchor.min(doc_text.len_chars()));
let first_line = first_visible_line.saturating_sub(view_height);

View File

@ -127,12 +127,13 @@ fn open(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) ->
cx.jobs.callback(callback);
} else {
// Otherwise, just open the file
let unobtrusive_statusline = cx.editor.config().statusline.unobtrusive;
let _ = cx.editor.open(&path, Action::Replace)?;
let (view, doc) = current!(cx.editor);
let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true));
doc.set_selection(view.id, pos);
// does not affect opening a buffer without pos
align_view(doc, view, Align::Center);
align_view(doc, view, Align::Center, unobtrusive_statusline);
}
}
Ok(())
@ -1277,10 +1278,12 @@ fn reload(
return Ok(());
}
let scrolloff = cx.editor.config().scrolloff;
let config = cx.editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let (view, doc) = current!(cx.editor);
doc.reload(view, &cx.editor.diff_providers).map(|_| {
view.ensure_cursor_in_view(doc, scrolloff);
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline);
})?;
if let Some(path) = doc.path() {
cx.editor
@ -1300,7 +1303,9 @@ fn reload_all(
return Ok(());
}
let scrolloff = cx.editor.config().scrolloff;
let config = cx.editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let view_id = view!(cx.editor).id;
let docs_view_ids: Vec<(DocumentId, Vec<ViewId>)> = cx
@ -1342,7 +1347,7 @@ fn reload_all(
for view_id in view_ids {
let view = view_mut!(cx.editor, view_id);
if view.doc.eq(&doc_id) {
view.ensure_cursor_in_view(doc, scrolloff);
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline);
}
}
}
@ -1578,6 +1583,8 @@ fn find_highlight_at_cursor(
) -> Option<helix_core::syntax::Highlight> {
use helix_core::syntax::HighlightEvent;
let config = &cx.editor.config();
let unobtrusive_statusline = config.statusline.unobtrusive;
let (view, doc) = current!(cx.editor);
let syntax = doc.syntax()?;
let text = doc.text().slice(..);
@ -1590,7 +1597,7 @@ fn find_highlight_at_cursor(
let row = text.char_to_line(doc.view_offset(view.id).anchor.min(text.len_chars()));
// Saturating subs to make it inclusive zero indexing.
let last_line = text.len_lines().saturating_sub(1);
let height = view.inner_area(doc).height;
let height = view.inner_area(doc, unobtrusive_statusline).height;
let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
let start = text.line_to_byte(row.min(last_line));
let end = text.line_to_byte(last_visible_line + 1);
@ -1795,11 +1802,14 @@ fn tutor(
fn abort_goto_line_number_preview(cx: &mut compositor::Context) {
if let Some(last_selection) = cx.editor.last_selection.take() {
let scrolloff = cx.editor.config().scrolloff;
let config = cx.editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let (view, doc) = current!(cx.editor);
doc.set_selection(view.id, last_selection);
view.ensure_cursor_in_view(doc, scrolloff);
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline);
}
}
@ -1812,12 +1822,14 @@ fn update_goto_line_number_preview(
doc.selection(view.id).clone()
});
let scrolloff = cx.editor.config().scrolloff;
let config = cx.editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let line = args[0].parse::<usize>()?;
goto_line_without_jumplist(cx.editor, NonZeroUsize::new(line));
let (view, doc) = current!(cx.editor);
view.ensure_cursor_in_view(doc, scrolloff);
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline);
Ok(())
}
@ -2062,7 +2074,9 @@ fn sort_impl(
_args: &[Cow<str>],
reverse: bool,
) -> anyhow::Result<()> {
let scrolloff = cx.editor.config().scrolloff;
let config = cx.editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
@ -2088,7 +2102,7 @@ fn sort_impl(
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
view.ensure_cursor_in_view(doc, scrolloff);
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline);
Ok(())
}
@ -2102,7 +2116,9 @@ fn reflow(
return Ok(());
}
let scrolloff = cx.editor.config().scrolloff;
let config = cx.editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let cfg_text_width: usize = cx.editor.config().text_width;
let (view, doc) = current!(cx.editor);
@ -2129,7 +2145,7 @@ fn reflow(
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
view.ensure_cursor_in_view(doc, scrolloff);
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline);
Ok(())
}
@ -2329,7 +2345,9 @@ fn reset_diff_change(
ensure!(args.is_empty(), ":reset-diff-change takes no arguments");
let editor = &mut cx.editor;
let scrolloff = editor.config().scrolloff;
let config = editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let (view, doc) = current!(editor);
let Some(handle) = doc.diff_handle() else {
@ -2363,7 +2381,7 @@ fn reset_diff_change(
drop(diff); // make borrow check happy
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
view.ensure_cursor_in_view(doc, scrolloff);
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline);
cx.editor.set_status(format!(
"Reset {changes} change{}",
if changes == 1 { "" } else { "s" }
@ -2492,7 +2510,9 @@ fn read(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) ->
return Ok(());
}
let scrolloff = cx.editor.config().scrolloff;
let config = cx.editor.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let (view, doc) = current!(cx.editor);
ensure!(!args.is_empty(), "file name is expected");
@ -2515,7 +2535,7 @@ fn read(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) ->
let transaction = Transaction::insert(doc.text(), selection, contents);
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
view.ensure_cursor_in_view(doc, scrolloff);
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline);
Ok(())
}

View File

@ -88,10 +88,11 @@ pub fn render_view(
surface: &mut Surface,
is_focused: bool,
) {
let inner = view.inner_area(doc);
let area = view.area;
let theme = &editor.theme;
let config = editor.config();
let unobtrusive_statusline = config.statusline.unobtrusive;
let inner = view.inner_area(doc, unobtrusive_statusline);
let view_offset = doc.view_offset(view.id);
@ -103,7 +104,7 @@ pub fn render_view(
}
if is_focused && config.cursorcolumn {
Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations);
Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations, config.statusline.unobtrusive);
}
// Set DAP highlights, if needed.
@ -240,7 +241,7 @@ pub fn render_view(
let mut context =
statusline::RenderContext::new(editor, doc, view, is_focused, &self.spinners);
statusline::render(&mut context, statusline_area, surface);
statusline::render(&mut context, statusline_area, surface, unobtrusive_statusline);
}
pub fn render_rulers(
@ -812,6 +813,7 @@ pub fn highlight_cursorcolumn(
theme: &Theme,
viewport: Rect,
text_annotations: &TextAnnotations,
unobtrusive_statusline: bool,
) {
let text = doc.text().slice(..);
@ -826,7 +828,7 @@ pub fn highlight_cursorcolumn(
.or_else(|| theme.try_get_exact("ui.cursorcolumn"))
.unwrap_or_else(|| theme.get("ui.cursorline.secondary"));
let inner_area = view.inner_area(doc);
let inner_area = view.inner_area(doc, unobtrusive_statusline);
let selection = doc.selection(view.id);
let view_offset = doc.view_offset(view.id);
@ -1122,6 +1124,7 @@ fn handle_mouse_event(
row,
column,
ignore_virtual_text,
editor.config().statusline.unobtrusive,
)
.map(|pos| (pos, view.id))
})
@ -1193,7 +1196,7 @@ fn handle_mouse_event(
MouseEventKind::Drag(MouseButton::Left) => {
let (view, doc) = current!(cxt.editor);
let pos = match view.pos_at_screen_coords(doc, row, column, true) {
let pos = match view.pos_at_screen_coords(doc, row, column, true, config.statusline.unobtrusive) {
Some(pos) => pos,
None => return EventResult::Ignored(None),
};
@ -1340,7 +1343,7 @@ fn handle_event(
let config = cx.editor.config();
let mode = cx.editor.mode();
let (view, doc) = current!(cx.editor);
view.ensure_cursor_in_view(doc, config.scrolloff);
view.ensure_cursor_in_view(doc, config.scrolloff, config.statusline.unobtrusive);
// Store a history state if not in insert mode. Otherwise wait till we exit insert
// to include any edits to the paste in the history state.
@ -1434,7 +1437,7 @@ fn handle_event(
let mode = cx.editor.mode();
let (view, doc) = current!(cx.editor);
view.ensure_cursor_in_view(doc, config.scrolloff);
view.ensure_cursor_in_view(doc, config.scrolloff, config.statusline.unobtrusive);
// Store a history state if not in insert mode. This also takes care of
// committing changes when leaving insert mode.

View File

@ -131,7 +131,7 @@ pub fn raw_regex_prompt(
fun(cx, regex, input, event);
let (view, doc) = current!(cx.editor);
view.ensure_cursor_in_view(doc, config.scrolloff);
view.ensure_cursor_in_view(doc, config.scrolloff, config.statusline.unobtrusive);
}
Err(err) => {
let (view, doc) = current!(cx.editor);

View File

@ -49,14 +49,16 @@ pub struct RenderBuffer<'a> {
pub right: Spans<'a>,
}
pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface) {
pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface, unobtrusive_statusline: bool) {
let base_style = if context.focused {
context.editor.theme.get("ui.statusline")
} else {
context.editor.theme.get("ui.statusline.inactive")
};
surface.set_style(viewport.with_height(1), base_style);
if !unobtrusive_statusline {
surface.set_style(viewport.with_height(1), base_style);
}
let write_left = |context: &mut RenderContext, text, style| {
append(&mut context.parts.left, text, &base_style, style)

View File

@ -464,6 +464,7 @@ pub struct SearchConfig {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct StatusLineConfig {
pub unobtrusive: bool,
pub left: Vec<StatusLineElement>,
pub center: Vec<StatusLineElement>,
pub right: Vec<StatusLineElement>,
@ -476,6 +477,7 @@ fn default() -> Self {
use StatusLineElement as E;
Self {
unobtrusive: false,
left: vec![
E::Mode,
E::Spinner,
@ -1538,12 +1540,14 @@ fn _refresh(&mut self) {
let doc = doc_mut!(self, &view.doc);
view.sync_changes(doc);
view.gutters = config.gutters.clone();
view.ensure_cursor_in_view(doc, config.scrolloff)
view.ensure_cursor_in_view(doc, config.scrolloff, config.statusline.unobtrusive)
}
}
fn replace_document_in_view(&mut self, current_view: ViewId, doc_id: DocumentId) {
let scrolloff = self.config().scrolloff;
let config = self.config();
let scrolloff = config.scrolloff;
let unobtrusive_statusline = config.statusline.unobtrusive;
let view = self.tree.get_mut(current_view);
view.doc = doc_id;
@ -1553,7 +1557,7 @@ fn replace_document_in_view(&mut self, current_view: ViewId, doc_id: DocumentId)
view.sync_changes(doc);
doc.mark_as_focused();
view.ensure_cursor_in_view(doc, scrolloff)
view.ensure_cursor_in_view(doc, scrolloff, unobtrusive_statusline)
}
pub fn switch(&mut self, id: DocumentId, action: Action) {
@ -1915,7 +1919,7 @@ pub fn ensure_cursor_in_view(&mut self, id: ViewId) {
let config = self.config();
let view = self.tree.get(id);
let doc = doc_mut!(self, &view.doc);
view.ensure_cursor_in_view(doc, config.scrolloff)
view.ensure_cursor_in_view(doc, config.scrolloff, config.statusline.unobtrusive)
}
#[inline]
@ -2005,8 +2009,9 @@ pub fn doc_diagnostics_with_filter<'a>(
pub fn cursor(&self) -> (Option<Position>, CursorKind) {
let config = self.config();
let (view, doc) = current_ref!(self);
if let Some(mut pos) = self.cursor_cache.get(view, doc) {
let inner = view.inner_area(doc);
let unobtrusive_statusline = config.statusline.unobtrusive;
if let Some(mut pos) = self.cursor_cache.get(view, doc, unobtrusive_statusline) {
let inner = view.inner_area(doc, unobtrusive_statusline);
pos.col += inner.x as usize;
pos.row += inner.y as usize;
let cursorkind = config.cursor_shape.from_mode(self.mode);
@ -2205,14 +2210,14 @@ fn inserted_a_new_blank_line(changes: &[Operation], pos: usize, line_end_pos: us
pub struct CursorCache(Cell<Option<Option<Position>>>);
impl CursorCache {
pub fn get(&self, view: &View, doc: &Document) -> Option<Position> {
pub fn get(&self, view: &View, doc: &Document, unobtrusive_statusline: bool) -> Option<Position> {
if let Some(pos) = self.0.get() {
return pos;
}
let text = doc.text().slice(..);
let cursor = doc.selection(view.id).primary().cursor(text);
let res = view.screen_coords_at_pos(doc, text, cursor);
let res = view.screen_coords_at_pos(doc, text, cursor, unobtrusive_statusline);
self.set(res);
res
}

View File

@ -147,8 +147,10 @@ pub fn line_numbers<'doc>(
) -> GutterFn<'doc> {
let text = doc.text().slice(..);
let width = line_numbers_width(view, doc);
let config = editor.config();
let unobtrusive_statusline = config.statusline.unobtrusive;
let last_line_in_view = view.estimate_last_doc_line(doc);
let last_line_in_view = view.estimate_last_doc_line(doc, unobtrusive_statusline);
// Whether to draw the line number for the last line of the
// document or not. We only draw it if it's not an empty line.
@ -161,7 +163,7 @@ pub fn line_numbers<'doc>(
.text()
.char_to_line(doc.selection(view.id).primary().cursor(text));
let line_number = editor.config().line_number;
let line_number = config.line_number;
let mode = editor.mode;
Box::new(

View File

@ -68,6 +68,7 @@ pub fn jump_to_stack_frame(editor: &mut Editor, frame: &helix_dap::StackFrame) {
return;
}
let unobtrusive_statusline = editor.config().statusline.unobtrusive;
let (view, doc) = current!(editor);
let text_end = doc.text().len_chars().saturating_sub(1);
@ -79,7 +80,7 @@ pub fn jump_to_stack_frame(editor: &mut Editor, frame: &helix_dap::StackFrame) {
let selection = Selection::single(start.min(text_end), end.min(text_end));
doc.set_selection(view.id, selection);
align_view(doc, view, Align::Center);
align_view(doc, view, Align::Center, unobtrusive_statusline);
}
pub fn breakpoints_changed(

View File

@ -47,10 +47,10 @@ pub enum Align {
Bottom,
}
pub fn align_view(doc: &mut Document, view: &View, align: Align) {
pub fn align_view(doc: &mut Document, view: &View, align: Align, unobtrusive_statusline: bool) {
let doc_text = doc.text().slice(..);
let cursor = doc.selection(view.id).primary().cursor(doc_text);
let viewport = view.inner_area(doc);
let viewport = view.inner_area(doc, unobtrusive_statusline);
let last_line_height = viewport.height.saturating_sub(1);
let mut view_offset = doc.view_offset(view.id);

View File

@ -190,12 +190,32 @@ pub fn add_to_history(&mut self, id: DocumentId) {
self.docs_access_history.push(id);
}
pub fn inner_area(&self, doc: &Document) -> Rect {
self.area.clip_left(self.gutter_offset(doc)).clip_bottom(1) // -1 for statusline
/// Accounts for height of statusline
pub fn inner_area(&self, doc: &Document, unobtrusive_statusline: bool) -> Rect {
if unobtrusive_statusline {
self.area.clip_left(self.gutter_offset(doc))
} else {
self.area.clip_left(self.gutter_offset(doc)).clip_bottom(1) // -1 for statusline
}
}
pub fn inner_height(&self) -> usize {
self.area.clip_bottom(1).height.into() // -1 for statusline
/// Accounts for height of statusline
pub fn inner_height(&self, unobtrusive_statusline: bool) -> usize {
if unobtrusive_statusline {
self.area.height.into()
} else {
self.area.clip_bottom(1).height.into() // -1 for statusline
}
}
/// Does not account for height of statusline
pub fn inner_area_raw(&self, doc: &Document) -> Rect {
self.area.clip_left(self.gutter_offset(doc))
}
/// Does not account for height of statusline
pub fn inner_height_raw(&self) -> usize {
self.area.height.into()
}
pub fn inner_width(&self, doc: &Document) -> u16 {
@ -225,18 +245,20 @@ pub fn offset_coords_to_in_view(
&self,
doc: &Document,
scrolloff: usize,
unobtrusive_statusline: bool,
) -> Option<ViewPosition> {
self.offset_coords_to_in_view_center::<false>(doc, scrolloff)
self.offset_coords_to_in_view_center::<false>(doc, scrolloff, unobtrusive_statusline)
}
pub fn offset_coords_to_in_view_center<const CENTERING: bool>(
&self,
doc: &Document,
scrolloff: usize,
unobtrusive_statusline: bool,
) -> Option<ViewPosition> {
let view_offset = doc.get_view_offset(self.id)?;
let doc_text = doc.text().slice(..);
let viewport = self.inner_area(doc);
let viewport = self.inner_area(doc, unobtrusive_statusline);
let vertical_viewport_end = view_offset.vertical_offset + viewport.height as usize;
let text_fmt = doc.text_format(viewport.width, None);
let annotations = self.text_annotations(doc, None);
@ -333,22 +355,22 @@ pub fn offset_coords_to_in_view_center<const CENTERING: bool>(
Some(offset)
}
pub fn ensure_cursor_in_view(&self, doc: &mut Document, scrolloff: usize) {
if let Some(offset) = self.offset_coords_to_in_view_center::<false>(doc, scrolloff) {
pub fn ensure_cursor_in_view(&self, doc: &mut Document, scrolloff: usize, unobtrusive_statusline: bool) {
if let Some(offset) = self.offset_coords_to_in_view_center::<false>(doc, scrolloff, unobtrusive_statusline) {
doc.set_view_offset(self.id, offset);
}
}
pub fn ensure_cursor_in_view_center(&self, doc: &mut Document, scrolloff: usize) {
if let Some(offset) = self.offset_coords_to_in_view_center::<true>(doc, scrolloff) {
pub fn ensure_cursor_in_view_center(&self, doc: &mut Document, scrolloff: usize, unobtrusive_statusline: bool) {
if let Some(offset) = self.offset_coords_to_in_view_center::<true>(doc, scrolloff, unobtrusive_statusline) {
doc.set_view_offset(self.id, offset);
} else {
align_view(doc, self, Align::Center);
align_view(doc, self, Align::Center, unobtrusive_statusline);
}
}
pub fn is_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) -> bool {
self.offset_coords_to_in_view(doc, scrolloff).is_none()
pub fn is_cursor_in_view(&mut self, doc: &Document, scrolloff: usize, unobtrusive_statusline: bool) -> bool {
self.offset_coords_to_in_view(doc, scrolloff, unobtrusive_statusline).is_none()
}
/// Estimates the last visible document line on screen.
@ -357,20 +379,20 @@ pub fn is_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) -> bool {
/// The actual last visible line may be smaller if softwrapping occurs
/// or virtual text lines are visible
#[inline]
pub fn estimate_last_doc_line(&self, doc: &Document) -> usize {
pub fn estimate_last_doc_line(&self, doc: &Document, unobtrusive_statusline: bool) -> usize {
let doc_text = doc.text().slice(..);
let line = doc_text.char_to_line(doc.view_offset(self.id).anchor.min(doc_text.len_chars()));
// Saturating subs to make it inclusive zero indexing.
(line + self.inner_height())
(line + self.inner_height(unobtrusive_statusline))
.min(doc_text.len_lines())
.saturating_sub(1)
}
/// Calculates the last non-empty visual line on screen
#[inline]
pub fn last_visual_line(&self, doc: &Document) -> usize {
pub fn last_visual_line(&self, doc: &Document, unobtrusive_statusline: bool) -> usize {
let doc_text = doc.text().slice(..);
let viewport = self.inner_area(doc);
let viewport = self.inner_area(doc, unobtrusive_statusline);
let text_fmt = doc.text_format(viewport.width, None);
let annotations = self.text_annotations(doc, None);
let view_offset = doc.view_offset(self.id);
@ -379,7 +401,7 @@ pub fn last_visual_line(&self, doc: &Document) -> usize {
let visual_height = doc.view_offset(self.id).vertical_offset + viewport.height as usize;
// fast path when the EOF is not visible on the screen,
if self.estimate_last_doc_line(doc) < doc_text.len_lines() - 1 {
if self.estimate_last_doc_line(doc, unobtrusive_statusline) < doc_text.len_lines() - 1 {
return visual_height.saturating_sub(1);
}
@ -408,10 +430,11 @@ pub fn screen_coords_at_pos(
doc: &Document,
text: RopeSlice,
pos: usize,
unobtrusive_statusline: bool,
) -> Option<Position> {
let view_offset = doc.view_offset(self.id);
let viewport = self.inner_area(doc);
let viewport = self.inner_area(doc, unobtrusive_statusline);
let text_fmt = doc.text_format(viewport.width, None);
let annotations = self.text_annotations(doc, None);
@ -512,8 +535,9 @@ pub fn text_pos_at_screen_coords(
fmt: TextFormat,
annotations: &TextAnnotations,
ignore_virtual_text: bool,
unobtrusive_statusline: bool,
) -> Option<usize> {
let inner = self.inner_area(doc);
let inner = self.inner_area(doc, unobtrusive_statusline);
// 1 for status
if row < inner.top() || row >= inner.bottom() {
return None;
@ -572,6 +596,7 @@ pub fn pos_at_screen_coords(
row: u16,
column: u16,
ignore_virtual_text: bool,
unobtrusive_statusline: bool,
) -> Option<usize> {
self.text_pos_at_screen_coords(
doc,
@ -580,6 +605,7 @@ pub fn pos_at_screen_coords(
doc.text_format(self.inner_width(doc), None),
&self.text_annotations(doc, None),
ignore_virtual_text,
unobtrusive_statusline,
)
}
@ -712,7 +738,8 @@ fn test_text_pos_at_screen_coords() {
2,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
None
);
@ -725,6 +752,7 @@ fn test_text_pos_at_screen_coords() {
TextFormat::default(),
&TextAnnotations::default(),
true
false,
),
None
);
@ -736,7 +764,8 @@ fn test_text_pos_at_screen_coords() {
2,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false,
),
None
);
@ -748,7 +777,8 @@ fn test_text_pos_at_screen_coords() {
49,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false,
),
None
);
@ -760,7 +790,8 @@ fn test_text_pos_at_screen_coords() {
41,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
None
);
@ -772,7 +803,8 @@ fn test_text_pos_at_screen_coords() {
81,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false,
),
None
);
@ -784,7 +816,8 @@ fn test_text_pos_at_screen_coords() {
41,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
None
);
@ -796,7 +829,8 @@ fn test_text_pos_at_screen_coords() {
40 + DEFAULT_GUTTER_OFFSET + 3,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false,
),
Some(3)
);
@ -808,7 +842,8 @@ fn test_text_pos_at_screen_coords() {
80,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false,
),
Some(3)
);
@ -820,7 +855,8 @@ fn test_text_pos_at_screen_coords() {
40 + DEFAULT_GUTTER_OFFSET + 1,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false,
),
Some(4)
);
@ -832,7 +868,8 @@ fn test_text_pos_at_screen_coords() {
40 + DEFAULT_GUTTER_OFFSET + 4,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false,
),
Some(5)
);
@ -844,7 +881,8 @@ fn test_text_pos_at_screen_coords() {
40 + DEFAULT_GUTTER_OFFSET + 7,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false,
),
Some(8)
);
@ -856,7 +894,8 @@ fn test_text_pos_at_screen_coords() {
80,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false,
),
Some(8)
);
@ -886,7 +925,8 @@ fn test_text_pos_at_screen_coords_without_line_numbers_gutter() {
40 + DEFAULT_GUTTER_OFFSET_ONLY_DIAGNOSTICS + 1,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false,
),
Some(4)
);
@ -916,7 +956,8 @@ fn test_text_pos_at_screen_coords_without_any_gutters() {
40 + 1,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
Some(4)
);
@ -941,7 +982,8 @@ fn test_text_pos_at_screen_coords_cjk() {
40 + DEFAULT_GUTTER_OFFSET,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
Some(0)
);
@ -953,7 +995,8 @@ fn test_text_pos_at_screen_coords_cjk() {
40 + DEFAULT_GUTTER_OFFSET + 4,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
Some(4)
);
@ -964,7 +1007,8 @@ fn test_text_pos_at_screen_coords_cjk() {
40 + DEFAULT_GUTTER_OFFSET + 5,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
Some(4)
);
@ -976,7 +1020,8 @@ fn test_text_pos_at_screen_coords_cjk() {
40 + DEFAULT_GUTTER_OFFSET + 6,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
Some(5)
);
@ -988,7 +1033,8 @@ fn test_text_pos_at_screen_coords_cjk() {
40 + DEFAULT_GUTTER_OFFSET + 7,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
Some(5)
);
@ -1000,7 +1046,8 @@ fn test_text_pos_at_screen_coords_cjk() {
40 + DEFAULT_GUTTER_OFFSET + 8,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
Some(6)
);
@ -1025,7 +1072,8 @@ fn test_text_pos_at_screen_coords_graphemes() {
40 + DEFAULT_GUTTER_OFFSET,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
Some(0)
);
@ -1037,7 +1085,8 @@ fn test_text_pos_at_screen_coords_graphemes() {
40 + DEFAULT_GUTTER_OFFSET + 1,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
Some(1)
);
@ -1049,7 +1098,8 @@ fn test_text_pos_at_screen_coords_graphemes() {
40 + DEFAULT_GUTTER_OFFSET + 2,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
Some(3)
);
@ -1061,7 +1111,8 @@ fn test_text_pos_at_screen_coords_graphemes() {
40 + DEFAULT_GUTTER_OFFSET + 3,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false
),
Some(5)
);
@ -1073,7 +1124,8 @@ fn test_text_pos_at_screen_coords_graphemes() {
40 + DEFAULT_GUTTER_OFFSET + 4,
TextFormat::default(),
&TextAnnotations::default(),
true
true,
false,
),
Some(7)
);