qt: phase 4 — extract UndoStack into qt/src/undo/
Pulls the static s_undoStack / s_redoStack / s_redoInProgress / kUndoCap plus pushTabUndo / pushWindowUndo / undoLastClose / redoLastClose out of MainWindow into a new `undo::` namespace under qt/src/undo/. State lives file-static in UndoStack.cpp; callers see only push / replay free functions. Replay-side hooks are two new public methods on MainWindow: closeCurrentTabForRedo (Tab redo) and closeForRedo (Window redo). Both bypass the close-confirm prompt — the user accepted the close on the original action. surfaceAt is promoted to public so undo::undoLast can title-tag the revived tab/window without going through MainWindow internals. MainWindow.cpp shrinks ~140 lines; MainWindow.h drops the UndoEntry struct and the static stack declarations. Co-Authored-By: claude-flow <ruv@ruv.net>pull/12846/head
parent
4102618fbe
commit
7845751538
|
|
@ -115,6 +115,7 @@ add_executable(ghastty
|
|||
src/OverlayScrollbar.cpp
|
||||
src/SearchBar.cpp
|
||||
src/TabWidget.cpp
|
||||
src/undo/UndoStack.cpp
|
||||
src/Util.cpp
|
||||
src/WindowBlur.cpp
|
||||
src/XkbTracker.cpp
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
#include "CommandPalette.h"
|
||||
#include "GhosttySurface.h"
|
||||
#include "TabWidget.h"
|
||||
#include "undo/UndoStack.h"
|
||||
#include "Util.h"
|
||||
#include "WindowBlur.h"
|
||||
|
||||
|
|
@ -442,11 +443,11 @@ void MainWindow::removeSurface(GhosttySurface *surface) {
|
|||
const int index = m_tabs->indexOf(parent);
|
||||
// Push to undo so a shell-exited tab close is symmetric with a
|
||||
// user-initiated tab close (closeTab pushes too). Skip the last
|
||||
// tab — its closeEvent runs pushWindowUndo and we don't want to
|
||||
// tab — its closeEvent runs undo::pushWindow and we don't want to
|
||||
// double-stack. Also skip the quick terminal (which doesn't push
|
||||
// to either stack by design).
|
||||
if (index >= 0 && m_tabs->count() > 1 && !m_quickTerminal)
|
||||
pushTabUndo(index);
|
||||
undo::pushTab(m_tabs->tabText(index), m_quickTerminal);
|
||||
if (index >= 0) m_tabs->removeTab(index);
|
||||
if (parent) parent->deleteLater(); // page; destroys the surface too
|
||||
// The surface close was already confirmed; don't re-prompt on the
|
||||
|
|
@ -461,9 +462,10 @@ void MainWindow::closeTab(int index) {
|
|||
QWidget *page = m_tabs->widget(index);
|
||||
if (!page) return;
|
||||
// Snapshot the tab's title for undo before we lose the reference.
|
||||
// pushTabUndo is no-op for the last tab in a window — that close
|
||||
// ends up triggering pushWindowUndo via closeEvent instead.
|
||||
if (m_tabs->count() > 1 && !m_quickTerminal) pushTabUndo(index);
|
||||
// undo::pushTab is no-op for the last tab in a window — that close
|
||||
// ends up triggering undo::pushWindow via closeEvent instead.
|
||||
if (m_tabs->count() > 1 && !m_quickTerminal)
|
||||
undo::pushTab(m_tabs->tabText(index), m_quickTerminal);
|
||||
const auto inTab = page->findChildren<GhosttySurface *>();
|
||||
for (GhosttySurface *s : inTab) m_surfaces.removeOne(s);
|
||||
// If the zoomed surface was in this tab, clear the stash so a later
|
||||
|
|
@ -655,7 +657,11 @@ void MainWindow::closeEvent(QCloseEvent *e) {
|
|||
// Snapshot for undo. We push the window's full tab list so undo
|
||||
// restores all of them; closeTab paths skip the per-tab push when
|
||||
// they reach the last tab so we don't double-stack the same close.
|
||||
pushWindowUndo();
|
||||
QStringList titles;
|
||||
titles.reserve(m_tabs->count());
|
||||
for (int i = 0; i < m_tabs->count(); ++i)
|
||||
titles << m_tabs->tabText(i);
|
||||
undo::pushWindow(titles, geometry(), m_quickTerminal);
|
||||
e->accept();
|
||||
}
|
||||
|
||||
|
|
@ -1444,147 +1450,22 @@ void MainWindow::setCellSize(uint32_t w, uint32_t h) {
|
|||
setSizeIncrement(0, 0); // back to pixel-precise
|
||||
}
|
||||
|
||||
// Process-wide undo state — see MainWindow.h.
|
||||
QList<MainWindow::UndoEntry> MainWindow::s_undoStack;
|
||||
QList<MainWindow::UndoEntry> MainWindow::s_redoStack;
|
||||
bool MainWindow::s_redoInProgress = false;
|
||||
void MainWindow::undoLastClose() { undo::undoLast(); }
|
||||
void MainWindow::redoLastClose() { undo::redoLast(); }
|
||||
|
||||
// Snapshot the tab at `index` (its tab text — last-known title) onto
|
||||
// the undo stack. Called from closeTab / closeTabsByMode / right
|
||||
// before the tab is removed. No-op while a redo is replaying.
|
||||
void MainWindow::pushTabUndo(int index) {
|
||||
if (s_redoInProgress) return;
|
||||
if (index < 0 || index >= m_tabs->count()) return;
|
||||
UndoEntry e;
|
||||
e.kind = UndoEntry::Kind::Tab;
|
||||
e.pageTitles << m_tabs->tabText(index);
|
||||
s_undoStack.append(std::move(e));
|
||||
if (s_undoStack.size() > kUndoCap) s_undoStack.removeFirst();
|
||||
// A fresh close invalidates any pending redo: the new "future" no
|
||||
// longer matches what the redo stack would re-close.
|
||||
s_redoStack.clear();
|
||||
// Close the active tab without prompting. Called from
|
||||
// undo::redoLast for a Tab redo: the user already accepted the
|
||||
// original close, so re-closing carries the same prior consent.
|
||||
void MainWindow::closeCurrentTabForRedo() {
|
||||
const int idx = m_tabs->currentIndex();
|
||||
if (idx >= 0) closeTab(idx);
|
||||
}
|
||||
|
||||
// Snapshot every tab in this window before it goes away. Called from
|
||||
// closeAllWindows and from closeEvent for the user-driven X. No-op
|
||||
// while a redo is replaying.
|
||||
void MainWindow::pushWindowUndo() {
|
||||
if (s_redoInProgress) return;
|
||||
if (m_quickTerminal || m_tabs->count() == 0) return;
|
||||
UndoEntry e;
|
||||
e.kind = UndoEntry::Kind::Window;
|
||||
for (int i = 0; i < m_tabs->count(); ++i)
|
||||
e.pageTitles << m_tabs->tabText(i);
|
||||
e.geometry = geometry();
|
||||
s_undoStack.append(std::move(e));
|
||||
if (s_undoStack.size() > kUndoCap) s_undoStack.removeFirst();
|
||||
s_redoStack.clear();
|
||||
}
|
||||
|
||||
// Pop the most recent undo entry and revive it. A new tab/window is
|
||||
// opened that inherits cwd from the active surface (libghostty
|
||||
// supplies the cwd via inherited_config), and the saved title is
|
||||
// reapplied as a manual tab-title override so it persists across
|
||||
// shell prompts.
|
||||
void MainWindow::undoLastClose() {
|
||||
if (s_undoStack.isEmpty()) return;
|
||||
const UndoEntry e = s_undoStack.takeLast();
|
||||
|
||||
// The active window picks the new tab's parent surface for cwd
|
||||
// inheritance. Skip the quick terminal — it doesn't push undo
|
||||
// entries and isn't a meaningful target. Fall back to the most
|
||||
// recent regular window in registration order.
|
||||
auto isUndoTarget = [](MainWindow *w) {
|
||||
return w && !w->m_quickTerminal;
|
||||
};
|
||||
MainWindow *active = qobject_cast<MainWindow *>(qApp->activeWindow());
|
||||
if (!isUndoTarget(active)) {
|
||||
active = nullptr;
|
||||
const QList<MainWindow *> &live = GhosttyApp::instance().windows();
|
||||
for (int i = live.size() - 1; i >= 0; --i) {
|
||||
if (isUndoTarget(live.at(i))) {
|
||||
active = live.at(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
GhosttySurface *parent = active
|
||||
? active->surfaceAt(active->m_tabs->currentIndex())
|
||||
: nullptr;
|
||||
|
||||
if (e.kind == UndoEntry::Kind::Tab) {
|
||||
if (!active) return;
|
||||
GhosttySurface *s = active->newTab(parent ? parent->surface() : nullptr);
|
||||
if (s && !e.pageTitles.isEmpty())
|
||||
active->setTabTitleOverride(s, e.pageTitles.first());
|
||||
} else {
|
||||
// Window: open a fresh window, then add additional tabs to match
|
||||
// the saved tab count. We don't try to recreate the split tree
|
||||
// — that would require a real session save mechanism.
|
||||
MainWindow *w = MainWindow::newWindow(parent ? parent->surface() : nullptr);
|
||||
if (!w) return;
|
||||
if (e.geometry.isValid()) w->setGeometry(e.geometry);
|
||||
// Title for the (eventually created) first tab.
|
||||
if (!e.pageTitles.isEmpty()) {
|
||||
const QString first = e.pageTitles.first();
|
||||
QPointer<MainWindow> wp(w);
|
||||
QTimer::singleShot(0, w, [wp, first]() {
|
||||
if (!wp) return;
|
||||
if (auto *s = wp->surfaceAt(0)) wp->setTabTitleOverride(s, first);
|
||||
});
|
||||
}
|
||||
// Additional tabs for the rest of the saved set.
|
||||
for (int i = 1; i < e.pageTitles.size(); ++i) {
|
||||
const QString t = e.pageTitles.at(i);
|
||||
QPointer<MainWindow> wp(w);
|
||||
QTimer::singleShot(0, w, [wp, t]() {
|
||||
if (!wp) return;
|
||||
GhosttySurface *s =
|
||||
wp->newTab(wp->surfaceAt(0) ? wp->surfaceAt(0)->surface() : nullptr);
|
||||
if (s) wp->setTabTitleOverride(s, t);
|
||||
});
|
||||
}
|
||||
}
|
||||
s_redoStack.append(e);
|
||||
if (s_redoStack.size() > kUndoCap) s_redoStack.removeFirst();
|
||||
}
|
||||
|
||||
// Redo: re-close whatever undo just opened. We don't have a handle on
|
||||
// the revived widgets so we close the active window's current tab
|
||||
// (or the active window itself for a Window entry); pragmatic,
|
||||
// matches what a user normally means by "redo close-tab".
|
||||
//
|
||||
// pushTabUndo / pushWindowUndo are no-ops while s_redoInProgress is
|
||||
// true, so the close paths below don't:
|
||||
// (a) clear s_redoStack — preserving the rest of the redo chain
|
||||
// so a sequence of REDOs works.
|
||||
// (b) push a fresh undo entry — a redo that re-closes shouldn't
|
||||
// feed itself a new undo or the user can ping-pong undo/redo
|
||||
// on a single past close indefinitely.
|
||||
void MainWindow::redoLastClose() {
|
||||
if (s_redoStack.isEmpty()) return;
|
||||
UndoEntry e = s_redoStack.takeLast();
|
||||
|
||||
MainWindow *active = qobject_cast<MainWindow *>(qApp->activeWindow());
|
||||
if (!active) {
|
||||
const QList<MainWindow *> &live = GhosttyApp::instance().windows();
|
||||
if (!live.isEmpty()) active = live.last();
|
||||
}
|
||||
if (!active) {
|
||||
// No window to act on — restore the entry so the user can retry.
|
||||
s_redoStack.append(std::move(e));
|
||||
return;
|
||||
}
|
||||
|
||||
s_redoInProgress = true;
|
||||
if (e.kind == UndoEntry::Kind::Tab) {
|
||||
const int idx = active->m_tabs->currentIndex();
|
||||
if (idx >= 0) active->closeTab(idx);
|
||||
} else {
|
||||
active->m_skipCloseConfirm = true;
|
||||
active->close();
|
||||
}
|
||||
s_redoInProgress = false;
|
||||
// Close the entire window without re-prompting. Called from
|
||||
// undo::redoLast for a Window redo.
|
||||
void MainWindow::closeForRedo() {
|
||||
m_skipCloseConfirm = true;
|
||||
close();
|
||||
}
|
||||
|
||||
void MainWindow::applyWindowConfig() {
|
||||
|
|
|
|||
|
|
@ -76,17 +76,20 @@ public:
|
|||
// need to take a dependency on app/GhosttyApp.h.
|
||||
ghostty_config_t config() const;
|
||||
|
||||
// UNDO / REDO close-tab/window. The libghostty actions carry no
|
||||
// payload — the apprt is responsible for tracking what was closed
|
||||
// and reviving it. macOS uses NSUndoManager; we keep a small bounded
|
||||
// stack of "snapshots" per kind. Surfaces themselves can't be
|
||||
// revived (the child PTY is gone) — undo opens a fresh tab/window
|
||||
// and reapplies the saved title; the new surface inherits cwd from
|
||||
// the active surface (matching macOS, which also spawns a fresh
|
||||
// shell rather than re-attaching).
|
||||
// UNDO / REDO close-tab/window action handlers — thin wrappers that
|
||||
// drive undo::undoLast / undo::redoLast. The undo state lives in
|
||||
// qt/src/undo/UndoStack; the comment there explains the lifetime
|
||||
// and replay model.
|
||||
static void undoLastClose();
|
||||
static void redoLastClose();
|
||||
|
||||
// Replay-side hooks used by undo::redoLast. closeCurrentTabForRedo
|
||||
// closes whichever tab is currently active (Tab redo); closeForRedo
|
||||
// closes the entire window (Window redo). Both bypass the
|
||||
// close-confirm prompt — the user already accepted the close once.
|
||||
void closeCurrentTabForRedo();
|
||||
void closeForRedo();
|
||||
|
||||
// PRESENT_TERMINAL: bring this window to front and focus the surface.
|
||||
void presentTerminal(GhosttySurface *surface);
|
||||
// GOTO_WINDOW: cycle to the previous/next window in registration order.
|
||||
|
|
@ -154,6 +157,9 @@ public:
|
|||
// First surface in the currently-visible tab, or nullptr. Used by
|
||||
// PROMPT_TITLE app-target promotion.
|
||||
GhosttySurface *currentSurface() const;
|
||||
// First surface in the tab at `index`, or nullptr. Used by
|
||||
// undo::undoLast to title-tag the revived tab/window.
|
||||
GhosttySurface *surfaceAt(int index) const;
|
||||
// Default size cached on INITIAL_SIZE for RESET_WINDOW_SIZE.
|
||||
QSize defaultWindowSize() const { return m_defaultWindowSize; }
|
||||
void setDefaultWindowSize(QSize s) { m_defaultWindowSize = s; }
|
||||
|
|
@ -194,7 +200,6 @@ private:
|
|||
void detachTab(int index);
|
||||
// Move `page` (a tab and its surfaces) from `src` into this window.
|
||||
void adoptTab(MainWindow *src, QWidget *page);
|
||||
GhosttySurface *surfaceAt(int index) const;
|
||||
int tabIndexForSurface(GhosttySurface *surface) const;
|
||||
QList<GhosttySurface *> surfacesInTab(int index) const;
|
||||
|
||||
|
|
@ -254,33 +259,8 @@ private:
|
|||
// GhosttyApp::instance(). MainWindow's config() / needsPremultiply()
|
||||
// accessors forward to it.
|
||||
|
||||
// Snapshot of a closed tab or window for undo/redo. `pageTitles`
|
||||
// holds each tab's last-known title (window snapshots have N tabs;
|
||||
// tab snapshots have one). `geometry` is unused for tab snapshots.
|
||||
// `kind` distinguishes the two so REDO can reclose the right thing.
|
||||
struct UndoEntry {
|
||||
enum class Kind { Tab, Window } kind = Kind::Tab;
|
||||
QStringList pageTitles;
|
||||
QRect geometry;
|
||||
};
|
||||
// Bounded undo/redo stacks (tail = most recent). Each tab/window
|
||||
// close pushes an entry, capped at kUndoCap; opening a new
|
||||
// tab/window via undo pushes onto the redo stack. While
|
||||
// `s_redoInProgress` is true, the close paths that normally
|
||||
// mutate these stacks (pushTabUndo / pushWindowUndo) become
|
||||
// no-ops — a redo is replaying a previous close and shouldn't
|
||||
// also feed itself a fresh undo entry that the user will then
|
||||
// unwind into a loop.
|
||||
static QList<UndoEntry> s_undoStack;
|
||||
static QList<UndoEntry> s_redoStack;
|
||||
static bool s_redoInProgress;
|
||||
static constexpr int kUndoCap = 16;
|
||||
// Push a snapshot for the tab at `index` onto s_undoStack and
|
||||
// clear the redo stack (a new close invalidates a forward redo).
|
||||
void pushTabUndo(int index);
|
||||
// Push a snapshot of every tab in this window onto s_undoStack as a
|
||||
// single Window entry; called from closeAllWindows / closeEvent.
|
||||
void pushWindowUndo();
|
||||
// The undo/redo stacks live in qt/src/undo/UndoStack — see comment
|
||||
// there for the lifecycle and replay semantics.
|
||||
|
||||
// Wakeup tick coalescing lives on GhosttyApp::m_tickPending.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
#include "UndoStack.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
|
||||
#include "../app/GhosttyApp.h"
|
||||
#include "../GhosttySurface.h"
|
||||
#include "../MainWindow.h"
|
||||
|
||||
namespace undo {
|
||||
|
||||
namespace {
|
||||
|
||||
// Bounded undo / redo stacks (tail = most recent). A push past the cap
|
||||
// drops the oldest entry. The redo stack is cleared by every fresh
|
||||
// close — a new "future" no longer matches what redo would re-close.
|
||||
constexpr int kCap = 16;
|
||||
|
||||
QList<Entry> &undoStack() {
|
||||
static QList<Entry> s;
|
||||
return s;
|
||||
}
|
||||
|
||||
QList<Entry> &redoStack() {
|
||||
static QList<Entry> s;
|
||||
return s;
|
||||
}
|
||||
|
||||
// True while undo::redoLast is replaying. push* is gated on this so a
|
||||
// redo that re-closes doesn't:
|
||||
// (a) clear the redo stack (the rest of the redo chain stays
|
||||
// playable), and
|
||||
// (b) push a fresh undo entry (otherwise the user can ping-pong
|
||||
// undo/redo on a single past close indefinitely).
|
||||
bool g_redoInProgress = false;
|
||||
|
||||
// Pick the active window for an undo target. Skips the quick terminal
|
||||
// (it doesn't push undo entries, so re-opening into it isn't
|
||||
// meaningful). Falls back to the most recent registered regular
|
||||
// window. Returns nullptr if no regular window exists.
|
||||
MainWindow *activeUndoTarget() {
|
||||
auto isUndoTarget = [](MainWindow *w) {
|
||||
return w && !w->isQuickTerminal();
|
||||
};
|
||||
MainWindow *active = qobject_cast<MainWindow *>(qApp->activeWindow());
|
||||
if (isUndoTarget(active)) return active;
|
||||
const QList<MainWindow *> &live = GhosttyApp::instance().windows();
|
||||
for (int i = live.size() - 1; i >= 0; --i) {
|
||||
if (isUndoTarget(live.at(i))) return live.at(i);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Pick the window the user is currently looking at for a redo. Unlike
|
||||
// undo, redo doesn't filter the quick terminal — REDO without an
|
||||
// active regular window leaves the entry in place (caller restores).
|
||||
MainWindow *activeRedoTarget() {
|
||||
MainWindow *active = qobject_cast<MainWindow *>(qApp->activeWindow());
|
||||
if (active) return active;
|
||||
const QList<MainWindow *> &live = GhosttyApp::instance().windows();
|
||||
return live.isEmpty() ? nullptr : live.last();
|
||||
}
|
||||
|
||||
void pushUndo(Entry e) {
|
||||
QList<Entry> &s = undoStack();
|
||||
s.append(std::move(e));
|
||||
if (s.size() > kCap) s.removeFirst();
|
||||
// A fresh close invalidates any pending redo: the future the redo
|
||||
// stack would replay no longer matches the world.
|
||||
redoStack().clear();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void pushTab(const QString &tabText, bool quickTerminal) {
|
||||
if (g_redoInProgress) return;
|
||||
if (quickTerminal) return;
|
||||
Entry e;
|
||||
e.kind = Entry::Kind::Tab;
|
||||
e.pageTitles << tabText;
|
||||
pushUndo(std::move(e));
|
||||
}
|
||||
|
||||
void pushWindow(const QStringList &tabTitles, const QRect &geometry,
|
||||
bool quickTerminal) {
|
||||
if (g_redoInProgress) return;
|
||||
if (quickTerminal || tabTitles.isEmpty()) return;
|
||||
Entry e;
|
||||
e.kind = Entry::Kind::Window;
|
||||
e.pageTitles = tabTitles;
|
||||
e.geometry = geometry;
|
||||
pushUndo(std::move(e));
|
||||
}
|
||||
|
||||
void undoLast() {
|
||||
QList<Entry> &s = undoStack();
|
||||
if (s.isEmpty()) return;
|
||||
const Entry e = s.takeLast();
|
||||
|
||||
MainWindow *active = activeUndoTarget();
|
||||
GhosttySurface *parent = active ? active->currentSurface() : nullptr;
|
||||
|
||||
if (e.kind == Entry::Kind::Tab) {
|
||||
if (!active) return; // dropping the entry: no target to revive into
|
||||
GhosttySurface *fresh =
|
||||
active->newTab(parent ? parent->surface() : nullptr);
|
||||
if (fresh && !e.pageTitles.isEmpty())
|
||||
active->setTabTitleOverride(fresh, e.pageTitles.first());
|
||||
} else {
|
||||
// Window: spawn a fresh window, then queue extra tabs to match
|
||||
// the saved tab count. We don't try to recreate the split tree
|
||||
// — that would need a real session save mechanism.
|
||||
MainWindow *w =
|
||||
MainWindow::newWindow(parent ? parent->surface() : nullptr);
|
||||
if (!w) return;
|
||||
if (e.geometry.isValid()) w->setGeometry(e.geometry);
|
||||
if (!e.pageTitles.isEmpty()) {
|
||||
const QString first = e.pageTitles.first();
|
||||
QPointer<MainWindow> wp(w);
|
||||
QTimer::singleShot(0, w, [wp, first]() {
|
||||
if (!wp) return;
|
||||
if (auto *fresh = wp->surfaceAt(0))
|
||||
wp->setTabTitleOverride(fresh, first);
|
||||
});
|
||||
}
|
||||
for (int i = 1; i < e.pageTitles.size(); ++i) {
|
||||
const QString t = e.pageTitles.at(i);
|
||||
QPointer<MainWindow> wp(w);
|
||||
QTimer::singleShot(0, w, [wp, t]() {
|
||||
if (!wp) return;
|
||||
GhosttySurface *first = wp->surfaceAt(0);
|
||||
GhosttySurface *fresh =
|
||||
wp->newTab(first ? first->surface() : nullptr);
|
||||
if (fresh) wp->setTabTitleOverride(fresh, t);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QList<Entry> &r = redoStack();
|
||||
r.append(e);
|
||||
if (r.size() > kCap) r.removeFirst();
|
||||
}
|
||||
|
||||
void redoLast() {
|
||||
QList<Entry> &r = redoStack();
|
||||
if (r.isEmpty()) return;
|
||||
Entry e = r.takeLast();
|
||||
|
||||
MainWindow *active = activeRedoTarget();
|
||||
if (!active) {
|
||||
// No window to act on — restore the entry so the user can retry.
|
||||
r.append(std::move(e));
|
||||
return;
|
||||
}
|
||||
|
||||
g_redoInProgress = true;
|
||||
if (e.kind == Entry::Kind::Tab) {
|
||||
active->closeCurrentTabForRedo();
|
||||
} else {
|
||||
active->closeForRedo();
|
||||
}
|
||||
g_redoInProgress = false;
|
||||
}
|
||||
|
||||
} // namespace undo
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
class MainWindow;
|
||||
|
||||
// Process-wide undo / redo of closed tabs and windows.
|
||||
//
|
||||
// libghostty's UNDO / REDO actions carry no payload — the apprt
|
||||
// remembers what was closed and revives it. Surfaces themselves can't
|
||||
// be revived (the child PTY is gone), so undo opens a fresh tab/window
|
||||
// and reapplies the saved title; the new surface inherits cwd from
|
||||
// the active surface, matching macOS (which also spawns a fresh
|
||||
// shell rather than re-attaching).
|
||||
//
|
||||
// State lives in this file's anonymous namespace; callers see only
|
||||
// the four push / replay functions. push* are no-ops while a redo is
|
||||
// replaying so the redo path doesn't feed itself.
|
||||
namespace undo {
|
||||
|
||||
// Snapshot of one closed tab or window. Window snapshots carry every
|
||||
// tab's last-known title and the window's geometry; tab snapshots
|
||||
// carry one title and an unused geometry.
|
||||
struct Entry {
|
||||
enum class Kind { Tab, Window } kind = Kind::Tab;
|
||||
QStringList pageTitles;
|
||||
QRect geometry;
|
||||
};
|
||||
|
||||
// Snapshot the tab at `index` — single title — onto the undo stack.
|
||||
// `tabText` is the tab's last-known display text; `quickTerminal` is
|
||||
// the window's quick-terminal flag (quick-terminal tabs are excluded
|
||||
// from the stack, mirroring the prior MainWindow behavior).
|
||||
void pushTab(const QString &tabText, bool quickTerminal);
|
||||
|
||||
// Snapshot every tab's title plus the window's geometry as a single
|
||||
// Window entry. Excluded for the quick terminal and for empty
|
||||
// windows.
|
||||
void pushWindow(const QStringList &tabTitles, const QRect &geometry,
|
||||
bool quickTerminal);
|
||||
|
||||
// Pop the most recent entry and revive it: open a fresh tab or
|
||||
// window, set the saved title(s) as a manual override, and push the
|
||||
// entry onto the redo stack. No-op if the stack is empty.
|
||||
void undoLast();
|
||||
|
||||
// Pop the most recent redo entry and re-close the active window's
|
||||
// current tab (Tab entries) or the active window itself (Window
|
||||
// entries). No-op if the stack is empty or no active window exists.
|
||||
void redoLast();
|
||||
|
||||
} // namespace undo
|
||||
Loading…
Reference in New Issue