diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7e5296f2e..0e3b1134d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -272,6 +272,7 @@ Interactive improvements - fish is now more resilient against broken terminal modes (:issue:`7133`, :issue:`4873`). - fish handles being in control of the TTY without owning its own process group better, avoiding some hangs in special configurations (:issue:`7388`). - Keywords can now be colored differently by setting the ``fish_color_keyword`` variable (but ``fish_color_command`` will still be used if it is unset) (:issue:`7678`). +- Just like new ``fish_indent``, the interactive reader will indent continuation lines that follow a line ending in a backslash, ``|``, ``&&`` or ``||`` (:issue:`7694`). New or improved bindings ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index c115d609a..a343d2705 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1451,6 +1451,60 @@ static void test_indents() { 1, "\n" // ); + // Continuation lines. + add_test(&tests, // + 0, "echo 'continuation line' \\", // + 1, "\ncont", // + 0, "\n" // + ); + add_test(&tests, // + 0, "echo 'empty continuation line' \\", // + 1, "\n" // + ); + add_test(&tests, // + 0, "begin # continuation line in block", // + 1, "\necho \\", // + 2, "\ncont" // + ); + add_test(&tests, // + 0, "begin # empty continuation line in block", // + 1, "\necho \\", // + 2, "\n", // + 0, "\nend" // + ); + add_test(&tests, // + 0, "echo 'multiple continuation lines' \\", // + 1, "\nline1 \\", // + 1, "\n# comment", // + 1, "\n# more comment", // + 1, "\nline2 \\", // + 1, "\n" // + ); + add_test(&tests, // + 0, "echo # comment ending in \\", // + 0, "\nline" // + ); + add_test(&tests, // + 0, "echo 'multiple empty continuation lines' \\", // + 1, "\n\\", // + 1, "\n", // + 0, "\n" // + ); + add_test(&tests, // + 0, "echo 'multiple statements with continuation lines' \\", // + 1, "\nline 1", // + 0, "\necho \\", // + 1, "\n" // + ); + // This is an edge case, probably okay to change the behavior here. + add_test(&tests, // + 0, "begin", 1, " \\", // + 2, "\necho 'continuation line in block header' \\", // + 2, "\n", // + 1, "\n", // + 0, "\nend" // + ); + int test_idx = 0; for (const test_t &test : tests) { // Construct the input text and expected indents. diff --git a/src/parse_util.cpp b/src/parse_util.cpp index 80faa0033..5d7ba25b6 100644 --- a/src/parse_util.cpp +++ b/src/parse_util.cpp @@ -679,6 +679,7 @@ std::vector parse_util_compute_indents(const wcstring &src) { auto range = node.source_range(); if (range.length > 0 && node.category == category_t::leaf) { + record_line_continuations_until(range.start); std::fill(indents.begin() + last_leaf_end, indents.begin() + range.start, last_indent); } @@ -714,6 +715,21 @@ std::vector parse_util_compute_indents(const wcstring &src) { return nls.source(src).find(L'\n') != wcstring::npos; } + void record_line_continuations_until(size_t offset) { + wcstring gap_text = src.substr(last_leaf_end, offset - last_leaf_end); + size_t escaped_nl = gap_text.find(L"\\\n"); + if (escaped_nl == wcstring::npos) return; + auto end = src.begin() + offset; + auto newline = src.begin() + last_leaf_end + escaped_nl + 1; + // The gap text might contain multiple newlines if there are multiple lines that + // don't contain an AST node, for example, comment lines, or lines containing only + // the escaped newline. + do { + line_continuations.push_back(newline - src.begin()); + newline = std::find(newline + 1, end, L'\n'); + } while (newline != end); + } + // The one-past-the-last index of the most recently encountered leaf node. // We use this to populate the indents even if there's no tokens in the range. size_t last_leaf_end{0}; @@ -730,10 +746,14 @@ std::vector parse_util_compute_indents(const wcstring &src) { // Initialize our starting indent to -1, as our top-level node is a job list which // will immediately increment it. int indent{-1}; + + // List of locations of escaped newline characters. + std::vector line_continuations; }; indent_visitor_t iv(src, indents); node_visitor(iv).accept(ast.top()); + iv.record_line_continuations_until(indents.size()); std::fill(indents.begin() + iv.last_leaf_end, indents.end(), iv.last_indent); // All newlines now get the *next* indent. @@ -755,6 +775,13 @@ std::vector parse_util_compute_indents(const wcstring &src) { next_indent = indents.at(idx); } } + // Add an extra level of indentation to continuation lines. + for (size_t idx : iv.line_continuations) { + do { + indents.at(idx)++; + } while (++idx < src_size && src.at(idx) != L'\n'); + } + return indents; }