Index: include/lldb/API/SBSourceManager.h =================================================================== --- include/lldb/API/SBSourceManager.h +++ include/lldb/API/SBSourceManager.h @@ -30,6 +30,11 @@ const lldb::SBFileSpec &file, uint32_t line, uint32_t context_before, uint32_t context_after, const char *current_line_cstr, lldb::SBStream &s); + size_t DisplaySourceLinesWithLineNumbersAndColumn( + const lldb::SBFileSpec &file, uint32_t line, uint32_t column, + uint32_t context_before, uint32_t context_after, + const char *current_line_cstr, lldb::SBStream &s); + protected: friend class SBCommandInterpreter; friend class SBDebugger; Index: include/lldb/Core/Debugger.h =================================================================== --- include/lldb/Core/Debugger.h +++ include/lldb/Core/Debugger.h @@ -238,6 +238,12 @@ bool SetUseColor(bool use_color); + lldb::StopShowColumn GetStopShowColumn() const; + + const FormatEntity::Entry *GetStopShowColumnAnsiPrefix() const; + + const FormatEntity::Entry *GetStopShowColumnAnsiSuffix() const; + uint32_t GetStopSourceLineCount(bool before) const; StopDisassemblyType GetStopDisassemblyDisplay() const; Index: include/lldb/Core/Disassembler.h =================================================================== --- include/lldb/Core/Disassembler.h +++ include/lldb/Core/Disassembler.h @@ -428,15 +428,16 @@ struct SourceLine { FileSpec file; uint32_t line; + uint32_t column; - SourceLine() : file(), line(LLDB_INVALID_LINE_NUMBER) {} + SourceLine() : file(), line(LLDB_INVALID_LINE_NUMBER), column(0) {} bool operator==(const SourceLine &rhs) const { - return file == rhs.file && line == rhs.line; + return file == rhs.file && line == rhs.line && rhs.column == column; } bool operator!=(const SourceLine &rhs) const { - return file != rhs.file || line != rhs.line; + return file != rhs.file || line != rhs.line || column != rhs.column; } bool IsValid() const { return line != LLDB_INVALID_LINE_NUMBER; } @@ -486,6 +487,7 @@ SourceLine sl; sl.file = line.file; sl.line = line.line; + sl.column = line.column; return ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, sl); }; Index: include/lldb/Core/SourceManager.h =================================================================== --- include/lldb/Core/SourceManager.h +++ include/lldb/Core/SourceManager.h @@ -32,12 +32,14 @@ public: File(const FileSpec &file_spec, Target *target); + File(const FileSpec &file_spec, lldb::DebuggerSP debugger_sp); ~File(); void UpdateIfNeeded(); - size_t DisplaySourceLines(uint32_t line, uint32_t context_before, - uint32_t context_after, Stream *s); + size_t DisplaySourceLines(uint32_t line, uint32_t column, + uint32_t context_before, uint32_t context_after, + Stream *s); void FindLinesMatchingRegex(RegularExpression ®ex, uint32_t start_line, uint32_t end_line, std::vector &match_lines); @@ -76,6 +78,10 @@ lldb::DataBufferSP m_data_sp; typedef std::vector LineOffsets; LineOffsets m_offsets; + lldb::DebuggerWP m_debugger_wp; + + private: + void CommonInitializer(const FileSpec &file_spec, Target *target); }; #endif // SWIG @@ -114,14 +120,16 @@ FileSP GetLastFile() { return m_last_file_sp; } - size_t DisplaySourceLinesWithLineNumbers( - const FileSpec &file, uint32_t line, uint32_t context_before, - uint32_t context_after, const char *current_line_cstr, Stream *s, - const SymbolContextList *bp_locs = nullptr); + size_t + DisplaySourceLinesWithLineNumbers(const FileSpec &file, uint32_t line, + uint32_t column, uint32_t context_before, + uint32_t context_after, + const char *current_line_cstr, Stream *s, + const SymbolContextList *bp_locs = nullptr); // This variant uses the last file we visited. size_t DisplaySourceLinesWithLineNumbersUsingLastFile( - uint32_t start_line, uint32_t count, uint32_t curr_line, + uint32_t start_line, uint32_t count, uint32_t curr_line, uint32_t column, const char *current_line_cstr, Stream *s, const SymbolContextList *bp_locs = nullptr); Index: include/lldb/lldb-enumerations.h =================================================================== --- include/lldb/lldb-enumerations.h +++ include/lldb/lldb-enumerations.h @@ -462,6 +462,13 @@ eDynamicDontRunTarget = 2 }; +enum StopShowColumn { + eStopShowColumnAnsiOrCaret = 0, + eStopShowColumnAnsi = 1, + eStopShowColumnCaret = 2, + eStopShowColumnNone = 3 +}; + enum AccessType { eAccessNone, eAccessPublic, Index: packages/Python/lldbsuite/test/functionalities/load_unload/TestLoadUnload.py =================================================================== --- packages/Python/lldbsuite/test/functionalities/load_unload/TestLoadUnload.py +++ packages/Python/lldbsuite/test/functionalities/load_unload/TestLoadUnload.py @@ -158,6 +158,10 @@ exe = os.path.join(os.getcwd(), "a.out") self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + # Shut off ANSI color usage so we don't get ANSI escape sequences + # mixed in with stop locations. + self.dbg.SetUseColor(False) + if self.platformIsDarwin(): dylibName = 'libloadunload_d.dylib' dsymName = 'libloadunload_d.dylib.dSYM' Index: packages/Python/lldbsuite/test/lldbtest.py =================================================================== --- packages/Python/lldbsuite/test/lldbtest.py +++ packages/Python/lldbsuite/test/lldbtest.py @@ -228,6 +228,10 @@ "Unable to find '%s' within file %s" % (string_to_match, filename)) +def get_line(filename, line_number): + """Return the text of the line at the 1-based line number.""" + with io.open(filename, mode='r', encoding="utf-8") as f: + return f.readlines()[line_number - 1] def pointer_size(): """Return the pointer size of the host system.""" Index: packages/Python/lldbsuite/test/settings/TestSettings.py =================================================================== --- packages/Python/lldbsuite/test/settings/TestSettings.py +++ packages/Python/lldbsuite/test/settings/TestSettings.py @@ -527,6 +527,7 @@ "stop-disassembly-display", "stop-line-count-after", "stop-line-count-before", + "stop-show-column", "term-width", "thread-format", "use-external-editor", Index: packages/Python/lldbsuite/test/source-manager/TestSourceManager.py =================================================================== --- packages/Python/lldbsuite/test/source-manager/TestSourceManager.py +++ packages/Python/lldbsuite/test/source-manager/TestSourceManager.py @@ -10,7 +10,7 @@ """ from __future__ import print_function - +import re import lldb from lldbsuite.test.decorators import * @@ -18,19 +18,37 @@ from lldbsuite.test import lldbutil +def ansi_underline_surround_regex(inner_regex_text): + # return re.compile(r"\[4m%s\[0m" % inner_regex_text) + return "4.+\033\\[4m%s\033\\[0m" % inner_regex_text + + class SourceManagerTestCase(TestBase): mydir = TestBase.compute_mydir(__file__) + SOURCE_FILE = 'main.c' + + NO_DEBUG_INFO_TESTCASE = True + def setUp(self): # Call super's setUp(). TestBase.setUp(self) # Find the line number to break inside main(). - self.line = line_number('main.c', '// Set break point at this line.') - - @add_test_categories(['pyapi']) - def test_display_source_python(self): - """Test display of source using the SBSourceManager API.""" + self.line = line_number(self.SOURCE_FILE, '// Set break point at this line.') + + def get_expected_stop_column_number(self): + """Return the 1-based column number of the first non-whitespace + character in the breakpoint source line.""" + stop_line = get_line(self.SOURCE_FILE, self.line) + # The number of spaces that must be skipped to get to the first non- + # whitespace character --- where we expect the debugger breakpoint + # column to be --- is equal to the number of characters that get + # stripped off the front when we lstrip it, plus one to specify + # the character column after the initial whitespace. + return len(stop_line) - len(stop_line.lstrip()) + 1 + + def do_display_source_python_api(self, use_color, column_marker_regex): self.build() exe = os.path.join(os.getcwd(), "a.out") self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) @@ -39,24 +57,32 @@ self.assertTrue(target, VALID_TARGET) # Launch the process, and do not stop at the entry point. + args = None + envp = None process = target.LaunchSimple( - None, None, self.get_process_working_directory()) + args, envp, self.get_process_working_directory()) + self.assertIsNotNone(process) # # Exercise Python APIs to display source lines. # + # Setup whether we should use ansi escape sequences, including color + # and styles such as underline. + self.dbg.SetUseColor(use_color) + # Create the filespec for 'main.c'. filespec = lldb.SBFileSpec('main.c', False) source_mgr = self.dbg.GetSourceManager() # Use a string stream as the destination. stream = lldb.SBStream() - source_mgr.DisplaySourceLinesWithLineNumbers(filespec, - self.line, - 2, # context before - 2, # context after - "=>", # prefix for current line - stream) + column = self.get_expected_stop_column_number() + context_before = 2 + context_after = 2 + current_line_prefix = "=>" + source_mgr.DisplaySourceLinesWithLineNumbersAndColumn( + filespec, self.line, column, context_before, context_after, + current_line_prefix, stream) # 2 # 3 int main(int argc, char const *argv[]) { @@ -65,12 +91,28 @@ # 6 } self.expect(stream.GetData(), "Source code displayed correctly", exe=False, - patterns=['=> %d.*Hello world' % self.line]) + patterns=['=> %d.*Hello world' % self.line, + column_marker_regex]) # Boundary condition testings for SBStream(). LLDB should not crash! stream.Print(None) stream.RedirectToFile(None, True) + @add_test_categories(['pyapi']) + def test_display_source_python_dumb_terminal(self): + """Test display of source using the SBSourceManager API, using a + dumb terminal and thus no color support (the default).""" + use_color = False + self.do_display_source_python_api(use_color, r"\s+\^") + + @add_test_categories(['pyapi']) + def test_display_source_python_ansi_terminal(self): + """Test display of source using the SBSourceManager API, using a + dumb terminal and thus no color support (the default).""" + use_color = True + underline_regex = ansi_underline_surround_regex(r".") + self.do_display_source_python_api(use_color, underline_regex) + def test_move_and_then_display_source(self): """Test that target.source-map settings work by moving main.c to hidden/main.c.""" self.build() Index: scripts/interface/SBSourceManager.i =================================================================== --- scripts/interface/SBSourceManager.i +++ scripts/interface/SBSourceManager.i @@ -49,6 +49,13 @@ uint32_t context_after, const char* current_line_cstr, lldb::SBStream &s); + size_t + DisplaySourceLinesWithLineNumbersAndColumn (const lldb::SBFileSpec &file, + uint32_t line, uint32_t column, + uint32_t context_before, + uint32_t context_after, + const char* current_line_cstr, + lldb::SBStream &s); }; } // namespace lldb Index: source/API/SBSourceManager.cpp =================================================================== --- source/API/SBSourceManager.cpp +++ source/API/SBSourceManager.cpp @@ -37,7 +37,7 @@ } size_t DisplaySourceLinesWithLineNumbers(const lldb_private::FileSpec &file, - uint32_t line, + uint32_t line, uint32_t column, uint32_t context_before, uint32_t context_after, const char *current_line_cstr, @@ -48,14 +48,15 @@ lldb::TargetSP target_sp(m_target_wp.lock()); if (target_sp) { return target_sp->GetSourceManager().DisplaySourceLinesWithLineNumbers( - file, line, context_before, context_after, current_line_cstr, s); + file, line, column, context_before, context_after, current_line_cstr, + s); } else { lldb::DebuggerSP debugger_sp(m_debugger_wp.lock()); if (debugger_sp) { return debugger_sp->GetSourceManager() - .DisplaySourceLinesWithLineNumbers(file, line, context_before, - context_after, current_line_cstr, - s); + .DisplaySourceLinesWithLineNumbers(file, line, column, + context_before, context_after, + current_line_cstr, s); } } return 0; @@ -96,10 +97,20 @@ size_t SBSourceManager::DisplaySourceLinesWithLineNumbers( const SBFileSpec &file, uint32_t line, uint32_t context_before, uint32_t context_after, const char *current_line_cstr, SBStream &s) { + const uint32_t column = 0; + return DisplaySourceLinesWithLineNumbersAndColumn( + file.ref(), line, column, context_before, context_after, + current_line_cstr, s); +} + +size_t SBSourceManager::DisplaySourceLinesWithLineNumbersAndColumn( + const SBFileSpec &file, uint32_t line, uint32_t column, + uint32_t context_before, uint32_t context_after, + const char *current_line_cstr, SBStream &s) { if (m_opaque_ap.get() == NULL) return 0; return m_opaque_ap->DisplaySourceLinesWithLineNumbers( - file.ref(), line, context_before, context_after, current_line_cstr, - s.get()); + file.ref(), line, column, context_before, context_after, + current_line_cstr, s.get()); } Index: source/Commands/CommandObjectSource.cpp =================================================================== --- source/Commands/CommandObjectSource.cpp +++ source/Commands/CommandObjectSource.cpp @@ -896,8 +896,10 @@ result.AppendMessageWithFormat("File: %s\n", start_file.GetPath().c_str()); + // We don't care about the column here. + const uint32_t column = 0; return target->GetSourceManager().DisplaySourceLinesWithLineNumbers( - start_file, line_no, 0, m_options.num_lines, "", + start_file, line_no, 0, m_options.num_lines, column, "", &result.GetOutputStream(), GetBreakpointLocations()); } else { result.AppendErrorWithFormat( @@ -1150,8 +1152,13 @@ size_t lines_to_back_up = m_options.num_lines >= 10 ? 5 : m_options.num_lines / 2; + const uint32_t column = + (m_interpreter.GetDebugger().GetStopShowColumn() != + eStopShowColumnNone) + ? sc.line_entry.column + : 0; target->GetSourceManager().DisplaySourceLinesWithLineNumbers( - sc.comp_unit, sc.line_entry.line, lines_to_back_up, + sc.comp_unit, sc.line_entry.line, lines_to_back_up, column, m_options.num_lines - lines_to_back_up, "->", &result.GetOutputStream(), GetBreakpointLocations()); result.SetStatus(eReturnStatusSuccessFinishResult); @@ -1187,12 +1194,14 @@ } else m_breakpoint_locations.Clear(); + const uint32_t column = 0; if (target->GetSourceManager() .DisplaySourceLinesWithLineNumbersUsingLastFile( m_options.start_line, // Line to display m_options.num_lines, // Lines after line to UINT32_MAX, // Don't mark "line" - "", // Don't mark "line" + column, + "", // Don't mark "line" &result.GetOutputStream(), GetBreakpointLocations())) { result.SetStatus(eReturnStatusSuccessFinishResult); } @@ -1269,10 +1278,10 @@ if (m_options.num_lines == 0) m_options.num_lines = 10; - + const uint32_t column = 0; target->GetSourceManager().DisplaySourceLinesWithLineNumbers( - sc.comp_unit, m_options.start_line, 0, m_options.num_lines, "", - &result.GetOutputStream(), GetBreakpointLocations()); + sc.comp_unit, m_options.start_line, 0, m_options.num_lines, + column, "", &result.GetOutputStream(), GetBreakpointLocations()); result.SetStatus(eReturnStatusSuccessFinishResult); } else { Index: source/Core/Debugger.cpp =================================================================== --- source/Core/Debugger.cpp +++ source/Core/Debugger.cpp @@ -140,6 +140,24 @@ // {${function.initial-function}{${module.file.basename}`}{${function.name-without-args}}:\n}{${function.changed}\n{${module.file.basename}`}{${function.name-without-args}}:\n}{${current-pc-arrow} // }{${addr-file-or-load}}: +#define DEFAULT_STOP_SHOW_COLUMN_ANSI_PREFIX "${ansi.underline}" +#define DEFAULT_STOP_SHOW_COLUMN_ANSI_SUFFIX "${ansi.normal}" + +static OptionEnumValueElement s_stop_show_column_values[] = { + {eStopShowColumnAnsiOrCaret, "ansi-or-caret", + "Highlight the stop column with ANSI terminal codes when color/ANSI mode " + "is enabled; otherwise, fall back to using a text-only caret (^) as if " + "\"caret-only\" mode was selected."}, + {eStopShowColumnAnsi, "ansi", "Highlight the stop column with ANSI " + "terminal codes when running LLDB with " + "color/ANSI enabled."}, + {eStopShowColumnCaret, "caret", + "Highlight the stop column with a caret character (^) underneath the stop " + "column. This method introduces a new line in source listings that " + "display thread stop locations."}, + {eStopShowColumnNone, "none", "Do not highlight the stop column."}, + {0, nullptr, nullptr}}; + static PropertyDefinition g_properties[] = { {"auto-confirm", OptionValue::eTypeBoolean, true, false, nullptr, nullptr, "If true all confirmation prompts will receive their default reply."}, @@ -173,6 +191,20 @@ {"stop-line-count-before", OptionValue::eTypeSInt64, true, 3, nullptr, nullptr, "The number of sources lines to display that come before the " "current source line when displaying a stopped context."}, + {"stop-show-column", OptionValue::eTypeEnum, false, + eStopShowColumnAnsiOrCaret, nullptr, s_stop_show_column_values, + "If true, LLDB will use the column information from the debug info to " + "mark the current position when displaying a stopped context."}, + {"stop-show-column-ansi-prefix", OptionValue::eTypeFormatEntity, true, 0, + DEFAULT_STOP_SHOW_COLUMN_ANSI_PREFIX, nullptr, + "When displaying the column marker in a color-enabled (i.e. ANSI) " + "terminal, use the ANSI terminal code specified in this format at the " + "immediately before the column to be marked."}, + {"stop-show-column-ansi-suffix", OptionValue::eTypeFormatEntity, true, 0, + DEFAULT_STOP_SHOW_COLUMN_ANSI_SUFFIX, nullptr, + "When displaying the column marker in a color-enabled (i.e. ANSI) " + "terminal, use the ANSI terminal code specified in this format " + "immediately after the column to be marked."}, {"term-width", OptionValue::eTypeSInt64, true, 80, nullptr, nullptr, "The maximum number of columns to use for displaying text."}, {"thread-format", OptionValue::eTypeFormatEntity, true, 0, @@ -210,6 +242,9 @@ ePropertyStopDisassemblyDisplay, ePropertyStopLineCountAfter, ePropertyStopLineCountBefore, + ePropertyStopShowColumn, + ePropertyStopShowColumnAnsiPrefix, + ePropertyStopShowColumnAnsiSuffix, ePropertyTerminalWidth, ePropertyThreadFormat, ePropertyUseExternalEditor, @@ -371,6 +406,22 @@ return ret; } +StopShowColumn Debugger::GetStopShowColumn() const { + const uint32_t idx = ePropertyStopShowColumn; + return (lldb::StopShowColumn)m_collection_sp->GetPropertyAtIndexAsEnumeration( + nullptr, idx, g_properties[idx].default_uint_value); +} + +const FormatEntity::Entry *Debugger::GetStopShowColumnAnsiPrefix() const { + const uint32_t idx = ePropertyStopShowColumnAnsiPrefix; + return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx); +} + +const FormatEntity::Entry *Debugger::GetStopShowColumnAnsiSuffix() const { + const uint32_t idx = ePropertyStopShowColumnAnsiSuffix; + return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx); +} + uint32_t Debugger::GetStopSourceLineCount(bool before) const { const uint32_t idx = before ? ePropertyStopLineCountBefore : ePropertyStopLineCountAfter; Index: source/Core/Disassembler.cpp =================================================================== --- source/Core/Disassembler.cpp +++ source/Core/Disassembler.cpp @@ -289,6 +289,9 @@ func_decl_file == prologue_end_line.original_file) { decl_line.file = func_decl_file; decl_line.line = func_decl_line; + // TODO do we care about column on these entries? If so, we need to + // plumb that through GetStartLineSourceInfo. + decl_line.column = 0; } } return decl_line; @@ -448,6 +451,7 @@ SourceLine this_line; this_line.file = sc.line_entry.file; this_line.line = sc.line_entry.line; + this_line.column = sc.line_entry.column; if (ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, this_line) == false) AddLineToSourceLineTables(this_line, source_lines_seen); @@ -613,7 +617,7 @@ line_highlight = "**"; } source_manager.DisplaySourceLinesWithLineNumbers( - ln.file, ln.line, 0, 0, line_highlight, &strm); + ln.file, ln.line, ln.column, 0, 0, line_highlight, &strm); } if (source_lines_to_display.print_source_context_end_eol) strm.EOL(); Index: source/Core/SourceManager.cpp =================================================================== --- source/Core/SourceManager.cpp +++ source/Core/SourceManager.cpp @@ -22,6 +22,7 @@ #include "lldb/Symbol/Function.h" #include "lldb/Symbol/SymbolContext.h" #include "lldb/Target/Target.h" +#include "lldb/Utility/AnsiTerminal.h" using namespace lldb; using namespace lldb_private; @@ -71,7 +72,10 @@ // If file_sp is no good or it points to a non-existent file, reset it. if (!file_sp || !file_sp->GetFileSpec().Exists()) { - file_sp.reset(new File(file_spec, target_sp.get())); + if (target_sp) + file_sp.reset(new File(file_spec, target_sp.get())); + else + file_sp.reset(new File(file_spec, debugger_sp)); if (debugger_sp) debugger_sp->GetSourceFileCache().AddSourceFile(file_sp); @@ -79,8 +83,44 @@ return file_sp; } +static bool should_show_stop_column_with_ansi(DebuggerSP debugger_sp) { + // We don't use ANSI stop column formatting if we can't lookup values from + // the debugger. + if (!debugger_sp) + return false; + + // We don't use ANSI stop column formatting if the debugger doesn't think + // it should be using color. + if (!debugger_sp->GetUseColor()) + return false; + + // We only use ANSI stop column formatting if we're either supposed to show + // ANSI where available (which we know we have when we get to this point), or + // if we're only supposed to use ANSI. + const auto value = debugger_sp->GetStopShowColumn(); + return ((value == eStopShowColumnAnsiOrCaret) || + (value == eStopShowColumnAnsi)); +} + +static bool should_show_stop_column_with_caret(DebuggerSP debugger_sp) { + // We don't use text-based stop column formatting if we can't lookup values + // from the debugger. + if (!debugger_sp) + return false; + + // If we're asked to show the first available of ANSI or caret, then + // we do show the caret when ANSI is not available. + const auto value = debugger_sp->GetStopShowColumn(); + if ((value == eStopShowColumnAnsiOrCaret) && !debugger_sp->GetUseColor()) + return true; + + // The only other time we use caret is if we're explicitly asked to show + // caret. + return value == eStopShowColumnCaret; +} + size_t SourceManager::DisplaySourceLinesWithLineNumbersUsingLastFile( - uint32_t start_line, uint32_t count, uint32_t curr_line, + uint32_t start_line, uint32_t count, uint32_t curr_line, uint32_t column, const char *current_line_cstr, Stream *s, const SymbolContextList *bp_locs) { if (count == 0) @@ -123,7 +163,20 @@ return_value += s->Printf("%s%2.2s %-4u\t", prefix, line == curr_line ? current_line_cstr : "", line); - size_t this_line_size = m_last_file_sp->DisplaySourceLines(line, 0, 0, s); + size_t this_line_size = m_last_file_sp->DisplaySourceLines( + line, line == curr_line ? column : 0, 0, 0, s); + if (column != 0 && line == curr_line && + should_show_stop_column_with_caret(m_debugger_wp.lock())) { + // Display caret cursor. + std::string src_line; + m_last_file_sp->GetLine(line, src_line); + return_value += s->Printf(" \t"); + // Insert a space for every non-tab character in the source line. + for (int i = 0; i < column - 1 && i < src_line.length(); ++i) + return_value += s->PutChar(src_line[i] == '\t' ? '\t' : ' '); + // Now add the caret. + return_value += s->Printf("^\n"); + } if (this_line_size == 0) { m_last_line = UINT32_MAX; break; @@ -135,8 +188,9 @@ } size_t SourceManager::DisplaySourceLinesWithLineNumbers( - const FileSpec &file_spec, uint32_t line, uint32_t context_before, - uint32_t context_after, const char *current_line_cstr, Stream *s, + const FileSpec &file_spec, uint32_t line, uint32_t column, + uint32_t context_before, uint32_t context_after, + const char *current_line_cstr, Stream *s, const SymbolContextList *bp_locs) { FileSP file_sp(GetFile(file_spec)); @@ -153,7 +207,7 @@ m_last_file_sp = file_sp; } return DisplaySourceLinesWithLineNumbersUsingLastFile( - start_line, count, line, current_line_cstr, s, bp_locs); + start_line, count, line, column, current_line_cstr, s, bp_locs); } size_t SourceManager::DisplayMoreWithLineNumbers( @@ -193,8 +247,9 @@ } else m_last_line = 1; + const uint32_t column = 0; return DisplaySourceLinesWithLineNumbersUsingLastFile( - m_last_line, m_last_count, UINT32_MAX, "", s, bp_locs); + m_last_line, m_last_count, UINT32_MAX, column, "", s, bp_locs); } return 0; } @@ -272,10 +327,27 @@ match_lines); } +SourceManager::File::File(const FileSpec &file_spec, + lldb::DebuggerSP debugger_sp) + : m_file_spec_orig(file_spec), m_file_spec(file_spec), + m_mod_time(file_spec.GetModificationTime()), m_source_map_mod_id(0), + m_data_sp(), m_offsets(), m_debugger_wp(debugger_sp) { + CommonInitializer(file_spec, nullptr); +} + SourceManager::File::File(const FileSpec &file_spec, Target *target) : m_file_spec_orig(file_spec), m_file_spec(file_spec), m_mod_time(file_spec.GetModificationTime()), m_source_map_mod_id(0), - m_data_sp(), m_offsets() { + m_data_sp(), m_offsets(), + m_debugger_wp(target ? target->GetDebugger().shared_from_this() + : DebuggerSP()) { + CommonInitializer(file_spec, target); +} + +SourceManager::File::~File() {} + +void SourceManager::File::CommonInitializer(const FileSpec &file_spec, + Target *target) { if (!m_mod_time.IsValid()) { if (target) { m_source_map_mod_id = target->GetSourcePathMap().GetModificationID(); @@ -337,8 +409,6 @@ m_data_sp = m_file_spec.ReadFileContents(); } -SourceManager::File::~File() {} - uint32_t SourceManager::File::GetLineOffset(uint32_t line) { if (line == 0) return UINT32_MAX; @@ -418,10 +488,14 @@ } } -size_t SourceManager::File::DisplaySourceLines(uint32_t line, +size_t SourceManager::File::DisplaySourceLines(uint32_t line, uint32_t column, uint32_t context_before, uint32_t context_after, Stream *s) { + // Nothing to write if there's no stream. + if (!s) + return 0; + // Sanity check m_data_sp before proceeding. if (!m_data_sp) return 0; @@ -440,7 +514,61 @@ if (start_line_offset < end_line_offset) { size_t count = end_line_offset - start_line_offset; const uint8_t *cstr = m_data_sp->GetBytes() + start_line_offset; - bytes_written = s->Write(cstr, count); + + bool displayed_line = false; + + if (column && (column < count)) { + auto debugger_sp = m_debugger_wp.lock(); + if (should_show_stop_column_with_ansi(debugger_sp) && debugger_sp) { + // Check if we have any ANSI codes with which to mark this column. + // If not, no need to do this work. + auto ansi_prefix_entry = debugger_sp->GetStopShowColumnAnsiPrefix(); + auto ansi_suffix_entry = debugger_sp->GetStopShowColumnAnsiSuffix(); + + // We only bother breaking up the line to format the marked column if + // there is any marking specified on both sides of the marked column. + // In ANSI-terminal-sequence land, there must be a post if there is a + // pre format, and vice versa. + if (ansi_prefix_entry && ansi_suffix_entry) { + // Mark the current column with the desired escape sequence for + // formatting the column (e.g. underline, inverse, etc.) + + // First print the part before the column to mark. + bytes_written = s->Write(cstr, column - 1); + + // Write the pre escape sequence. + const SymbolContext *sc = nullptr; + const ExecutionContext *exe_ctx = nullptr; + const Address addr = LLDB_INVALID_ADDRESS; + ValueObject *valobj = nullptr; + const bool function_changed = false; + const bool initial_function = false; + + FormatEntity::Format(*ansi_prefix_entry, *s, sc, exe_ctx, &addr, + valobj, function_changed, initial_function); + + // Write the marked column. + bytes_written += s->Write(cstr + column - 1, 1); + + // Write the post escape sequence. + FormatEntity::Format(*ansi_suffix_entry, *s, sc, exe_ctx, &addr, + valobj, function_changed, initial_function); + + // And finish up with the rest of the line. + bytes_written += s->Write(cstr + column, count - column); + + // Keep track of the fact that we just wrote the line. + displayed_line = true; + } + } + } + + // If we didn't end up displaying the line with ANSI codes for whatever + // reason, display it now sans codes. + if (!displayed_line) + bytes_written = s->Write(cstr, count); + + // Ensure we get an end of line character one way or another. if (!is_newline_char(cstr[count - 1])) bytes_written += s->EOL(); } Index: source/Target/StackFrame.cpp =================================================================== --- source/Target/StackFrame.cpp +++ source/Target/StackFrame.cpp @@ -1855,7 +1855,8 @@ size_t num_lines = target->GetSourceManager().DisplaySourceLinesWithLineNumbers( m_sc.line_entry.file, m_sc.line_entry.line, - source_lines_before, source_lines_after, "->", &strm); + m_sc.line_entry.column, source_lines_before, + source_lines_after, "->", &strm); if (num_lines != 0) have_source = true; // TODO: Give here a one time warning if source file is missing.