Step 146: add Emacs buffer bridge

This commit is contained in:
Bill
2026-02-09 18:29:36 -07:00
parent 7ae51ebd2e
commit ca9628edfd
7 changed files with 161 additions and 2 deletions

View File

@@ -188,6 +188,7 @@ struct EditorState {
EmacsFunctionIndex emacsFunctionIndex;
bool emacsFunctionIndexDirty = true;
EmacsKeybindingState emacsKeys;
bool showEmacsBridgePanel = false;
bool showOrgPanel = true;
OrgDocumentState orgDoc;
int orgTempCounter = 0;
@@ -1434,6 +1435,48 @@ struct EditorState {
updateEmacsModeLine(emacsKeys, emacs, nowSeconds, outputLog);
}
void pullFromEmacs() {
if (!active()) return;
if (active()->path.rfind("(untitled", 0) == 0) {
outputLog += "[emacs] Save file before syncing.\n";
return;
}
std::string text = emacs.getBufferText(active()->path);
if (!emacs.getLastError().empty() && text == "error") {
outputLog += "[emacs] " + emacs.getLastError() + "\n";
return;
}
active()->editBuf = text;
onTextChanged();
outputLog += "[emacs] Pulled buffer from Emacs.\n";
}
void pushToEmacs() {
if (!active()) return;
if (active()->path.rfind("(untitled", 0) == 0) {
outputLog += "[emacs] Save file before syncing.\n";
return;
}
if (!emacs.setBufferText(active()->path, active()->editBuf)) {
outputLog += "[emacs] " + emacs.getLastError() + "\n";
return;
}
outputLog += "[emacs] Pushed buffer to Emacs.\n";
}
void openInEmacsFrame() {
if (!active()) return;
if (active()->path.rfind("(untitled", 0) == 0) {
outputLog += "[emacs] Save file before opening in Emacs.\n";
return;
}
if (!emacs.openFileInEmacsFrame(active()->path)) {
outputLog += "[emacs] " + emacs.getLastError() + "\n";
return;
}
outputLog += "[emacs] Opened in Emacs frame.\n";
}
std::string buildRunCommand(const std::string& path,
const std::string& language,
bool buildOnly) const {

View File

@@ -106,6 +106,20 @@ public:
static std::string modeLine() {
return "(format-mode-line mode-line-format)";
}
// Build an Elisp expression to read a buffer for a given file
static std::string bufferStringForFile(const std::string& path) {
return "(with-current-buffer (find-file-noselect \"" +
escapeString(path) + "\") (buffer-string))";
}
// Build an Elisp expression to replace a buffer's contents and save
static std::string replaceBufferForFile(const std::string& path, const std::string& text) {
return "(with-current-buffer (find-file-noselect \"" + escapeString(path) + "\") "
"(erase-buffer) "
"(insert \"" + escapeString(text) + "\") "
"(save-buffer))";
}
};
// ---------------------------------------------------------------------------
@@ -213,6 +227,27 @@ public:
return output;
}
std::string getBufferText(const std::string& path) {
return sendCommand(ElispCommandBuilder::bufferStringForFile(path));
}
bool setBufferText(const std::string& path, const std::string& text) {
std::string result = sendCommand(ElispCommandBuilder::replaceBufferForFile(path, text));
if (!lastError_.empty() && result == "error") return false;
return true;
}
bool openFileInEmacsFrame(const std::string& path) {
lastError_.clear();
std::string cmd = emacsclientPath_ + " -c -n \"" + path + "\"";
int code = runCommand(cmd);
if (code != 0) {
lastError_ = "emacsclient failed to open frame";
return false;
}
return true;
}
// Auto-restart daemon if it died
bool ensureDaemon() {
if (daemonRunning_ && isDaemonAlive()) return true;
@@ -300,6 +335,12 @@ public:
lastSentCommand_ = elispCommand;
// Simulate responses for known commands
if (elispCommand.find("buffer-string") != std::string::npos) {
return "mock-buffer";
}
if (elispCommand.find("erase-buffer") != std::string::npos) {
return "t";
}
if (elispCommand.find("find-file") != std::string::npos) {
return "t"; // success
}
@@ -356,7 +397,6 @@ public:
if (elispCommand.find("format-mode-line") != std::string::npos) {
return "Emacs: Fundamental (Whetstone)";
}
// Unknown command — simulate error
lastError_ = "void-function";
return "error";
@@ -372,10 +412,18 @@ public:
// Test helper: get the last command sent
std::string getLastSentCommand() const { return lastSentCommand_; }
std::string getLastInitPath() const { return lastInitPath_; }
std::string getLastRunCommand() const { return lastRunCommand_; }
protected:
int runCommand(const std::string& cmd) const override {
lastRunCommand_ = cmd;
return 0;
}
private:
std::string lastError_;
std::string lastSentCommand_;
std::string lastInitPath_;
mutable std::string lastRunCommand_;
};

View File

@@ -360,6 +360,7 @@ int main(int, char**) {
ImGui::MenuItem("Libraries", nullptr, &state.showLibraryBrowserPanel);
ImGui::MenuItem("Compose", nullptr, &state.showCompositionPanel);
ImGui::MenuItem("Emacs Packages", nullptr, &state.showEmacsPackagesPanel);
ImGui::MenuItem("Emacs Bridge", nullptr, &state.showEmacsBridgePanel);
ImGui::MenuItem("Settings", nullptr, &state.showSettingsPanel);
ImGui::MenuItem("LSP Servers...", nullptr, &state.showLspSettings);
if (state.active()) {
@@ -674,6 +675,30 @@ int main(int, char**) {
ImGui::End();
}
if (state.showEmacsBridgePanel) {
ImGui::Begin("Emacs Bridge", &state.showEmacsBridgePanel);
ImGui::PushFont(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();
}
if (state.layoutPreset == LayoutPreset::Emacs && state.emacsKeys.minibufferActive) {
ImGuiWindowFlags mbFlags = ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |