591 lines
23 KiB
C++
591 lines
23 KiB
C++
// Whetstone Editor — ImGui-based structured code editor
|
|
//
|
|
// Panel rendering is delegated to headers in panels/.
|
|
// main.cpp handles: initialization, event loop, panel dispatch, cleanup.
|
|
|
|
#include "imgui_impl_sdl2.h"
|
|
#include "imgui_impl_opengl3.h"
|
|
#include "imgui_internal.h"
|
|
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_opengl.h>
|
|
#include <cctype>
|
|
#include <filesystem>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include "EditorState.h"
|
|
#include "EditorUtils.h"
|
|
#include "CompletionUtils.h"
|
|
#include "ThemeEngine.h"
|
|
#include "FirstRunWizard.h"
|
|
#include "panels/WizardPanels.h"
|
|
#include "FeatureHints.h"
|
|
#include "ShortcutReference.h"
|
|
|
|
// --- Panel headers ---
|
|
#include "panels/MenuBarPanel.h"
|
|
#include "panels/ExplorerPanel.h"
|
|
#include "panels/SidePanels.h"
|
|
#include "panels/SearchPanels.h"
|
|
#include "panels/SettingsPanel.h"
|
|
#include "panels/DialogPanels.h"
|
|
#include "panels/EditorPanel.h"
|
|
#include "panels/BottomPanel.h"
|
|
#include "panels/StatusBarPanel.h"
|
|
#include "panels/HelpPanelWindow.h"
|
|
#include "panels/AgentChatPanel.h"
|
|
|
|
static std::string emacsChordForEvent(SDL_Keycode sym, Uint16 mods) {
|
|
std::string base;
|
|
if (sym >= SDLK_a && sym <= SDLK_z) {
|
|
char c = (char)('a' + (sym - SDLK_a));
|
|
if (mods & KMOD_SHIFT) c = (char)std::toupper(c);
|
|
base.push_back(c);
|
|
} else if (sym >= SDLK_0 && sym <= SDLK_9) {
|
|
base.push_back((char)('0' + (sym - SDLK_0)));
|
|
} else if (sym == SDLK_SPACE) {
|
|
base = "SPC";
|
|
} else if (sym == SDLK_RETURN || sym == SDLK_KP_ENTER) {
|
|
base = "RET";
|
|
} else if (sym == SDLK_TAB) {
|
|
base = "TAB";
|
|
} else if (sym == SDLK_BACKSPACE) {
|
|
base = "DEL";
|
|
} else if (sym == SDLK_ESCAPE) {
|
|
base = "ESC";
|
|
} else if (sym == SDLK_MINUS) {
|
|
base = "-";
|
|
} else if (sym == SDLK_EQUALS) {
|
|
base = "=";
|
|
} else if (sym == SDLK_SLASH) {
|
|
base = "/";
|
|
} else if (sym == SDLK_SEMICOLON) {
|
|
base = ";";
|
|
} else if (sym == SDLK_PERIOD) {
|
|
base = ".";
|
|
}
|
|
|
|
if (base.empty()) return "";
|
|
|
|
std::string prefix;
|
|
if (mods & KMOD_CTRL) prefix += "C-";
|
|
if (mods & KMOD_ALT) prefix += "M-";
|
|
if ((mods & KMOD_SHIFT) && base.size() > 1) prefix += "S-";
|
|
|
|
return prefix + base;
|
|
}
|
|
|
|
static ImGuiID splitDockNode(ImGuiID& mainId, DockDirection direction, float ratio) {
|
|
ImGuiDir dir = ImGuiDir_None;
|
|
switch (direction) {
|
|
case DockDirection::Left: dir = ImGuiDir_Left; break;
|
|
case DockDirection::Right: dir = ImGuiDir_Right; break;
|
|
case DockDirection::Bottom: dir = ImGuiDir_Down; break;
|
|
case DockDirection::Top: dir = ImGuiDir_Up; break;
|
|
case DockDirection::Center: return mainId;
|
|
}
|
|
return ImGui::DockBuilderSplitNode(mainId, dir, ratio, nullptr, &mainId);
|
|
}
|
|
|
|
static std::unordered_map<std::string, ImGuiID> buildDockNodes(
|
|
ImGuiID dockId,
|
|
const LayoutConfig& cfg,
|
|
const ImVec2& size) {
|
|
std::unordered_map<std::string, ImGuiID> nodes;
|
|
ImGui::DockBuilderRemoveNode(dockId);
|
|
ImGui::DockBuilderAddNode(dockId, ImGuiDockNodeFlags_DockSpace);
|
|
ImGui::DockBuilderSetNodeSize(dockId, size);
|
|
ImGuiID mainId = dockId;
|
|
for (const auto& panel : cfg.panels) {
|
|
if (panel.direction == DockDirection::Center) continue;
|
|
nodes[panel.id] = splitDockNode(mainId, panel.direction, panel.sizeRatio);
|
|
}
|
|
for (const auto& panel : cfg.panels) {
|
|
if (panel.direction != DockDirection::Center) continue;
|
|
nodes[panel.id] = mainId;
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
static void dockPanelGroup(
|
|
const std::unordered_map<std::string, ImGuiID>& nodes,
|
|
const std::string& panelId,
|
|
std::initializer_list<const char*> windows) {
|
|
auto it = nodes.find(panelId);
|
|
if (it == nodes.end()) return;
|
|
for (const char* window : windows) {
|
|
ImGui::DockBuilderDockWindow(window, it->second);
|
|
}
|
|
}
|
|
|
|
static void applyDockLayout(ImGuiID dockId, const LayoutConfig& cfg, const ImVec2& size) {
|
|
auto nodes = buildDockNodes(dockId, cfg, size);
|
|
dockPanelGroup(nodes, "Explorer", {"Explorer"});
|
|
dockPanelGroup(nodes, "Editor", {"Editor"});
|
|
dockPanelGroup(nodes, "Panel", {"Panel"});
|
|
dockPanelGroup(nodes, "ToolWindow",
|
|
{"Outline", "Dependencies", "Libraries", "Compose",
|
|
"Emacs Packages", "Emacs Bridge", "Memory Strategies"});
|
|
dockPanelGroup(nodes, "Minibuffer", {"##Minibuffer"});
|
|
ImGui::DockBuilderFinish(dockId);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Main
|
|
// ---------------------------------------------------------------------------
|
|
int main(int, char**) {
|
|
auto logStage = [](const char* msg) {
|
|
std::fprintf(stderr, "[whetstone-startup] %s\n", msg);
|
|
std::fflush(stderr);
|
|
};
|
|
|
|
logStage("begin");
|
|
{
|
|
const char* envDriver = std::getenv("SDL_VIDEODRIVER");
|
|
if (envDriver && std::string(envDriver) == "offscreen") {
|
|
std::fprintf(stderr,
|
|
"[whetstone-startup] overriding SDL_VIDEODRIVER=offscreen\n");
|
|
SDL_setenv("SDL_VIDEODRIVER", "", 1);
|
|
}
|
|
bool hasX11 = false;
|
|
bool hasWayland = false;
|
|
int numDrivers = SDL_GetNumVideoDrivers();
|
|
std::fprintf(stderr, "[whetstone-startup] available SDL drivers:");
|
|
for (int i = 0; i < numDrivers; ++i) {
|
|
const char* d = SDL_GetVideoDriver(i);
|
|
if (!d) continue;
|
|
std::fprintf(stderr, " %s", d);
|
|
if (std::string(d) == "x11") hasX11 = true;
|
|
if (std::string(d) == "wayland") hasWayland = true;
|
|
}
|
|
std::fprintf(stderr, "\n");
|
|
std::fflush(stderr);
|
|
const char* currentEnv = std::getenv("SDL_VIDEODRIVER");
|
|
if (!currentEnv || std::string(currentEnv).empty()) {
|
|
if (hasX11) SDL_setenv("SDL_VIDEODRIVER", "x11", 1);
|
|
else if (hasWayland) SDL_setenv("SDL_VIDEODRIVER", "wayland", 1);
|
|
}
|
|
}
|
|
|
|
// SDL init
|
|
logStage("SDL_Init");
|
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) {
|
|
printf("SDL Error: %s\n", SDL_GetError());
|
|
return -1;
|
|
}
|
|
{
|
|
const char* driver = SDL_GetCurrentVideoDriver();
|
|
std::fprintf(stderr, "[whetstone-startup] SDL video driver: %s\n",
|
|
driver ? driver : "(null)");
|
|
std::fflush(stderr);
|
|
}
|
|
|
|
const char* glsl_version = "#version 130";
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
|
|
|
auto winFlags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE |
|
|
SDL_WINDOW_ALLOW_HIGHDPI);
|
|
logStage("SDL_CreateWindow");
|
|
SDL_Window* window = SDL_CreateWindow("Whetstone Editor",
|
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
|
1440, 900, winFlags);
|
|
if (!window) {
|
|
printf("SDL_CreateWindow Error: %s\n", SDL_GetError());
|
|
SDL_Quit();
|
|
return -1;
|
|
}
|
|
logStage("SDL_GL_CreateContext");
|
|
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
|
|
if (!gl_context) {
|
|
printf("SDL_GL_CreateContext Error: %s\n", SDL_GetError());
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
return -1;
|
|
}
|
|
logStage("SDL_GL_MakeCurrent");
|
|
SDL_GL_MakeCurrent(window, gl_context);
|
|
SDL_GL_SetSwapInterval(1);
|
|
SDL_ShowWindow(window);
|
|
SDL_RaiseWindow(window);
|
|
logStage("window shown");
|
|
|
|
// ImGui init
|
|
IMGUI_CHECKVERSION();
|
|
ImGui::CreateContext();
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
|
|
|
// Load fonts
|
|
const float baseFontSize = 15.0f;
|
|
// Bootstrap with default fonts; platform-specific/custom fonts are loaded in reloadFonts().
|
|
ImFont* monoFont = io.Fonts->AddFontDefault();
|
|
ImFont* uiFont = io.Fonts->AddFontDefault();
|
|
|
|
logStage("imgui initialized");
|
|
ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
|
|
ImGui_ImplOpenGL3_Init(glsl_version);
|
|
|
|
// Editor state
|
|
EditorState state;
|
|
state.monoFont = monoFont;
|
|
state.uiFont = uiFont;
|
|
state.baseFontSize = baseFontSize;
|
|
logStage("state.init begin");
|
|
state.init();
|
|
logStage("state.init end");
|
|
|
|
auto loadFontFromPath = [&](const std::string& preferred,
|
|
const std::vector<std::string>& fallbacks,
|
|
float size) -> ImFont* {
|
|
namespace fs = std::filesystem;
|
|
if (!preferred.empty() && fs::exists(preferred)) {
|
|
if (auto* f = io.Fonts->AddFontFromFileTTF(preferred.c_str(), size)) return f;
|
|
}
|
|
for (const auto& path : fallbacks) {
|
|
if (fs::exists(path)) {
|
|
if (auto* f = io.Fonts->AddFontFromFileTTF(path.c_str(), size)) return f;
|
|
}
|
|
}
|
|
return io.Fonts->AddFontDefault();
|
|
};
|
|
|
|
auto reloadFonts = [&]() {
|
|
ImGui_ImplOpenGL3_DestroyFontsTexture();
|
|
io.Fonts->Clear();
|
|
std::vector<std::string> codeFallbacks;
|
|
std::vector<std::string> uiFallbacks;
|
|
#ifdef _WIN32
|
|
codeFallbacks = {
|
|
"C:\\Windows\\Fonts\\consola.ttf",
|
|
"C:\\Windows\\Fonts\\CascadiaCode.ttf",
|
|
"C:\\Windows\\Fonts\\CascadiaMono.ttf",
|
|
"C:\\Windows\\Fonts\\cour.ttf"
|
|
};
|
|
uiFallbacks = {
|
|
"C:\\Windows\\Fonts\\segoeui.ttf",
|
|
"C:\\Windows\\Fonts\\calibri.ttf",
|
|
"C:\\Windows\\Fonts\\arial.ttf"
|
|
};
|
|
#else
|
|
codeFallbacks = {
|
|
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
"/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf"
|
|
};
|
|
uiFallbacks = {
|
|
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
|
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"
|
|
};
|
|
#endif
|
|
state.monoFont = loadFontFromPath(state.settings.getCodeFontPath(),
|
|
codeFallbacks,
|
|
state.baseFontSize);
|
|
state.uiFont = loadFontFromPath(state.settings.getUiFontPath(),
|
|
uiFallbacks,
|
|
state.baseFontSize);
|
|
io.FontGlobalScale = state.settings.getFontSize() / state.baseFontSize;
|
|
ImGui_ImplOpenGL3_CreateFontsTexture();
|
|
};
|
|
|
|
reloadFonts();
|
|
ThemeEngine& themes = ThemeEngine::instance();
|
|
themes.loadThemesFromDirectory("themes");
|
|
themes.loadThemesFromDirectory("editor/themes");
|
|
themes.loadThemesFromDirectory(ThemeEngine::userThemeDirectory());
|
|
std::string themeName = state.settings.getTheme();
|
|
if (themeName == "Dark") themeName = "Whetstone Dark";
|
|
if (themeName == "Light") themeName = "Whetstone Light";
|
|
if (!themes.applyTheme(themeName)) {
|
|
SetupVSCodeDarkTheme();
|
|
}
|
|
io.FontGlobalScale = state.settings.getFontSize() / baseFontSize;
|
|
SessionData session;
|
|
bool hasSession = state.loadSession(session);
|
|
bool applyInitialLayout = !hasSession;
|
|
bool dockLayoutApplied = false;
|
|
if (hasSession) {
|
|
if (!session.imguiIni.empty()) {
|
|
ImGui::LoadIniSettingsFromMemory(session.imguiIni.c_str(),
|
|
session.imguiIni.size());
|
|
}
|
|
state.applySession(session);
|
|
} else {
|
|
state.ui.showFirstRunWizard = true;
|
|
}
|
|
if (state.buffers.bufferCount() == 0) {
|
|
state.createBuffer(state.makeUntitledName(), "", "python",
|
|
BufferManager::BufferMode::Text);
|
|
state.ui.focusTarget = FocusRegion::Editor;
|
|
}
|
|
state.lspTransport = std::make_shared<NullLSPTransport>();
|
|
state.lsp = std::make_shared<LSPClient>(state.lspTransport);
|
|
|
|
bool done = false;
|
|
bool firstFrame = true;
|
|
while (!done) {
|
|
// --- Event handling ---
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event)) {
|
|
ImGui_ImplSDL2_ProcessEvent(&event);
|
|
if (event.type == SDL_QUIT)
|
|
done = true;
|
|
if (event.type == SDL_WINDOWEVENT &&
|
|
event.window.event == SDL_WINDOWEVENT_CLOSE &&
|
|
event.window.windowID == SDL_GetWindowID(window))
|
|
done = true;
|
|
if (event.type == SDL_DROPFILE) {
|
|
char* droppedFile = event.drop.file;
|
|
if (droppedFile) {
|
|
DragDropHandler::handleDrop(droppedFile,
|
|
[&](const std::string& p) { state.doOpen(p, state.defaultBufferMode()); },
|
|
[&](const std::string& p) {
|
|
std::string error;
|
|
if (!state.setWorkspaceRoot(p, &error)) {
|
|
state.notify(NotificationLevel::Error,
|
|
"Failed to open dropped folder: " + error);
|
|
}
|
|
});
|
|
SDL_free(droppedFile);
|
|
}
|
|
}
|
|
|
|
// Keyboard shortcuts
|
|
if (event.type == SDL_KEYDOWN && !io.WantTextInput) {
|
|
int key = 0;
|
|
auto sym = event.key.keysym.sym;
|
|
auto sdlMod = event.key.keysym.mod;
|
|
if (state.ui.layoutPreset == LayoutPreset::Emacs) {
|
|
std::string chord = emacsChordForEvent(sym, sdlMod);
|
|
if (!chord.empty() && state.handleEmacsKeyChord(chord)) {
|
|
continue;
|
|
}
|
|
}
|
|
if (sym >= SDLK_a && sym <= SDLK_z) key = 'A' + (sym - SDLK_a);
|
|
else if (sym >= SDLK_0 && sym <= SDLK_9) key = '0' + (sym - SDLK_0);
|
|
else if (sym == SDLK_EQUALS) key = '=';
|
|
else if (sym == SDLK_MINUS) key = '-';
|
|
else if (sym == SDLK_SLASH) key = '/';
|
|
else if (sym == SDLK_SEMICOLON) key = ';';
|
|
else if (sym == SDLK_PERIOD) key = '.';
|
|
else if (sym == SDLK_BACKQUOTE) key = '`';
|
|
|
|
int mods = WMOD_NONE;
|
|
if (sdlMod & KMOD_CTRL) mods |= WMOD_CTRL;
|
|
if (sdlMod & KMOD_SHIFT) mods |= WMOD_SHIFT;
|
|
if (sdlMod & KMOD_ALT) mods |= WMOD_ALT;
|
|
|
|
if (sym == SDLK_ESCAPE && mods == WMOD_NONE) {
|
|
state.ui.focusTarget = FocusRegion::Editor;
|
|
}
|
|
|
|
if (sym == SDLK_F6 && mods == WMOD_NONE) {
|
|
state.cyclePanelFocus();
|
|
}
|
|
|
|
if ((sdlMod & KMOD_CTRL) && (sdlMod & KMOD_SHIFT) && sym == SDLK_p) {
|
|
state.showCommandPalette = true;
|
|
state.commandQuery[0] = '\0';
|
|
state.commandSelected = 0;
|
|
}
|
|
|
|
auto applyFontSize = [&](int newSize) {
|
|
newSize = clampFontSize(newSize);
|
|
state.settings.setFontSize(newSize);
|
|
io.FontGlobalScale = newSize / baseFontSize;
|
|
state.saveSettingsToDisk();
|
|
};
|
|
|
|
if ((sdlMod & KMOD_CTRL) && sym == SDLK_0) {
|
|
applyFontSize((int)baseFontSize);
|
|
}
|
|
|
|
if (key != 0 && mods != WMOD_NONE) {
|
|
LegacyKeyCombo combo{key, mods};
|
|
std::string action = state.keys.getAction(combo);
|
|
if (action == "edit.undo") state.doUndo();
|
|
else if (action == "edit.redo") state.doRedo();
|
|
else if (action == "search.find") {
|
|
state.search.showFind = true;
|
|
state.search.showReplace = false;
|
|
}
|
|
else if (action == "search.replace") {
|
|
state.search.showFind = true;
|
|
state.search.showReplace = true;
|
|
}
|
|
else if (action == "search.findNext") state.doFindNext(false);
|
|
else if (action == "search.findPrev") state.doFindNext(true);
|
|
else if (action == "search.findInFiles") state.search.showProjectSearch = !state.search.showProjectSearch;
|
|
else if (action == "nav.goToLine") {
|
|
state.search.showGoToLine = true;
|
|
state.search.goToLineBuf[0] = '\0';
|
|
state.search.goToLineError = false;
|
|
}
|
|
else if (action == "view.toggleTerminal") {
|
|
state.build.showTerminalPanel = !state.build.showTerminalPanel;
|
|
}
|
|
else if (action == "file.save") state.doSave();
|
|
else if (action == "file.new") {
|
|
std::string lang = state.active() ? state.active()->language : "python";
|
|
state.createBuffer(state.makeUntitledName(), "", lang, state.defaultBufferMode());
|
|
}
|
|
else if (action == "build.run") state.runActiveFile(false);
|
|
else if (action == "build.build") state.runActiveFile(true);
|
|
else if (action == "view.zoomIn") applyFontSize(state.settings.getFontSize() + 1);
|
|
else if (action == "view.zoomOut") applyFontSize(state.settings.getFontSize() - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Per-frame updates ---
|
|
state.handleFileChanges();
|
|
if (state.settings.getAutoSaveSeconds() > 0 && state.active() && state.active()->modified) {
|
|
double now = ImGui::GetTime();
|
|
if ((now - state.lastAutoSave) >= state.settings.getAutoSaveSeconds()) {
|
|
state.doSave();
|
|
state.lastAutoSave = now;
|
|
}
|
|
}
|
|
state.pollLspMessages();
|
|
state.flushLspDidChange(ImGui::GetTime());
|
|
state.flushDeferredAstSync(ImGui::GetTime());
|
|
state.processLibraryIndexResponses();
|
|
if (state.emacsState.emacsFunctionIndexDirty) {
|
|
state.updateEmacsFunctionIndex();
|
|
}
|
|
state.refreshEmacsModeLine(ImGui::GetTime());
|
|
state.events.tick(ImGui::GetTime());
|
|
#ifdef _DEBUG
|
|
double now = ImGui::GetTime();
|
|
if (io.DeltaTime > 0.016 && (now - state.lastFrameBudgetWarning) > 1.0) {
|
|
state.notify(NotificationLevel::Warning,
|
|
"[perf] Frame time exceeded 16ms budget.");
|
|
state.lastFrameBudgetWarning = now;
|
|
}
|
|
#endif
|
|
themes.refreshWatchedThemes();
|
|
if (state.fontsDirty) {
|
|
reloadFonts();
|
|
state.fontsDirty = false;
|
|
}
|
|
|
|
// --- Start frame ---
|
|
ImGui_ImplOpenGL3_NewFrame();
|
|
ImGui_ImplSDL2_NewFrame();
|
|
ImGui::NewFrame();
|
|
|
|
// Full-window dockspace
|
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
ImGui::SetNextWindowPos(viewport->WorkPos);
|
|
ImGui::SetNextWindowSize(viewport->WorkSize);
|
|
ImGui::SetNextWindowViewport(viewport->ID);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
|
ImGuiWindowFlags dockFlags = ImGuiWindowFlags_NoDocking |
|
|
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse |
|
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
|
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
|
|
ImGui::Begin("##DockHost", nullptr, dockFlags);
|
|
ImGui::PopStyleVar(3);
|
|
ImGuiID dockId = ImGui::GetID("WhetstoneDS");
|
|
ImGui::DockSpace(dockId, ImVec2(0, 0), ImGuiDockNodeFlags_None);
|
|
if ((applyInitialLayout && !dockLayoutApplied) || state.ui.requestLayoutReset) {
|
|
LayoutManager layout;
|
|
layout.setPreset(state.ui.layoutPreset);
|
|
applyDockLayout(dockId, layout.getConfig(), viewport->WorkSize);
|
|
dockLayoutApplied = true;
|
|
state.ui.requestBottomCollapse = true;
|
|
state.ui.requestLayoutReset = false;
|
|
}
|
|
ImGui::End();
|
|
|
|
// --- Render all panels ---
|
|
renderMenuBar(state);
|
|
renderExplorerPanel(state);
|
|
renderOutlinePanel(state);
|
|
renderDependenciesPanel(state);
|
|
renderLibrariesPanel(state);
|
|
renderCompositionPanel(state);
|
|
renderEmacsPackagesPanel(state);
|
|
renderEmacsBridgePanel(state);
|
|
renderMinibuffer(state);
|
|
renderFindReplaceBar(state);
|
|
renderProjectSearchPanel(state);
|
|
renderSettingsPanel(state);
|
|
renderGoToLineDialog(state);
|
|
renderLargeFilePrompt(state);
|
|
renderLspSettingsPanel(state);
|
|
renderRefactorPopup(state);
|
|
renderCommandPalette(state);
|
|
renderEditorPanel(state);
|
|
renderBottomPanel(state);
|
|
renderAgentChatPanel(state);
|
|
renderMemoryStrategiesPanel(state);
|
|
renderStatusBar(state);
|
|
static FirstRunWizardState wizard;
|
|
wizard.open = state.ui.showFirstRunWizard;
|
|
renderFirstRunWizard(state, wizard);
|
|
state.ui.showFirstRunWizard = wizard.open;
|
|
static AnnotateFileWizardState annotateWizard;
|
|
annotateWizard.frame.open = state.ui.showAnnotateWizard;
|
|
renderAnnotateFileWizard(state, annotateWizard);
|
|
state.ui.showAnnotateWizard = annotateWizard.frame.open;
|
|
static ProjectWizardState projectWizard;
|
|
projectWizard.frame.open = state.ui.showProjectWizard;
|
|
renderProjectWizard(state, projectWizard);
|
|
state.ui.showProjectWizard = projectWizard.frame.open;
|
|
static ConnectAgentWizardState agentWizard;
|
|
agentWizard.frame.open = state.ui.showAgentWizard;
|
|
renderConnectAgentWizard(state, agentWizard);
|
|
state.ui.showAgentWizard = agentWizard.frame.open;
|
|
renderFeatureHintBar(state.featureHints, state.workspaceRoot);
|
|
static ShortcutReferenceState shortcutPanel;
|
|
renderShortcutReference(state, shortcutPanel);
|
|
renderHelpPanelWindow(state);
|
|
state.notifications.renderHistoryWindow([&](const Notification& note) {
|
|
if (note.hasTarget) state.navigateToTarget(note.target);
|
|
});
|
|
state.notifications.renderToasts([&](const Notification& note) {
|
|
if (note.hasTarget) state.navigateToTarget(note.target);
|
|
}, state.settings.getReduceMotion());
|
|
|
|
// Check for exit request from menu
|
|
if (state.exitRequested) done = true;
|
|
|
|
// --- Render frame ---
|
|
ImGui::Render();
|
|
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
|
|
glClearColor(0.12f, 0.12f, 0.12f, 1.00f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
|
|
|
SDL_GL_SwapWindow(window);
|
|
if (firstFrame) {
|
|
logStage("first frame swapped");
|
|
firstFrame = false;
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
size_t iniSize = 0;
|
|
const char* iniData = ImGui::SaveIniSettingsToMemory(&iniSize);
|
|
if (iniData && iniSize > 0) {
|
|
state.saveSession(std::string(iniData, iniSize));
|
|
}
|
|
state.shutdownAgentServer();
|
|
ImGui_ImplOpenGL3_Shutdown();
|
|
ImGui_ImplSDL2_Shutdown();
|
|
ImGui::DestroyContext();
|
|
SDL_GL_DeleteContext(gl_context);
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
|
|
return 0;
|
|
}
|