diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index abbc2992b19e8..6774794f64580 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -3182,26 +3182,41 @@ void CommandInterpreter::OutputHelpText(Stream &strm, llvm::StringRef word_text, uint32_t chars_left = max_columns; - auto nextWordLength = [](llvm::StringRef S) { - size_t pos = S.find(' '); - return pos == llvm::StringRef::npos ? S.size() : pos; + auto start_new_line = [&] { + strm.EOL(); + strm.Indent(); + chars_left = max_columns - indent_size; }; while (!text.empty()) { - if (text.front() == '\n' || - (text.front() == ' ' && nextWordLength(text.ltrim(' ')) > chars_left)) { - strm.EOL(); - strm.Indent(); - chars_left = max_columns - indent_size; - if (text.front() == '\n') - text = text.drop_front(); - else - text = text.ltrim(' '); - } else { - strm.PutChar(text.front()); - --chars_left; + if (text.starts_with('\n')) { text = text.drop_front(); + start_new_line(); + continue; } + + // Calculate the size of the next fragment. A fragment is defined as zero + // or more spaces followed by a word (which is sequence of non-whitespace + // characters). It is assumed that the only possible whitespaces in the + // input text are ' ' and '\n'. + size_t word_start_pos = text.find_first_not_of(' '); + size_t word_end_pos = text.find_first_of(" \n", /*from=*/word_start_pos); + size_t fragment_size = + word_end_pos == llvm::StringRef::npos ? text.size() : word_end_pos; + + if (fragment_size > chars_left && text.starts_with(' ')) { + // The fragment does not fit on the current line, but begins with a space. + // Break the line at the beginning of the word contained in the fragment. + text = text.drop_front(word_start_pos); + start_new_line(); + continue; + } + + // Print out the fragment. It fits on the current line or does not contain + // spaces where we could break the line. + strm.PutCString(text.take_front(fragment_size)); + text = text.drop_front(fragment_size); + chars_left = fragment_size > chars_left ? 0 : chars_left - fragment_size; } strm.EOL(); diff --git a/lldb/test/API/commands/help/TestHelp.py b/lldb/test/API/commands/help/TestHelp.py index 8423d410ca306..c8f87c7af27de 100644 --- a/lldb/test/API/commands/help/TestHelp.py +++ b/lldb/test/API/commands/help/TestHelp.py @@ -239,7 +239,7 @@ def test_help_unknown_flag(self): def test_help_format_output(self): """Test that help output reaches TerminalWidth and wraps to the next line if needed.""" - self.runCmd("settings set term-width 118") + self.runCmd("settings set term-width 105") self.expect( "help format", matching=True, @@ -249,9 +249,9 @@ def test_help_format_output(self): ], ) - # The length of the first line will not be exactly 108 because we split + # The length of the first line will not be exactly 105 because we split # at the last whitespace point before the limit. - self.runCmd("settings set term-width 108") + self.runCmd("settings set term-width 104") self.expect( "help format", matching=True, @@ -271,6 +271,16 @@ def test_help_format_output(self): ], ) + # Check that line splitting works with newline characters too. The raw + # input after the word "pointer" does not contain spaces among more than + # a hundred subsequent characters, the words are separated by newlines. + self.runCmd("settings set term-width 80") + self.expect( + "help format", + matching=True, + substrs=["'p' or \"pointer\""], + ) + @no_debug_info_test def test_help_option_group_format_options_usage(self): """Test that help on commands that use OptionGroupFormat options provide relevant help specific to that command."""