From b9c60e2578b8a9389556b621b67731cdfc4b2c93 Mon Sep 17 00:00:00 2001 From: Tobias Pisani Date: Tue, 26 Mar 2024 19:30:08 +0100 Subject: [PATCH] Add -indent option to show-whitespace highlighter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A couple of semi-opinionated choices were made in this implementation: 1. The guide is hidden in the first column. 2. The indent guides are highlighted using a new `WhitespaceIndent` face. 3. Nothing is done to continue the guide through empty lines. I believe this to be the correct approach, at least as long as it is kept as a part of the show-whitespaces highlighter. However some people's oppinion may differ, and if so, that could be implemented. 4. The guides default to on, like the other show-whitespace options. Default character is "│". 5. Spaces between the indent guides are currently highlighted as other spaces. Other reasonable options would be no replacement, -tabpad, or a similar -indentpad. 6. Guides are disabled by passing `-indent ""`. 7. Indent guides are separate from tab highlighting. Additionally, we could consider adding a separate face for the "current" indent level as many editors do, but this is a bit harder in kakoune because of multiple selections. Closes #2323 --- doc/pages/highlighters.asciidoc | 4 ++++ src/face_registry.cc | 1 + src/highlighters.cc | 40 ++++++++++++++++++++++++++------- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/doc/pages/highlighters.asciidoc b/doc/pages/highlighters.asciidoc index 7628dbba..32788736 100644 --- a/doc/pages/highlighters.asciidoc +++ b/doc/pages/highlighters.asciidoc @@ -55,6 +55,10 @@ highlighter is replaced with the new one. *-tabpad* ::: a one character long separator that will be appended to tabulations to honor the *tabstop* option + *-indent* ::: + a one character long separator that will replace the first space in indentation according to the *indentwidth* option + This will use the `WhitespaceIndent` face. + *-only-trailing*::: only highlight whitespaces at the end of the line diff --git a/src/face_registry.cc b/src/face_registry.cc index 7364cc2a..d6c5cd53 100644 --- a/src/face_registry.cc +++ b/src/face_registry.cc @@ -204,6 +204,7 @@ FaceRegistry::FaceRegistry() { "MatchingChar", {Face{ Color::Default, Color::Default, Attribute::Bold }} }, { "BufferPadding", {Face{ Color::Blue, Color::Default }} }, { "Whitespace", {Face{ Color::Default, Color::Default, Attribute::FinalFg }} }, + { "WhitespaceIndent", {Face{}, "Whitespace"} }, } {} diff --git a/src/highlighters.cc b/src/highlighters.cc index 22047097..60711582 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -954,7 +954,7 @@ struct TabulationHighlighter : Highlighter }; const HighlighterDesc show_whitespace_desc = { - "Parameters: [-tab ] [-tabpad ] [-lf ] [-spc ] [-nbsp ]\n" + "Parameters: [-tab ] [-tabpad ] [-lf ] [-spc ] [-nbsp ] [-indent ]\n" "Display whitespaces using symbols", { { { "tab", { ArgCompleter{}, "replace tabulations with the given character" } }, @@ -962,15 +962,16 @@ const HighlighterDesc show_whitespace_desc = { { "spc", { ArgCompleter{}, "replace spaces with the given character" } }, { "lf", { ArgCompleter{}, "replace line feeds with the given character" } }, { "nbsp", { ArgCompleter{}, "replace non-breakable spaces with the given character" } }, + { "indent", { ArgCompleter{}, "replace first space of every indent with the given character according to `indentwidth`" } }, { "only-trailing", { {}, "only highlighting trailing whitespaces" } } }, ParameterDesc::Flags::None, 0, 0 } }; struct ShowWhitespacesHighlighter : Highlighter { - ShowWhitespacesHighlighter(String tab, String tabpad, String spc, String lf, String nbsp, bool only_trailing) + ShowWhitespacesHighlighter(String tab, String tabpad, String spc, String lf, String nbsp, String indent, bool only_trailing) : Highlighter{HighlightPass::Move}, m_tab{std::move(tab)}, m_tabpad{std::move(tabpad)}, - m_spc{std::move(spc)}, m_lf{std::move(lf)}, m_nbsp{std::move(nbsp)}, m_only_trailing{std::move(only_trailing)} + m_spc{std::move(spc)}, m_lf{std::move(lf)}, m_nbsp{std::move(nbsp)}, m_indent{std::move(indent)}, m_only_trailing{std::move(only_trailing)} {} static std::unique_ptr create(HighlighterParameters params, Highlighter*) @@ -985,19 +986,26 @@ struct ShowWhitespacesHighlighter : Highlighter return value.str(); }; + String indent = parser.get_switch("indent").value_or("│").str(); + if (indent.char_length() > 1) + throw runtime_error{format("-indent expects a single character or empty parameter")}; + return std::make_unique( get_param("tab", "→"), get_param("tabpad", " "), get_param("spc", "·"), - get_param("lf", "¬"), get_param("nbsp", "⍽"), only_trailing); + get_param("lf", "¬"), get_param("nbsp", "⍽"), indent, only_trailing); } private: void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override { const int tabstop = context.context.options()["tabstop"].get(); + const int indentwidth = context.context.options()["indentwidth"].get(); auto whitespaceface = context.context.faces()["Whitespace"]; + auto indentface = context.context.faces()["WhitespaceIndent"]; const auto& buffer = context.context.buffer(); for (auto& line : display_buffer.lines()) { + bool is_indentation = true; for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it) { if (atom_it->type() != DisplayAtom::Range) @@ -1024,6 +1032,7 @@ private: { auto coord = it.coord(); Codepoint cp = utf8::read_codepoint(it, end); + auto face = whitespaceface; if (is_whitespace(cp)) { if (m_only_trailing and it.coord() <= last_non_space) @@ -1040,21 +1049,36 @@ private: const ColumnCount count = tabstop - (column % tabstop); atom_it->replace(m_tab + String(m_tabpad[(CharCount)0], count - m_tab.column_length())); } - else if (cp == ' ') - atom_it->replace(m_spc); + else if (cp == ' ') { + if (m_indent.empty() or indentwidth == 0 or not is_indentation) { + atom_it->replace(m_spc); + } else { + const ColumnCount column = get_column(buffer, tabstop, coord); + if (column % indentwidth == 0 and column != 0) { + atom_it->replace(m_indent); + face = indentface; + } else { + atom_it->replace(m_spc); + } + } + } else if (cp == '\n') atom_it->replace(m_lf); else if (cp == 0xA0 or cp == 0x202F) atom_it->replace(m_nbsp); - atom_it->face = merge_faces(atom_it->face, whitespaceface); + atom_it->face = merge_faces(atom_it->face, face); break; } + else + { + is_indentation = false; + } } } } } - const String m_tab, m_tabpad, m_spc, m_lf, m_nbsp; + const String m_tab, m_tabpad, m_spc, m_lf, m_nbsp, m_indent; const bool m_only_trailing; };