Step 169: notification system
This commit is contained in:
@@ -13,8 +13,30 @@ static void renderBottomPanel(EditorState& state) {
|
||||
if (ImGui::BeginTabItem("Output")) {
|
||||
ImGui::PushFont(state.monoFont);
|
||||
ImGui::BeginChild("##outputScroll", ImVec2(0, 0), false);
|
||||
ImGui::TextUnformatted(state.outputLog.c_str());
|
||||
// Auto-scroll to bottom
|
||||
for (const auto& note : state.notifications.getHistory()) {
|
||||
const char* label = "Info";
|
||||
ImVec4 color = ImVec4(0.40f, 0.70f, 0.95f, 1.0f);
|
||||
switch (note.level) {
|
||||
case NotificationLevel::Success:
|
||||
label = "Success";
|
||||
color = ImVec4(0.25f, 0.78f, 0.35f, 1.0f);
|
||||
break;
|
||||
case NotificationLevel::Warning:
|
||||
label = "Warning";
|
||||
color = ImVec4(0.95f, 0.72f, 0.28f, 1.0f);
|
||||
break;
|
||||
case NotificationLevel::Error:
|
||||
label = "Error";
|
||||
color = ImVec4(0.90f, 0.35f, 0.35f, 1.0f);
|
||||
break;
|
||||
case NotificationLevel::Info:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ImGui::TextColored(color, "[%s]", label);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextWrapped("%s", note.message.c_str());
|
||||
}
|
||||
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 20)
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
ImGui::EndChild();
|
||||
@@ -86,7 +108,7 @@ static void renderBottomPanel(EditorState& state) {
|
||||
ImGui::PushFont(state.monoFont);
|
||||
renderAgentMarketplace(state.agent.marketplace,
|
||||
state.agent.registry,
|
||||
state.outputLog,
|
||||
state.notifications,
|
||||
state.workspaceRoot);
|
||||
ImGui::PopFont();
|
||||
ImGui::EndTabItem();
|
||||
@@ -261,10 +283,10 @@ static void renderBottomPanel(EditorState& state) {
|
||||
}
|
||||
if (!warning.empty()) {
|
||||
summary += " — warning: " + warning;
|
||||
state.outputLog += warning + "\n";
|
||||
state.notify(NotificationLevel::Warning, warning);
|
||||
}
|
||||
state.optimizeFoldSummary = summary;
|
||||
state.outputLog += summary + "\n";
|
||||
state.notify(NotificationLevel::Info, summary);
|
||||
if (nodes > 0) state.applyOrchestratorToActive();
|
||||
std::string afterText = state.active()->editBuf;
|
||||
state.openDiff(beforeText, afterText, false, 1, {tid});
|
||||
@@ -308,7 +330,7 @@ static void renderBottomPanel(EditorState& state) {
|
||||
summary += "no changes";
|
||||
}
|
||||
state.optimizeDeadCodeSummary = summary;
|
||||
state.outputLog += summary + "\n";
|
||||
state.notify(NotificationLevel::Info, summary);
|
||||
if (nodes > 0) state.applyOrchestratorToActive();
|
||||
std::string afterText = state.active()->editBuf;
|
||||
state.openDiff(beforeText, afterText, false, 2, {tid});
|
||||
@@ -354,10 +376,10 @@ static void renderBottomPanel(EditorState& state) {
|
||||
}
|
||||
if (!warning.empty()) {
|
||||
summary += " — warning: " + warning;
|
||||
state.outputLog += warning + "\n";
|
||||
state.notify(NotificationLevel::Warning, warning);
|
||||
}
|
||||
state.optimizeApplySummary = summary;
|
||||
state.outputLog += summary + "\n";
|
||||
state.notify(NotificationLevel::Info, summary);
|
||||
if (totalNodes > 0) state.applyOrchestratorToActive();
|
||||
std::string afterText = state.active()->editBuf;
|
||||
state.openDiff(beforeText, afterText, false, 3, {tidFold, tidDce});
|
||||
@@ -460,10 +482,11 @@ static void renderBottomPanel(EditorState& state) {
|
||||
batch.setRoot(ast);
|
||||
auto res = batch.applySequence(state.diff.batchMutations);
|
||||
if (!res.success) {
|
||||
state.outputLog += res.error + "\n";
|
||||
state.notify(NotificationLevel::Error, res.error);
|
||||
} else {
|
||||
state.outputLog += "Refactor applied (" +
|
||||
std::to_string(res.appliedCount) + " mutations).\n";
|
||||
state.notify(NotificationLevel::Success,
|
||||
"Refactor applied (" +
|
||||
std::to_string(res.appliedCount) + " mutations).");
|
||||
state.applyOrchestratorToActive();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -155,8 +155,9 @@ static void renderRefactorPopup(EditorState& state) {
|
||||
if (!res.success) {
|
||||
state.refactorError = res.error;
|
||||
} else {
|
||||
state.outputLog += "Refactor applied (" +
|
||||
std::to_string(res.appliedCount) + " mutations).\n";
|
||||
state.notify(NotificationLevel::Success,
|
||||
"Refactor applied (" +
|
||||
std::to_string(res.appliedCount) + " mutations).");
|
||||
state.applyOrchestratorToActive();
|
||||
state.showRefactorPopup = false;
|
||||
}
|
||||
|
||||
@@ -452,7 +452,7 @@ static void renderEditorPanel(EditorState& state) {
|
||||
}
|
||||
state.mutator.setRoot(ast);
|
||||
auto res2 = state.mutator.insertNode(target->id, "annotations", anno);
|
||||
if (!res2.error.empty()) state.outputLog += res2.error + "\n";
|
||||
if (!res2.error.empty()) state.notify(NotificationLevel::Error, res2.error);
|
||||
state.applyOrchestratorToActive();
|
||||
}
|
||||
}
|
||||
@@ -555,7 +555,7 @@ static void renderEditorPanel(EditorState& state) {
|
||||
target->spanEndLine, target->spanEndCol);
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.insertNode(target->id, "annotations", anno);
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
}
|
||||
@@ -566,7 +566,7 @@ static void renderEditorPanel(EditorState& state) {
|
||||
target->spanEndLine, target->spanEndCol);
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.insertNode(target->id, "annotations", anno);
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
}
|
||||
@@ -577,7 +577,7 @@ static void renderEditorPanel(EditorState& state) {
|
||||
target->spanEndLine, target->spanEndCol);
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.insertNode(target->id, "annotations", anno);
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
}
|
||||
@@ -588,7 +588,7 @@ static void renderEditorPanel(EditorState& state) {
|
||||
target->spanEndLine, target->spanEndCol);
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.insertNode(target->id, "annotations", anno);
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
}
|
||||
@@ -599,7 +599,7 @@ static void renderEditorPanel(EditorState& state) {
|
||||
target->spanEndLine, target->spanEndCol);
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.insertNode(target->id, "annotations", anno);
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
}
|
||||
@@ -615,35 +615,35 @@ static void renderEditorPanel(EditorState& state) {
|
||||
if (ImGui::MenuItem("Tracing")) {
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.setProperty(anno->id, "strategy", "Tracing");
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
} else if (anno->conceptType == "OwnerAnnotation") {
|
||||
if (ImGui::MenuItem("Single")) {
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.setProperty(anno->id, "strategy", "Single");
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
} else if (anno->conceptType == "DeallocateAnnotation") {
|
||||
if (ImGui::MenuItem("Explicit")) {
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.setProperty(anno->id, "strategy", "Explicit");
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
} else if (anno->conceptType == "LifetimeAnnotation") {
|
||||
if (ImGui::MenuItem("RAII")) {
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.setProperty(anno->id, "strategy", "RAII");
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
} else if (anno->conceptType == "AllocateAnnotation") {
|
||||
if (ImGui::MenuItem("Static")) {
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.setProperty(anno->id, "strategy", "Static");
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
}
|
||||
@@ -659,7 +659,7 @@ static void renderEditorPanel(EditorState& state) {
|
||||
if (ImGui::MenuItem(label.c_str())) {
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.deleteNode(anno->id);
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
}
|
||||
@@ -682,7 +682,7 @@ static void renderEditorPanel(EditorState& state) {
|
||||
if (ImGui::MenuItem("Remove child annotation")) {
|
||||
state.mutator.setRoot(ast);
|
||||
auto res = state.mutator.deleteNode(lineConflict.childAnnoId);
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
if (ImGui::MenuItem("Match parent strategy")) {
|
||||
@@ -690,7 +690,7 @@ static void renderEditorPanel(EditorState& state) {
|
||||
auto res = state.mutator.setProperty(lineConflict.childAnnoId,
|
||||
"strategy",
|
||||
lineConflict.parentStrategy);
|
||||
if (!res.error.empty()) state.outputLog += res.error + "\n";
|
||||
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
|
||||
if (res.error.empty()) mutated = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ static void renderMenuBar(EditorState& state) {
|
||||
auto path = FileDialog::openFile({"Open Project", {"*.whetstone"}, state.lastDialogPath});
|
||||
if (!path.empty()) {
|
||||
if (!state.loadProject(path)) {
|
||||
state.outputLog += "Failed to open project: " + path + "\n";
|
||||
state.notify(NotificationLevel::Error,
|
||||
"Failed to open project: " + path);
|
||||
} else {
|
||||
state.lastDialogPath = path;
|
||||
}
|
||||
@@ -59,7 +60,8 @@ static void renderMenuBar(EditorState& state) {
|
||||
auto path = FileDialog::saveFile({"Save Project", {"*.whetstone"}, state.lastDialogPath});
|
||||
if (!path.empty()) {
|
||||
if (!state.saveProject(path)) {
|
||||
state.outputLog += "Failed to save project: " + path + "\n";
|
||||
state.notify(NotificationLevel::Error,
|
||||
"Failed to save project: " + path);
|
||||
} else {
|
||||
state.lastDialogPath = path;
|
||||
}
|
||||
|
||||
@@ -80,9 +80,11 @@ static void renderSettingsPanel(EditorState& state) {
|
||||
UpdateChecker checker;
|
||||
auto info = checker.check(state.settings.getUpdateUrl());
|
||||
if (info.available) {
|
||||
state.outputLog += "[update] Available: " + info.version + " (" + info.url + ")\n";
|
||||
state.notify(NotificationLevel::Info,
|
||||
"[update] Available: " + info.version + " (" + info.url + ")");
|
||||
} else {
|
||||
state.outputLog += "[update] No update info (offline/stub).\n";
|
||||
state.notify(NotificationLevel::Info,
|
||||
"[update] No update info (offline/stub).");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ static void renderDependenciesPanel(EditorState& state) {
|
||||
if (!state.library.showDependencyPanel) return;
|
||||
ImGui::Begin("Dependencies", &state.library.showDependencyPanel);
|
||||
ImGui::PushFont(state.uiFont);
|
||||
renderDependencyPanel(state.library.dependencyPanel, state.workspaceRoot, state.outputLog);
|
||||
renderDependencyPanel(state.library.dependencyPanel, state.workspaceRoot, state.notifications);
|
||||
ImGui::PopFont();
|
||||
ImGui::End();
|
||||
if (state.library.dependencyPanel.needsIndex) {
|
||||
@@ -100,7 +100,7 @@ static void renderLibrariesPanel(EditorState& state) {
|
||||
state.library.libraryIndex,
|
||||
insertText,
|
||||
insertLibrary,
|
||||
state.outputLog)) {
|
||||
state.notifications)) {
|
||||
state.ensureImportForSymbol(insertLibrary, insertText);
|
||||
state.insertTextAtCursor(insertText);
|
||||
}
|
||||
@@ -123,7 +123,7 @@ static void renderCompositionPanel(EditorState& state) {
|
||||
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.outputLog)) {
|
||||
if (renderCompositionPanel(state.library.compositionPanel, primitives, code, state.notifications)) {
|
||||
state.insertTextAtCursor(code);
|
||||
}
|
||||
ImGui::PopFont();
|
||||
@@ -134,7 +134,9 @@ static void renderEmacsPackagesPanel(EditorState& state) {
|
||||
if (!state.emacsState.showEmacsPackagesPanel) return;
|
||||
ImGui::Begin("Emacs Packages", &state.emacsState.showEmacsPackagesPanel);
|
||||
ImGui::PushFont(state.uiFont);
|
||||
if (renderEmacsPackageBrowser(state.emacsState.emacsPackages, state.emacsState.emacs, state.outputLog)) {
|
||||
if (renderEmacsPackageBrowser(state.emacsState.emacsPackages,
|
||||
state.emacsState.emacs,
|
||||
state.notifications)) {
|
||||
state.emacsState.emacsFunctionIndexDirty = true;
|
||||
}
|
||||
ImGui::PopFont();
|
||||
@@ -188,7 +190,9 @@ static void renderMinibuffer(EditorState& state) {
|
||||
state.emacsState.emacsKeys.minibufferBuf,
|
||||
sizeof(state.emacsState.emacsKeys.minibufferBuf),
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
emacsExecuteMinibuffer(state.emacsState.emacsKeys, state.emacsState.emacs, state.outputLog);
|
||||
emacsExecuteMinibuffer(state.emacsState.emacsKeys,
|
||||
state.emacsState.emacs,
|
||||
state.notifications);
|
||||
}
|
||||
ImGui::PopFont();
|
||||
ImGui::End();
|
||||
@@ -237,7 +241,8 @@ static void renderMemoryStrategiesPanel(EditorState& state) {
|
||||
|
||||
if (ImGui::Button("Export JSON")) {
|
||||
auto j = buildAnnotationSummaryJson(entries);
|
||||
state.outputLog += "Annotation summary:\\n" + j.dump(2) + "\\n";
|
||||
state.notify(NotificationLevel::Info,
|
||||
"Annotation summary:\\n" + j.dump(2));
|
||||
}
|
||||
}
|
||||
ImGui::PopFont();
|
||||
|
||||
@@ -68,6 +68,16 @@ static void renderStatusBar(EditorState& state) {
|
||||
ImGui::SameLine(0, 30);
|
||||
ImGui::Text("UTF-8");
|
||||
|
||||
ImGui::SameLine(0, 30);
|
||||
int unread = state.notifications.unreadCount();
|
||||
std::string notifLabel = "Notifications";
|
||||
if (unread > 0) {
|
||||
notifLabel += " (" + std::to_string(unread) + ")";
|
||||
}
|
||||
if (ImGui::Button(notifLabel.c_str())) {
|
||||
state.notifications.showHistory = !state.notifications.showHistory;
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
Reference in New Issue
Block a user