diff --git a/doc/pages/faces.asciidoc b/doc/pages/faces.asciidoc index 93f65f0844..cfd4cf7628 100644 --- a/doc/pages/faces.asciidoc +++ b/doc/pages/faces.asciidoc @@ -146,6 +146,12 @@ the user interface: *BufferPadding*:: Face applied on the *~* characters that follow the last line of a buffer. +*ScrollBarGutter*:: + Face applied on the scroll bar's gutter. + +*ScrollBarHandle*:: + Face applied on the scroll bar's handle. + === Built-in highlighter faces The following faces are used by built-in highlighters if enabled. diff --git a/doc/pages/options.asciidoc b/doc/pages/options.asciidoc index 7574fd6af8..f1444e524f 100644 --- a/doc/pages/options.asciidoc +++ b/doc/pages/options.asciidoc @@ -388,6 +388,9 @@ are exclusively available to built-in options. set the maximum allowable width of an info box. set to zero for no limit. + *terminal_scroll_bar*::: + if *yes* or *true* a scroll bar will be displayed. + *terminal_cursor_native*::: if *yes* or *true*, use native terminal cursor visibility control instead of Kakoune's cursor management (defaults to *false*) diff --git a/src/client.cc b/src/client.cc index fec018e36d..8f8565c3aa 100644 --- a/src/client.cc +++ b/src/client.cc @@ -264,7 +264,20 @@ void Client::redraw_ifn() { auto& display_buffer = window.update_display_buffer(context()); auto cursor_pos = window.display_coord(context().selections().main().cursor()).value_or(DisplayCoord{}); - m_ui->draw(display_buffer, cursor_pos, faces["Default"], faces["BufferPadding"]); + auto selections = context().selections(); + auto sel = selections.begin(); + Vector selection_lines; + selection_lines.reserve(selections.size()); + std::generate_n(std::back_inserter(selection_lines), selections.size(), [&sel] { return (sel++)->min().line; }); + m_ui->draw(display_buffer, + cursor_pos, + {display_buffer.range().begin.line, display_buffer.range().end.line}, + context().buffer().line_count(), + selection_lines, + faces["Default"], + faces["BufferPadding"], + faces["ScrollBarGutter"], + faces["ScrollBarHandle"]); } const bool update_menu_anchor = (m_ui_pending & Draw) and not (m_ui_pending & MenuHide) and diff --git a/src/face_registry.cc b/src/face_registry.cc index aa997dde7e..977651764a 100644 --- a/src/face_registry.cc +++ b/src/face_registry.cc @@ -207,6 +207,8 @@ FaceRegistry::FaceRegistry() { "BufferPadding", {Face{ Color::Blue, Color::Default }} }, { "Whitespace", {Face{ Color::Default, Color::Default, Attribute::FinalFg }} }, { "WhitespaceIndent", {Face{}, "Whitespace"} }, + { "ScrollBarGutter", {Face{ Color::Blue, Color::Default }} }, + { "ScrollBarHandle", {Face{ Color::Blue, Color::Default, Attribute::Reverse }} }, } {} diff --git a/src/json_ui.cc b/src/json_ui.cc index c994c1bed2..98d754061b 100644 --- a/src/json_ui.cc +++ b/src/json_ui.cc @@ -140,10 +140,27 @@ JsonUI::JsonUI() set_signal_handler(SIGINT, SIG_DFL); } -void JsonUI::draw(const DisplayBuffer& display_buffer, DisplayCoord cursor_pos, - const Face& default_face, const Face& padding_face) +void JsonUI::draw(const DisplayBuffer& display_buffer, + const DisplayCoord cursor_pos, + const Range range, + const LineCount buffer_line_count, + const Vector selection_lines, + const Face& default_face, + const Face& padding_face, + const Face& scroll_bar_gutter_face, + const Face& scroll_bar_handle_face) { - rpc_call("draw", display_buffer.lines(), cursor_pos, default_face, padding_face); + rpc_call("draw", + display_buffer.lines(), + cursor_pos, + range.begin, + range.end, + buffer_line_count, + selection_lines, + default_face, + padding_face, + scroll_bar_gutter_face, + scroll_bar_handle_face); } void JsonUI::draw_status(const DisplayLine& prompt, diff --git a/src/json_ui.hh b/src/json_ui.hh index 49790a312c..e5c077b35e 100644 --- a/src/json_ui.hh +++ b/src/json_ui.hh @@ -22,9 +22,14 @@ public: bool is_ok() const override { return m_stdin_watcher.fd() != -1; } void draw(const DisplayBuffer& display_buffer, - DisplayCoord cursor_pos, + const DisplayCoord cursor_pos, + const Range range, + const LineCount buffer_line_count, + const Vector selection_lines, const Face& default_face, - const Face& buffer_padding) override; + const Face& padding_face, + const Face& scroll_bar_gutter_face, + const Face& scroll_bar_handle_face) override; void draw_status(const DisplayLine& prompt, const DisplayLine& content, diff --git a/src/main.cc b/src/main.cc index 1c33c41afc..b88caa0677 100644 --- a/src/main.cc +++ b/src/main.cc @@ -530,6 +530,7 @@ void register_options() " terminal_shift_function_key int\n" " terminal_padding_char codepoint\n" " terminal_padding_fill bool\n" + " terminal_scroll_bar bool\n" " terminal_cursor_native bool\n" " terminal_info_max_width int\n", UserInterface::Options{}); @@ -582,7 +583,15 @@ UniquePtr make_ui(UIType ui_type) void info_show(const DisplayLine&, const DisplayLineList&, DisplayCoord, Face, InfoStyle) override {} void info_hide() override {} - void draw(const DisplayBuffer&, DisplayCoord, const Face&, const Face&) override {} + void draw(const DisplayBuffer& display_buffer, + const DisplayCoord, + const Range range, + const LineCount buffer_line_count, + const Vector selection_lines, + const Face& default_face, + const Face& padding_face, + const Face& scroll_bar_gutter_face, + const Face& scroll_bar_handle_face) override {} void draw_status(const DisplayLine&, const DisplayLine&, const ColumnCount, const DisplayLine&, const Face&) override {} DisplayCoord dimensions() override { return {24,80}; } void refresh(bool) override {} diff --git a/src/remote.cc b/src/remote.cc index 908f1765cc..ee58c882cb 100644 --- a/src/remote.cc +++ b/src/remote.cc @@ -398,9 +398,14 @@ class RemoteUI : public UserInterface void info_hide() override; void draw(const DisplayBuffer& display_buffer, - DisplayCoord cursor_pos, + const DisplayCoord cursor_pos, + const Range range, + const LineCount buffer_line_count, + const Vector selection_lines, const Face& default_face, - const Face& padding_face) override; + const Face& padding_face, + const Face& scroll_bar_gutter_face, + const Face& scroll_bar_handle_face) override; void draw_status(const DisplayLine& prompt, const DisplayLine& content, @@ -563,11 +568,16 @@ void RemoteUI::info_hide() } void RemoteUI::draw(const DisplayBuffer& display_buffer, - DisplayCoord cursor_pos, + const DisplayCoord cursor_pos, + const Range range, + const LineCount buffer_line_count, + const Vector selection_lines, const Face& default_face, - const Face& padding_face) + const Face& padding_face, + const Face& scroll_bar_gutter_face, + const Face& scroll_bar_handle_face) { - send_message(MessageType::Draw, display_buffer, cursor_pos, default_face, padding_face); + send_message(MessageType::Draw, display_buffer, cursor_pos, range, buffer_line_count, selection_lines, default_face, padding_face, scroll_bar_gutter_face, scroll_bar_handle_face); } void RemoteUI::draw_status(const DisplayLine& prompt, diff --git a/src/terminal_ui.cc b/src/terminal_ui.cc index 09abe8caf1..1a1c1f7573 100644 --- a/src/terminal_ui.cc +++ b/src/terminal_ui.cc @@ -557,12 +557,27 @@ void TerminalUI::refresh(bool force) m_dirty = false; } +template +T scale_to(T val, Range from, Range to) +{ + T from_size = from.end - from.begin + 1; + T to_size = to.end - to.begin + 1; + + return ((val - from.begin) * to_size + from_size / 2) / from_size + to.begin; +} + static const DisplayLine empty_line = { String(" "), {} }; + void TerminalUI::draw(const DisplayBuffer& display_buffer, - DisplayCoord cursor_pos, + const DisplayCoord cursor_pos, + const Range range, + const LineCount buffer_line_count, + const Vector selection_lines, const Face& default_face, - const Face& padding_face) + const Face& padding_face, + const Face& scroll_bar_gutter_face, + const Face& scroll_bar_handle_face) { check_resize(); @@ -576,9 +591,40 @@ void TerminalUI::draw(const DisplayBuffer& display_buffer, DisplayAtom padding{String{m_padding_char, m_padding_fill ? dim.column : 1}}; + const auto padding_lines = (dim.line + line_offset) - line_index; while (line_index < dim.line + line_offset) m_window.draw(line_index++, padding, face); + if (m_scroll_bar) + { + Range gutter_range = {0_line, dim.line - 1}; + Range buffer_range = {0_line, buffer_line_count - 1 + dim.line - 1}; + + std::fill(m_scroll_bar_scratch.begin(), m_scroll_bar_scratch.end(), 0); + + for (const LineCount selection_line : selection_lines) + m_scroll_bar_scratch[(int) scale_to(selection_line, buffer_range, gutter_range)]++; + + const auto visible_lines = range.end - range.begin + padding_lines; + const auto mark_height = scale_to(visible_lines, buffer_range, gutter_range); + + const auto mark_begin = scale_to(range.begin, buffer_range, gutter_range); + const auto mark_end = mark_begin + mark_height; + + for (auto line = 0_line; line < dim.line; ++line) { + const bool is_mark = line >= mark_begin and line <= mark_end; + String selections; + switch (m_scroll_bar_scratch[(int)line]) { + case 0: selections = " "; break; + case 1: selections = "-"; break; + case 2: selections = "="; break; + default: selections = "≡"; break; + } + + m_window.draw({line + line_offset, m_window.size.column - 1}, DisplayAtom(selections), is_mark ? scroll_bar_handle_face : scroll_bar_gutter_face); + } + } + m_cursor_pos = cursor_pos; m_dirty = true; @@ -605,12 +651,13 @@ void TerminalUI::draw_status(const DisplayLine& prompt, m_window.draw(status_line_pos, prompt.atoms(), default_face); m_window.draw(DisplayCoord{status_line_pos, prompt.length()}, trimmed_content.atoms(), default_face); + const auto scroll_bar_gutter = m_scroll_bar ? 1 : 0; const auto mode_len = mode_line.length(); m_status_len = prompt.length() + trimmed_content.length(); const auto remaining = m_dimensions.column - m_status_len; if (mode_len < remaining) { - ColumnCount col = m_dimensions.column - mode_len; + ColumnCount col = m_dimensions.column - mode_len + scroll_bar_gutter; m_window.draw({status_line_pos, col}, mode_line.atoms(), default_face); } else if (remaining > 2) @@ -620,7 +667,7 @@ void TerminalUI::draw_status(const DisplayLine& prompt, trimmed_mode_line.insert(trimmed_mode_line.begin(), { "…", {} }); kak_assert(trimmed_mode_line.length() == remaining - 1); - ColumnCount col = m_dimensions.column - remaining + 1; + ColumnCount col = m_dimensions.column - remaining + 1 + scroll_bar_gutter; m_window.draw({status_line_pos, col}, trimmed_mode_line.atoms(), default_face); } @@ -675,6 +722,11 @@ void TerminalUI::check_resize(bool force) m_dimensions = terminal_size - 1_line; + if (m_scroll_bar) { + m_dimensions -= {0_line, 1_col}; + m_scroll_bar_scratch.resize((size_t)m_dimensions.line); + } + // if (char* csr = tigetstr((char*)"csr")) // putp(tparm(csr, 0, ws.ws_row)); @@ -1593,7 +1645,7 @@ void TerminalUI::set_ui_options(const Options& options) m_padding_char = find("terminal_padding_char").map([](StringView s) { return s.column_length() < 1 ? ' ' : s[0_char]; }).value_or(Codepoint{'~'}); m_padding_fill = find("terminal_padding_fill").map(to_bool).value_or(false); - + bool new_cursor_native = find("terminal_cursor_native").map(to_bool).value_or(false); if (new_cursor_native != m_cursor_native) { @@ -1602,6 +1654,13 @@ void TerminalUI::set_ui_options(const Options& options) } m_info_max_width = find("terminal_info_max_width").map(str_to_int_ifp).value_or(0); + + bool new_scroll_bar = find("terminal_scroll_bar").map(to_bool).value_or(false); + + if (m_scroll_bar != new_scroll_bar) { + m_scroll_bar = new_scroll_bar; + check_resize(true); + } } } diff --git a/src/terminal_ui.hh b/src/terminal_ui.hh index a98dcd389e..f4cf7312c3 100644 --- a/src/terminal_ui.hh +++ b/src/terminal_ui.hh @@ -31,9 +31,14 @@ public: bool is_ok() const override { return (bool)m_window; } void draw(const DisplayBuffer& display_buffer, - DisplayCoord cursor_pos, + const DisplayCoord cursor_pos, + const Range range, + const LineCount buffer_line_count, + const Vector selection_lines, const Face& default_face, - const Face& padding_face) override; + const Face& padding_face, + const Face& scroll_bar_gutter_face, + const Face& scroll_bar_handle_face) override; void draw_status(const DisplayLine& prompt, const DisplayLine& content, @@ -167,6 +172,9 @@ private: bool m_padding_fill = false; bool m_cursor_native = false; + bool m_scroll_bar = false; + Vector m_scroll_bar_scratch; + bool m_dirty = false; bool m_resize_pending = false; diff --git a/src/user_interface.hh b/src/user_interface.hh index 8cb25a4837..53a05e06da 100644 --- a/src/user_interface.hh +++ b/src/user_interface.hh @@ -2,8 +2,13 @@ #define user_interface_hh_INCLUDED #include "array_view.hh" -#include "hash_map.hh" #include "function.hh" +#include "hash_map.hh" +#include "range.hh" +#include "selection.hh" +#include "units.hh" + +#include namespace Kakoune { @@ -58,9 +63,14 @@ public: virtual void info_hide() = 0; virtual void draw(const DisplayBuffer& display_buffer, - DisplayCoord cursor_pos, + const DisplayCoord cursor_pos, + const Range range, + const LineCount buffer_line_count, + const Vector selection_lines, const Face& default_face, - const Face& padding_face) = 0; + const Face& padding_face, + const Face& scroll_bar_gutter_face, + const Face& scroll_bar_handle_face) = 0; virtual void draw_status(const DisplayLine& prompt, const DisplayLine& content,