diff --git a/doc/pages/highlighters.asciidoc b/doc/pages/highlighters.asciidoc index e4b56a6b..9931355e 100644 --- a/doc/pages/highlighters.asciidoc +++ b/doc/pages/highlighters.asciidoc @@ -94,6 +94,9 @@ add-highlighter window regex //\h*(TODO:)[^\n]* 0:cyan 1:yellow,red *-word*::: wrap at word boundaries instead of codepoint boundaries. + *-indent*::: + preserve line indent when wrapping. + *-width *::: wrap text at *max_width* if the window is wider. diff --git a/src/highlighters.cc b/src/highlighters.cc index c59a57d1..6f75e3b2 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -654,8 +654,9 @@ HighlighterAndId create_column_highlighter(HighlighterParameters params) struct WrapHighlighter : Highlighter { - WrapHighlighter(ColumnCount max_width, bool word_wrap) - : Highlighter{HighlightPass::Wrap}, m_max_width{max_width}, m_word_wrap{word_wrap} {} + WrapHighlighter(ColumnCount max_width, bool word_wrap, bool preserve_indent) + : Highlighter{HighlightPass::Wrap}, m_max_width{max_width}, + m_word_wrap{word_wrap}, m_preserve_indent{preserve_indent} {} void do_highlight(const Context& context, HighlightPass pass, DisplayBuffer& display_buffer, BufferRange) override @@ -673,6 +674,9 @@ struct WrapHighlighter : Highlighter { const LineCount buf_line = it->range().begin.line; const ByteCount line_length = buffer[buf_line].length(); + ColumnCount indent = m_preserve_indent ? line_indent(buffer, tabstop, buf_line) : 0_col; + if (indent >= wrap_column) // do not preserve indent when its bigger than wrap column + indent = 0; auto coord = next_split_coord(buffer, wrap_column, tabstop, buf_line); if (buffer.is_valid(coord) and not buffer.is_end(coord)) @@ -695,9 +699,12 @@ struct WrapHighlighter : Highlighter DisplayLine new_line{ AtomList{ atom_it, line.end() } }; line.erase(atom_it, line.end()); + if (indent != 0) + new_line.insert(new_line.begin(), DisplayAtom{String{' ', indent}}); + if (it+1 - display_buffer.lines().begin() == win_height) { - if (cursor >= new_line.begin()->begin()) // strip first lines if cursor is not visible + if (cursor >= new_line.range().begin) // strip first lines if cursor is not visible { display_buffer.lines().erase(display_buffer.lines().begin(), display_buffer.lines().begin()+1); --it; @@ -710,7 +717,7 @@ struct WrapHighlighter : Highlighter } it = display_buffer.lines().insert(it+1, new_line); - coord = next_split_coord(buffer, wrap_column, tabstop, coord); + coord = next_split_coord(buffer, wrap_column - indent, tabstop, coord); atom_it = it->begin(); } } @@ -727,13 +734,14 @@ struct WrapHighlighter : Highlighter const auto& cursor = context.selections().main().cursor(); const int tabstop = context.options()["tabstop"].get(); - auto line_wrap_count = [&](LineCount line) { + auto line_wrap_count = [&](LineCount line, ColumnCount indent) { LineCount count = 0; BufferCoord coord{line}; const ByteCount line_length = buffer[line].length(); while (true) { - coord = next_split_coord(buffer, wrap_column, tabstop, coord); + coord = next_split_coord(buffer, wrap_column - (coord.column == 0 ? 0_col : indent), + tabstop, coord); if (coord.column == line_length) break; ++count; @@ -756,7 +764,11 @@ struct WrapHighlighter : Highlighter if (buf_line >= buffer.line_count()) break; - const auto wrap_count = line_wrap_count(buf_line); + ColumnCount indent = m_preserve_indent ? line_indent(buffer, tabstop, buf_line) : 0_col; + if (indent >= wrap_column) // do not preserve indent when its bigger than wrap column + indent = 0; + + const auto wrap_count = line_wrap_count(buf_line, indent); setup.window_range.line -= wrap_count; if (win_line < setup.cursor_pos.line) @@ -767,10 +779,14 @@ struct WrapHighlighter : Highlighter BufferCoord coord{buf_line}; while (true) { - auto split_coord = next_split_coord(buffer, wrap_column, tabstop, coord); + auto split_coord = next_split_coord(buffer, wrap_column - (coord.column == 0 ? 0_col : indent), + tabstop, coord); if (split_coord.column > cursor.column) { setup.cursor_pos.column = get_column(buffer, tabstop, cursor) - get_column(buffer, tabstop, coord); + if (coord.column != 0) + setup.cursor_pos.column += indent; + break; } ++setup.cursor_pos.line; @@ -786,7 +802,7 @@ struct WrapHighlighter : Highlighter while (setup.window_pos.line < cursor.line and setup.cursor_pos.line >= win_height - setup.scroll_offset.line) { - auto removed_lines = 1 + line_wrap_count(setup.window_pos.line++); + auto removed_lines = 1 + line_wrap_count(setup.window_pos.line++, indent); setup.cursor_pos.line -= removed_lines; win_line -= removed_lines; // removed one line from the range, added removed_lines potential ones @@ -807,6 +823,9 @@ struct WrapHighlighter : Highlighter auto column = get_column(buffer, tabstop, coord); auto col = get_byte_to_column( buffer, tabstop, {coord.line, column + wrap_column}); + if (col == coord.column) // Can happen if we try to wrap on a tab char + col = buffer.char_next(coord).column; + BufferCoord split_coord{coord.line, col}; if (m_word_wrap) @@ -823,10 +842,20 @@ struct WrapHighlighter : Highlighter return split_coord; }; + ColumnCount line_indent(const Buffer& buffer, int tabstop, LineCount line) const + { + StringView l = buffer[line]; + auto col = 0_byte; + while (is_horizontal_blank(l[col])) + ++col; + return get_column(buffer, tabstop, {line, col}); + } + static HighlighterAndId create(HighlighterParameters params) { static const ParameterDesc param_desc{ { { "word", { false, "" } }, + { "indent", { false, "" } }, { "width", { true, "" } } }, ParameterDesc::Flags::None, 0, 0 }; @@ -836,10 +865,12 @@ struct WrapHighlighter : Highlighter if (auto width = parser.get_switch("width")) max_width = str_to_int(*width); - return {"wrap", std::make_unique(max_width, (bool)parser.get_switch("word"))}; + return {"wrap", std::make_unique(max_width, (bool)parser.get_switch("word"), + (bool)parser.get_switch("indent"))}; } const bool m_word_wrap; + const bool m_preserve_indent; const ColumnCount m_max_width; };