Index: lldb/source/Core/IOHandlerCursesGUI.cpp =================================================================== --- lldb/source/Core/IOHandlerCursesGUI.cpp +++ lldb/source/Core/IOHandlerCursesGUI.cpp @@ -480,24 +480,41 @@ // Curses doesn't allow direct output of color escape sequences, but that's // how we get source lines from the Highligher class. Read the line and - // convert color escape sequences to curses color attributes. - void OutputColoredStringTruncated(int right_pad, StringRef string, + // convert color escape sequences to curses color attributes. Use + // first_skip_count to skip leading visible characters. Returns false if all + // visible characters were skipped due to first_skip_count. + bool OutputColoredStringTruncated(int right_pad, StringRef string, + size_t skip_first_count, bool use_blue_background) { attr_t saved_attr; short saved_pair; int saved_opts; + bool result = false; ::wattr_get(m_window, &saved_attr, &saved_pair, &saved_opts); if (use_blue_background) ::wattron(m_window, COLOR_PAIR(WhiteOnBlue)); while (!string.empty()) { size_t esc_pos = string.find('\x1b'); if (esc_pos == StringRef::npos) { - PutCStringTruncated(right_pad, string.data(), string.size()); + string = string.substr(skip_first_count); + if (!string.empty()) { + PutCStringTruncated(right_pad, string.data(), string.size()); + result = true; + } break; } if (esc_pos > 0) { - PutCStringTruncated(right_pad, string.data(), esc_pos); - string = string.drop_front(esc_pos); + if (skip_first_count > 0) { + int skip = std::min(esc_pos, skip_first_count); + string = string.substr(skip); + skip_first_count -= skip; + esc_pos -= skip; + } + if (esc_pos > 0) { + PutCStringTruncated(right_pad, string.data(), esc_pos); + result = true; + string = string.drop_front(esc_pos); + } } bool consumed = string.consume_front("\x1b"); assert(consumed); @@ -532,6 +549,7 @@ } } ::wattr_set(m_window, saved_attr, saved_pair, &saved_opts); + return result; } void Touch() { @@ -3380,7 +3398,8 @@ m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(), m_title(), m_line_width(4), m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX), m_first_visible_line(0), - m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {} + m_first_visible_column(0), m_min_x(0), m_min_y(0), m_max_x(0), + m_max_y(0) {} ~SourceFileWindowDelegate() override = default; @@ -3397,6 +3416,8 @@ {KEY_RETURN, "Run to selected line with one shot breakpoint"}, {KEY_UP, "Select previous source line"}, {KEY_DOWN, "Select next source line"}, + {KEY_LEFT, "Scroll to the left"}, + {KEY_RIGHT, "Scroll to the right"}, {KEY_PPAGE, "Page up"}, {KEY_NPAGE, "Page down"}, {'b', "Set breakpoint on selected source/disassembly line"}, @@ -3646,12 +3667,16 @@ StringRef line = lineStream.GetString(); if (line.endswith("\n")) line = line.drop_back(); - window.OutputColoredStringTruncated(1, line, line_is_selected); - if (line_is_selected && line.empty()) { - // Draw an empty space to show the selected line. + bool wasWritten = window.OutputColoredStringTruncated( + 1, line, m_first_visible_column, line_is_selected); + if (line_is_selected && !wasWritten) { + // Draw an empty space to show the selected line if empty, + // or draw '<' if nothing is visible because of scrolling too much + // to the right. const attr_t selected_attr = COLOR_PAIR(WhiteOnBlue); window.AttributeOn(selected_attr); - window.PutCStringTruncated(1, " "); + window.PutCStringTruncated( + 1, line.empty() && m_first_visible_column == 0 ? " " : "<"); window.AttributeOff(selected_attr); } @@ -3809,7 +3834,9 @@ strm.Printf("%s", mnemonic); int right_pad = 1; - window.PutCStringTruncated(right_pad, strm.GetData()); + window.PutCStringTruncated( + right_pad, + strm.GetString().substr(m_first_visible_column).data()); if (is_pc_line && frame_sp && frame_sp->GetConcreteFrameIndex() == 0) { @@ -3904,6 +3931,15 @@ } return eKeyHandled; + case KEY_LEFT: + if (m_first_visible_column > 0) + --m_first_visible_column; + return eKeyHandled; + + case KEY_RIGHT: + ++m_first_visible_column; + return eKeyHandled; + case '\r': case '\n': case KEY_ENTER: @@ -4135,6 +4171,7 @@ uint32_t m_stop_id; uint32_t m_frame_idx; int m_first_visible_line; + int m_first_visible_column; int m_min_x; int m_min_y; int m_max_x; Index: lldb/test/API/commands/gui/viewlarge/TestGuiViewLarge.py =================================================================== --- lldb/test/API/commands/gui/viewlarge/TestGuiViewLarge.py +++ lldb/test/API/commands/gui/viewlarge/TestGuiViewLarge.py @@ -25,6 +25,9 @@ self.expect("run", substrs=["stop reason ="]) escape_key = chr(27).encode() + left_key = chr(27)+'OD' # for vt100 terminal (lldbexpect sets TERM=vt100) + right_key = chr(27)+'OC' + ctrl_l = chr(12) # Start the GUI. self.child.sendline("gui") @@ -44,6 +47,20 @@ self.child.expect_exact("(int) a_variable_with_a_very_looooooooooooooooooooooooooooooo"+chr(27)) self.child.expect_exact("(int) shortvar = 1"+chr(27)) + # Scroll the sources view twice to the right. + self.child.send(right_key) + self.child.send(right_key) + # Force a redraw, otherwise curses will optimize the drawing to not draw all 'o'. + self.child.send(ctrl_l) + # The source code is indented by two spaces, so there'll be just two extra 'o' on the right. + self.child.expect_exact("int a_variable_with_a_very_looooooooooooooooooooooooooooo"+chr(27)) + + # And scroll back to the left. + self.child.send(left_key) + self.child.send(left_key) + self.child.send(ctrl_l) + self.child.expect_exact("int a_variable_with_a_very_looooooooooooooooooooooooooo"+chr(27)) + # Press escape to quit the gui self.child.send(escape_key)