diff --git a/lldb/source/Core/IOHandlerCursesGUI.cpp b/lldb/source/Core/IOHandlerCursesGUI.cpp --- a/lldb/source/Core/IOHandlerCursesGUI.cpp +++ b/lldb/source/Core/IOHandlerCursesGUI.cpp @@ -1608,6 +1608,8 @@ virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0; virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0; + virtual void SetDefaultSelectionIfNeeded(TreeItem &root, int *selection_index, + TreeItem *selected_item) = 0; virtual bool TreeDelegateItemSelected( TreeItem &item) = 0; // Return true if we need to update views }; @@ -1619,7 +1621,11 @@ TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children) : m_parent(parent), m_delegate(delegate), m_user_data(nullptr), m_identifier(0), m_row_idx(-1), m_children(), - m_might_have_children(might_have_children), m_is_expanded(false) {} + m_might_have_children(might_have_children), m_is_expanded(false) { + // Expand root tree items by default. + if (m_parent == nullptr) + m_is_expanded = true; + } TreeItem &operator=(const TreeItem &rhs) { if (this != &rhs) { @@ -1848,6 +1854,8 @@ const int num_visible_rows = NumVisibleRows(); m_num_rows = 0; m_root.CalculateRowIndexes(m_num_rows); + m_delegate_sp->SetDefaultSelectionIfNeeded(m_root, &m_selected_row_idx, + m_selected_item); // If we unexpanded while having something selected our total number of // rows is less than the num visible rows, then make sure we show all the @@ -2031,6 +2039,11 @@ // No children for frames yet... } + void SetDefaultSelectionIfNeeded(TreeItem &root, int *selection_index, + TreeItem *selected_item) override { + return; + } + bool TreeDelegateItemSelected(TreeItem &item) override { Thread *thread = (Thread *)item.GetUserData(); if (thread) { @@ -2117,6 +2130,11 @@ item.ClearChildren(); } + void SetDefaultSelectionIfNeeded(TreeItem &root, int *selection_index, + TreeItem *selected_item) override { + return; + } + bool TreeDelegateItemSelected(TreeItem &item) override { ProcessSP process_sp = GetProcess(); if (process_sp && process_sp->IsAlive()) { @@ -2149,7 +2167,7 @@ public: ThreadsTreeDelegate(Debugger &debugger) : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger), - m_stop_id(UINT32_MAX) { + m_stop_id(UINT32_MAX), m_need_to_set_default_selection(false) { FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", m_format); } @@ -2185,6 +2203,7 @@ return; // Children are already up to date m_stop_id = stop_id; + m_need_to_set_default_selection = true; if (!m_thread_delegate_sp) { // Always expand the thread item the first time we show it @@ -2196,11 +2215,15 @@ TreeItem t(&item, *m_thread_delegate_sp, false); ThreadList &threads = process_sp->GetThreadList(); std::lock_guard guard(threads.GetMutex()); + ThreadSP selected_thread = threads.GetSelectedThread(); size_t num_threads = threads.GetSize(); item.Resize(num_threads, t); for (size_t i = 0; i < num_threads; ++i) { - item[i].SetIdentifier(threads.GetThreadAtIndex(i)->GetID()); + ThreadSP thread = threads.GetThreadAtIndex(i); + item[i].SetIdentifier(thread->GetID()); item[i].SetMightHaveChildren(true); + if (selected_thread->GetID() == thread->GetID()) + item[i].Expand(); } return; } @@ -2208,12 +2231,41 @@ item.ClearChildren(); } + void SetDefaultSelectionIfNeeded(TreeItem &root, int *selection_index, + TreeItem *selected_item) override { + if (!m_need_to_set_default_selection) + return; + + ProcessSP process_sp = GetProcess(); + if (!(process_sp && process_sp->IsAlive())) + return; + + StateType state = process_sp->GetState(); + if (!StateIsStoppedState(state, true)) + return; + + ThreadList &threads = process_sp->GetThreadList(); + std::lock_guard guard(threads.GetMutex()); + ThreadSP selected_thread = threads.GetSelectedThread(); + size_t num_threads = threads.GetSize(); + for (size_t i = 0; i < num_threads; ++i) { + ThreadSP thread = threads.GetThreadAtIndex(i); + if (selected_thread->GetID() == thread->GetID()) { + selected_item = &root[i][thread->GetSelectedFrameIndex()]; + *selection_index = selected_item->GetRowIndex(); + m_need_to_set_default_selection = false; + return; + } + } + } + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } protected: std::shared_ptr m_thread_delegate_sp; Debugger &m_debugger; uint32_t m_stop_id; + bool m_need_to_set_default_selection; FormatEntity::Entry m_format; }; diff --git a/lldb/test/API/commands/gui/expand-threads-tree/Makefile b/lldb/test/API/commands/gui/expand-threads-tree/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/gui/expand-threads-tree/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c +ENABLE_THREADS := YES +include Makefile.rules diff --git a/lldb/test/API/commands/gui/expand-threads-tree/TestGuiExpandThreadsTree.py b/lldb/test/API/commands/gui/expand-threads-tree/TestGuiExpandThreadsTree.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/gui/expand-threads-tree/TestGuiExpandThreadsTree.py @@ -0,0 +1,55 @@ +""" +Test the 'gui' default thread tree expansion. +The root process tree item and the tree item corresponding to the selected +thread should be expanded by default. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test.lldbpexpect import PExpectTest + +class TestGuiExpandThreadsTree(PExpectTest): + + mydir = TestBase.compute_mydir(__file__) + + # PExpect uses many timeouts internally and doesn't play well + # under ASAN on a loaded machine.. + @skipIfAsan + @skipIfCursesSupportMissing + def test_gui(self): + self.build() + + self.launch(executable=self.getBuildArtifact("a.out"), dimensions=(100,500)) + self.expect("breakpoint set -r thread_start_routine", substrs=["Breakpoint 1", "address ="]) + self.expect("run", substrs=["stop reason ="]) + + escape_key = chr(27).encode() + + # Start the GUI and close the welcome window. + self.child.sendline("gui") + self.child.send(escape_key) + self.child.expect_exact("Threads") + + # The thread running thread_start_routine should be expanded. + self.child.expect_exact("frame #0: thread_start_routine") + + # Exit GUI. + self.child.send(escape_key) + self.expect_prompt() + + # Select the main thread. + self.child.sendline("thread select 1") + + # Start the GUI. + self.child.sendline("gui") + self.child.expect_exact("Threads") + + # The main thread should be expanded. + self.child.expect("frame #\d+: main") + + # Quit the GUI + self.child.send(escape_key) + + self.expect_prompt() + self.quit() diff --git a/lldb/test/API/commands/gui/expand-threads-tree/main.c b/lldb/test/API/commands/gui/expand-threads-tree/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/gui/expand-threads-tree/main.c @@ -0,0 +1,10 @@ +#include + +void *thread_start_routine(void *arg) { return NULL; } + +int main() { + pthread_t thread; + pthread_create(&thread, NULL, thread_start_routine, NULL); + pthread_join(thread, NULL); + return 0; +}