Files
luma/Luma/ThreadSidebar.swift
Ole André Vadla Ravnås 38e5198209 Move modules and threads into the sidebar
Drop the modules/threads tabs; show both as collapsible sidebar
groups mirroring the hooks UX: a handful of top picks plus a
browse popover for the rest. Default threads to collapsed.

Revert the eager re-analysis of persisted modules on attach; keep
disassembly analysis lazy and add a per-module Analyze action and
status indicator instead.

Share one sidebar-row and one browser-popover component across
hooks, modules, and threads in both the SwiftUI and GTK frontends.
2026-06-27 14:53:01 +02:00

93 lines
3.1 KiB
Swift

import LumaCore
import SwiftUI
struct ThreadSidebarChildren: View {
let sessionID: UUID
let engine: Engine
@Binding var selection: SidebarItemID?
var body: some View {
let threads = engine.session(id: sessionID)?.lastKnownThreads ?? []
let highlights = threads.sidebarHighlights(selectedID: selectedThreadID)
ForEach(highlights) { thread in
ThreadSidebarRow(sessionID: sessionID, thread: thread, engine: engine, selection: $selection)
.tag(SidebarItemID.thread(sessionID, thread.id))
}
if threads.count > highlights.count {
SidebarBrowseAllRow(count: threads.count) { dismiss in
ThreadBrowserPopover(
sessionID: sessionID,
threads: threads,
selection: $selection,
onDismiss: dismiss
)
}
}
}
private var selectedThreadID: ProcessThread.ID? {
if case .thread(let sid, let tid) = selection, sid == sessionID { return tid }
return engine.lastSelectedThreadID(for: sessionID)
}
}
private struct ThreadSidebarRow: View {
let sessionID: UUID
let thread: ProcessThread
let engine: Engine
@Binding var selection: SidebarItemID?
var body: some View {
SidebarFeatureRowLabel(
icon: { Image(systemName: "cpu").font(.system(size: 11)) },
title: thread.name ?? "tid \(thread.id)",
help: "tid \(thread.id)"
)
.contextMenu {
let actions = engine.threadActions(sessionID: sessionID, thread: thread)
ForEach(actions) { action in
Button(role: action.role == .destructive ? .destructive : nil) {
Task { @MainActor in
if let target = await action.perform() {
selection = SidebarItemID(navigationTarget: target)
}
}
} label: {
if let icon = action.systemImage {
Label(action.title, systemImage: icon)
} else {
Text(action.title)
}
}
}
}
}
}
struct ThreadBrowserPopover: View {
let sessionID: UUID
let threads: [ProcessThread]
@Binding var selection: SidebarItemID?
let onDismiss: () -> Void
var body: some View {
SidebarBrowserPopover(
placeholder: "Filter threads",
emptyMessage: "No matching threads",
items: threads,
groupName: { $0.name == nil ? "Unnamed" : "Named" },
title: { $0.name ?? "tid \($0.id)" },
help: { "tid \($0.id)" },
isDimmed: { _ in false },
matches: { thread, query in
(thread.name?.localizedCaseInsensitiveContains(query) ?? false)
|| String(thread.id).localizedCaseInsensitiveContains(query)
},
onChoose: { thread in
selection = .thread(sessionID, thread.id)
onDismiss()
}
)
}
}