309 lines
12 KiB
C++
309 lines
12 KiB
C++
#pragma once
|
|
#include "../EditorState.h"
|
|
#include "../EditorUtils.h"
|
|
|
|
static void renderOutlinePanel(EditorState& state) {
|
|
if (!state.ui.showOutline) return;
|
|
if (state.ui.focusTarget == FocusRegion::Side) {
|
|
ImGui::SetNextWindowFocus();
|
|
state.ui.focusTarget = FocusRegion::None;
|
|
}
|
|
ImGui::Begin("Outline", &state.ui.showOutline);
|
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
|
|
state.ui.focusedRegion = FocusRegion::Side;
|
|
}
|
|
queueFeatureHint(state.featureHints,
|
|
"hint.outline",
|
|
"Tip: Use the Outline to jump between symbols quickly.");
|
|
ImGui::PushFont(state.uiFont);
|
|
ImGui::SetNextItemWidth(-1.0f);
|
|
ImGui::InputText("Filter", state.outlineFilter, sizeof(state.outlineFilter));
|
|
ImGui::Separator();
|
|
|
|
std::string filter = toLowerCopy(state.outlineFilter);
|
|
if (!state.active()) {
|
|
ImGui::TextDisabled("(no file)");
|
|
} else {
|
|
std::vector<LSPClient::DocumentSymbol> lspSymbols =
|
|
state.lsp ? state.lsp->getDocumentSymbols() : std::vector<LSPClient::DocumentSymbol>{};
|
|
|
|
if (!lspSymbols.empty()) {
|
|
int outlineId = 0;
|
|
std::function<void(const LSPClient::DocumentSymbol&)> renderLsp;
|
|
renderLsp = [&](const LSPClient::DocumentSymbol& sym) {
|
|
if (!lspSymbolMatchesFilter(sym, filter)) return;
|
|
ImGui::PushID(outlineId++);
|
|
std::string label = std::string(lspSymbolKindLabel(sym.kind)) + ": " + sym.name;
|
|
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanFullWidth;
|
|
if (sym.children.empty()) flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
|
bool open = ImGui::TreeNodeEx(label.c_str(), flags);
|
|
if (ImGui::IsItemClicked()) {
|
|
state.jumpTo(state.active(), sym.selectionRange.start.line,
|
|
sym.selectionRange.start.character);
|
|
}
|
|
if (!sym.children.empty() && open) {
|
|
for (const auto& child : sym.children) {
|
|
renderLsp(child);
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
ImGui::PopID();
|
|
};
|
|
for (const auto& sym : lspSymbols) renderLsp(sym);
|
|
} else if (state.isStructured()) {
|
|
std::vector<OutlineItem> outline;
|
|
collectOutlineFromAST(state.activeAST(), outline);
|
|
if (outline.empty()) {
|
|
ImGui::TextDisabled("(no symbols)");
|
|
} else {
|
|
int outlineId = 0;
|
|
std::function<void(const OutlineItem&)> renderItem;
|
|
renderItem = [&](const OutlineItem& item) {
|
|
if (!outlineMatchesFilter(item, filter)) return;
|
|
ImGui::PushID(outlineId++);
|
|
std::string label = item.kind + ": " + item.name;
|
|
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanFullWidth;
|
|
if (item.children.empty()) flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
|
bool open = ImGui::TreeNodeEx(label.c_str(), flags);
|
|
if (ImGui::IsItemClicked() && item.line >= 0) {
|
|
state.jumpTo(state.active(), item.line, item.col);
|
|
}
|
|
if (!item.children.empty() && open) {
|
|
for (const auto& child : item.children) {
|
|
renderItem(child);
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
ImGui::PopID();
|
|
};
|
|
for (const auto& item : outline) renderItem(item);
|
|
}
|
|
} else {
|
|
ImGui::TextDisabled("(no symbols)");
|
|
}
|
|
}
|
|
ImGui::PopFont();
|
|
ImGui::End();
|
|
}
|
|
|
|
static void renderDependenciesPanel(EditorState& state) {
|
|
if (!state.library.showDependencyPanel) return;
|
|
if (state.ui.focusTarget == FocusRegion::Side) {
|
|
ImGui::SetNextWindowFocus();
|
|
state.ui.focusTarget = FocusRegion::None;
|
|
}
|
|
ImGui::Begin("Dependencies", &state.library.showDependencyPanel);
|
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
|
|
state.ui.focusedRegion = FocusRegion::Side;
|
|
}
|
|
ImGui::PushFont(state.uiFont);
|
|
renderDependencyPanel(state.library.dependencyPanel,
|
|
state.workspaceRoot,
|
|
state.notifications,
|
|
state.library.vulnDb);
|
|
ImGui::PopFont();
|
|
ImGui::End();
|
|
state.library.primitives.setVulnerableLibraries(
|
|
state.library.dependencyPanel.vulnerableLibraries);
|
|
if (state.library.dependencyPanel.needsIndex) {
|
|
state.requestLibraryIndex();
|
|
state.library.dependencyPanel.needsIndex = false;
|
|
}
|
|
}
|
|
|
|
static void renderLibrariesPanel(EditorState& state) {
|
|
if (!state.library.showLibraryBrowserPanel) return;
|
|
if (state.ui.focusTarget == FocusRegion::Side) {
|
|
ImGui::SetNextWindowFocus();
|
|
state.ui.focusTarget = FocusRegion::None;
|
|
}
|
|
ImGui::Begin("Libraries", &state.library.showLibraryBrowserPanel);
|
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
|
|
state.ui.focusedRegion = FocusRegion::Side;
|
|
}
|
|
ImGui::PushFont(state.uiFont);
|
|
std::string insertText;
|
|
std::string insertLibrary;
|
|
if (renderLibraryBrowser(state.library.libraryBrowser,
|
|
state.activeAST(),
|
|
state.library.libraryIndex,
|
|
insertText,
|
|
insertLibrary,
|
|
state.notifications)) {
|
|
state.ensureImportForSymbol(insertLibrary, insertText);
|
|
state.insertTextAtCursor(insertText);
|
|
}
|
|
ImGui::PopFont();
|
|
ImGui::End();
|
|
}
|
|
|
|
static void renderCompositionPanel(EditorState& state) {
|
|
if (!state.library.showCompositionPanel) return;
|
|
if (state.ui.focusTarget == FocusRegion::Side) {
|
|
ImGui::SetNextWindowFocus();
|
|
state.ui.focusTarget = FocusRegion::None;
|
|
}
|
|
ImGui::Begin("Compose", &state.library.showCompositionPanel);
|
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
|
|
state.ui.focusedRegion = FocusRegion::Side;
|
|
}
|
|
ImGui::PushFont(state.uiFont);
|
|
std::string nodeId;
|
|
if (state.activeAST()) {
|
|
ASTNode* scopeNode = findNodeAtPosition(state.activeAST(),
|
|
std::max(0, state.active()->cursorLine - 1),
|
|
std::max(0, state.active()->cursorCol - 1));
|
|
if (scopeNode) nodeId = scopeNode->id;
|
|
}
|
|
std::vector<PrimitiveSymbol> primitives;
|
|
auto funcs = state.library.primitives.getAvailableFunctions(nodeId);
|
|
primitives.insert(primitives.end(), funcs.begin(), funcs.end());
|
|
std::string code;
|
|
if (renderCompositionPanel(state.library.compositionPanel, primitives, code, state.notifications)) {
|
|
state.insertTextAtCursor(code);
|
|
}
|
|
ImGui::PopFont();
|
|
ImGui::End();
|
|
}
|
|
|
|
static void renderEmacsPackagesPanel(EditorState& state) {
|
|
if (!state.emacsState.showEmacsPackagesPanel) return;
|
|
if (state.ui.focusTarget == FocusRegion::Side) {
|
|
ImGui::SetNextWindowFocus();
|
|
state.ui.focusTarget = FocusRegion::None;
|
|
}
|
|
ImGui::Begin("Emacs Packages", &state.emacsState.showEmacsPackagesPanel);
|
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
|
|
state.ui.focusedRegion = FocusRegion::Side;
|
|
}
|
|
ImGui::PushFont(state.uiFont);
|
|
if (renderEmacsPackageBrowser(state.emacsState.emacsPackages,
|
|
state.emacsState.emacs,
|
|
state.notifications)) {
|
|
state.emacsState.emacsFunctionIndexDirty = true;
|
|
}
|
|
ImGui::PopFont();
|
|
ImGui::End();
|
|
}
|
|
|
|
static void renderEmacsBridgePanel(EditorState& state) {
|
|
if (!state.emacsState.showEmacsBridgePanel) return;
|
|
if (state.ui.focusTarget == FocusRegion::Side) {
|
|
ImGui::SetNextWindowFocus();
|
|
state.ui.focusTarget = FocusRegion::None;
|
|
}
|
|
ImGui::Begin("Emacs Bridge", &state.emacsState.showEmacsBridgePanel);
|
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
|
|
state.ui.focusedRegion = FocusRegion::Side;
|
|
}
|
|
ImGui::PushFont(state.uiFont);
|
|
if (state.active()) {
|
|
ImGui::Text("Active file: %s", state.active()->path.c_str());
|
|
} else {
|
|
ImGui::TextDisabled("(no active buffer)");
|
|
}
|
|
ImGui::Separator();
|
|
if (ImGui::Button("Open Emacs Frame")) {
|
|
state.openInEmacsFrame();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Pull From Emacs")) {
|
|
state.pullFromEmacs();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Push To Emacs")) {
|
|
state.pushToEmacs();
|
|
}
|
|
ImGui::PopFont();
|
|
ImGui::End();
|
|
}
|
|
|
|
static void renderMinibuffer(EditorState& state) {
|
|
if (!(state.ui.layoutPreset == LayoutPreset::Emacs && state.emacsState.emacsKeys.minibufferActive))
|
|
return;
|
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
ImGuiWindowFlags mbFlags = ImGuiWindowFlags_NoDecoration |
|
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
|
|
ImGuiWindowFlags_NoScrollbar;
|
|
float mbHeight = ImGui::GetFrameHeight() + 10;
|
|
ImVec2 mbPos(viewport->WorkPos.x,
|
|
viewport->WorkPos.y + viewport->WorkSize.y - mbHeight - 24.0f);
|
|
ImVec2 mbSize(viewport->WorkSize.x, mbHeight);
|
|
ImGui::SetNextWindowPos(mbPos);
|
|
ImGui::SetNextWindowSize(mbSize);
|
|
ImGui::Begin("##Minibuffer", nullptr, mbFlags);
|
|
ImGui::PushFont(state.uiFont);
|
|
ImGui::TextUnformatted(state.emacsState.emacsKeys.minibufferPrompt.c_str());
|
|
ImGui::SameLine();
|
|
ImGui::SetNextItemWidth(-1.0f);
|
|
if (ImGui::InputText("##minibuffer",
|
|
state.emacsState.emacsKeys.minibufferBuf,
|
|
sizeof(state.emacsState.emacsKeys.minibufferBuf),
|
|
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
emacsExecuteMinibuffer(state.emacsState.emacsKeys,
|
|
state.emacsState.emacs,
|
|
state.notifications);
|
|
}
|
|
ImGui::PopFont();
|
|
ImGui::End();
|
|
}
|
|
|
|
static void renderMemoryStrategiesPanel(EditorState& state) {
|
|
if (!state.ui.showMemoryStrategies) return;
|
|
if (state.ui.focusTarget == FocusRegion::Side) {
|
|
ImGui::SetNextWindowFocus();
|
|
state.ui.focusTarget = FocusRegion::None;
|
|
}
|
|
ImGui::Begin("Memory Strategies", &state.ui.showMemoryStrategies);
|
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
|
|
state.ui.focusedRegion = FocusRegion::Side;
|
|
}
|
|
ImGui::PushFont(state.uiFont);
|
|
Module* ast = state.active() ? state.active()->sync.getAST() : nullptr;
|
|
if (state.active() && state.active()->bufferMode == BufferManager::BufferMode::Text) {
|
|
ImGui::TextDisabled("(disabled in Text mode)");
|
|
} else if (!ast) {
|
|
ImGui::TextDisabled("(no AST)");
|
|
} else {
|
|
std::vector<AnnotationEntry> entries;
|
|
collectAnnotationEntries(ast, entries);
|
|
std::map<std::string, int> counts;
|
|
for (const auto& e : entries) counts[e.label]++;
|
|
|
|
ImGui::Text("Annotations");
|
|
ImGui::Separator();
|
|
for (const auto& [label, count] : counts) {
|
|
ImGui::Text("%s: %d", label.c_str(), count);
|
|
}
|
|
ImGui::Spacing();
|
|
|
|
ImGui::Text("Details");
|
|
ImGui::Separator();
|
|
for (const auto& e : entries) {
|
|
std::string lineInfo = e.line >= 0 ? ("L" + std::to_string(e.line + 1)) : "-";
|
|
std::string row = e.label + " — " + e.nodeName + " (" + lineInfo + ")";
|
|
if (ImGui::Selectable(row.c_str())) {
|
|
if (state.active()) state.jumpTo(state.active(), e.line, 0);
|
|
}
|
|
}
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Text("Suggestions");
|
|
ImGui::Separator();
|
|
for (const auto& s : state.suggestions) {
|
|
if (s.confidence < 0.5) continue;
|
|
std::string row = s.annotationType + "(" + s.strategy + ") — " +
|
|
s.nodeId + " (" + std::to_string(s.confidence) + ")";
|
|
ImGui::TextUnformatted(row.c_str());
|
|
}
|
|
|
|
if (ImGui::Button("Export JSON")) {
|
|
auto j = buildAnnotationSummaryJson(entries);
|
|
state.notify(NotificationLevel::Info,
|
|
"Annotation summary:\\n" + j.dump(2));
|
|
}
|
|
}
|
|
ImGui::PopFont();
|
|
ImGui::End();
|
|
}
|