| # This file implements synthetic children providers and summaries for various Dear ImGui types for LLDB. |
| # LLDB is used by Xcode, Android Studio, and may be used from VS Code, C++Builder, CLion, Eclipse etc. |
| |
| # |
| # Useful links/documentation related to the feature: |
| # - https://lldb.llvm.org/use/variable.html#summary-strings |
| # - https://lldb.llvm.org/use/variable.html#synthetic-children |
| # - https://lldb.llvm.org/python_reference/lldb-module.html |
| # |
| # To use it in a debug session: |
| # > (lldb) command script import <path-to-this-file> |
| # |
| # Alternatively you may include the above command in your ~/.lldbinit file to have the formatters |
| # available in all future sessions |
| |
| import lldb |
| |
| class ArraySynthBase(object): |
| """ |
| Helper baseclass aimed to reduce the boilerplate needed for "array-like" containers |
| """ |
| |
| def __init__(self, valobj, internal_dict): |
| self.valobj = valobj |
| |
| def bind_to(self, pointer, size): |
| array_p = pointer.GetType().GetPointeeType().GetArrayType(size).GetPointerType() |
| self.array = pointer.Cast(array_p).Dereference() |
| |
| def update(self): |
| self.array = self.valobj |
| |
| def num_children(self, max_children): |
| return self.array.GetNumChildren(max_children) |
| |
| def get_child_index(self, name): |
| return self.array.GetIndexOfChildWithName(name) |
| |
| def get_child_at_index(self, index): |
| return self.array.GetChildAtIndex(index) |
| |
| def has_children(self): |
| return self.array.MightHaveChildren() |
| |
| def get_value(self): |
| return self.array |
| |
| class ImVectorSynth(ArraySynthBase): |
| def update(self): |
| self.size = self.valobj.GetChildMemberWithName("Size").GetValueAsUnsigned() |
| self.capacity = self.valobj.GetChildMemberWithName("Capacity").GetValueAsUnsigned() |
| |
| data = self.valobj.GetChildMemberWithName("Data") |
| |
| self.bind_to(data, self.size) |
| |
| def get_summary(self): |
| return f"Size={self.size} Capacity={self.capacity}" |
| |
| class ImSpanSynth(ArraySynthBase): |
| def update(self): |
| data = self.valobj.GetChildMemberWithName("Data") |
| end = self.valobj.GetChildMemberWithName("DataEnd") |
| |
| element_size = data.GetType().GetPointeeType().GetByteSize() |
| array_size = end.GetValueAsUnsigned() - data.GetValueAsUnsigned() |
| |
| self.size = int(array_size / element_size) |
| |
| self.bind_to(data, self.size) |
| |
| def get_summary(self): |
| return f"Size={self.size}" |
| |
| class ImRectSummary(object): |
| def __init__(self, valobj, internal_dict): |
| self.valobj = valobj |
| |
| def update(self): |
| pass |
| |
| def get_summary(self): |
| min = self.valobj.GetChildMemberWithName("Min") |
| max = self.valobj.GetChildMemberWithName("Max") |
| |
| minX = float(min.GetChildMemberWithName("x").GetValue()) |
| minY = float(min.GetChildMemberWithName("y").GetValue()) |
| |
| maxX = float(max.GetChildMemberWithName("x").GetValue()) |
| maxY = float(max.GetChildMemberWithName("y").GetValue()) |
| |
| return f"Min=({minX}, {minY}) Max=({maxX}, {maxY}) Size=({maxX - minX}, {maxY - minY})" |
| |
| def get_active_enum_flags(valobj): |
| flag_set = set() |
| |
| enum_name = valobj.GetType().GetName() + "_" |
| enum_type = valobj.GetTarget().FindFirstType(enum_name) |
| |
| if not enum_type.IsValid(): |
| return flag_set |
| |
| enum_members = enum_type.GetEnumMembers() |
| value = valobj.GetValueAsUnsigned() |
| |
| for i in range(0, enum_members.GetSize()): |
| member = enum_members.GetTypeEnumMemberAtIndex(i) |
| |
| if value & member.GetValueAsUnsigned(): |
| flag_set.add(member.GetName().removeprefix(enum_name)) |
| |
| return flag_set |
| |
| class ImGuiWindowSummary(object): |
| def __init__(self, valobj, internal_dict): |
| self.valobj = valobj |
| |
| def update(self): |
| pass |
| |
| def get_summary(self): |
| name = self.valobj.GetChildMemberWithName("Name").GetSummary() |
| |
| active = self.valobj.GetChildMemberWithName("Active").GetValueAsUnsigned() != 0 |
| was_active = self.valobj.GetChildMemberWithName("WasActive").GetValueAsUnsigned() != 0 |
| hidden = self.valobj.GetChildMemberWithName("Hidden") != 0 |
| |
| flags = get_active_enum_flags(self.valobj.GetChildMemberWithName("Flags")) |
| |
| active = 1 if active or was_active else 0 |
| child = 1 if "ChildWindow" in flags else 0 |
| popup = 1 if "Popup" in flags else 0 |
| hidden = 1 if hidden else 0 |
| |
| return f"Name {name} Active {active} Child {child} Popup {popup} Hidden {hidden}" |
| |
| |
| def __lldb_init_module(debugger, internal_dict): |
| """ |
| This function will be automatically called by LLDB when the module is loaded, here |
| we register the various synthetics/summaries we have build before |
| """ |
| |
| category_name = "imgui" |
| category = debugger.GetCategory(category_name) |
| |
| # Make sure we don't accidentally keep accumulating languages or override the user's |
| # category enablement in Xcode, where lldb-rpc-server loads this file once for eac |
| # debugging session |
| if not category.IsValid(): |
| category = debugger.CreateCategory(category_name) |
| category.AddLanguage(lldb.eLanguageTypeC_plus_plus) |
| category.SetEnabled(True) |
| |
| def add_summary(typename, impl): |
| summary = None |
| |
| if isinstance(impl, str): |
| summary = lldb.SBTypeSummary.CreateWithSummaryString(impl) |
| summary.SetOptions(lldb.eTypeOptionCascade) |
| else: |
| # Unfortunately programmatic summary string generation is an entirely different codepath |
| # in LLDB. Register a convenient trampoline function which makes it look like it's part |
| # of the SyntheticChildrenProvider contract |
| summary = lldb.SBTypeSummary.CreateWithScriptCode(f''' |
| synth = {impl.__module__}.{impl.__qualname__}(valobj.GetNonSyntheticValue(), internal_dict) |
| synth.update() |
| |
| return synth.get_summary() |
| ''') |
| summary.SetOptions(lldb.eTypeOptionCascade | lldb.eTypeOptionFrontEndWantsDereference) |
| |
| category.AddTypeSummary(lldb.SBTypeNameSpecifier(typename, True), summary) |
| |
| def add_synthetic(typename, impl): |
| add_summary(typename, impl) |
| |
| synthetic = lldb.SBTypeSynthetic.CreateWithClassName(f"{impl.__module__}.{impl.__qualname__}") |
| synthetic.SetOptions(lldb.eTypeOptionCascade | lldb.eTypeOptionFrontEndWantsDereference) |
| |
| category.AddTypeSynthetic(lldb.SBTypeNameSpecifier(typename, True), synthetic) |
| |
| add_synthetic("^ImVector<.+>$", ImVectorSynth) |
| add_synthetic("^ImSpan<.+>$", ImSpanSynth) |
| |
| add_summary("^ImVec2$", "x=${var.x} y=${var.y}") |
| add_summary("^ImVec4$", "x=${var.x} y=${var.y} z=${var.z} w=${var.w}") |
| add_summary("^ImRect$", ImRectSummary) |
| add_summary("^ImGuiWindow$", ImGuiWindowSummary) |