diff --git a/src/nostalgia/studio/lib/plugin.hpp b/src/nostalgia/studio/lib/plugin.hpp
index c512cba6..e04f9ed0 100644
--- a/src/nostalgia/studio/lib/plugin.hpp
+++ b/src/nostalgia/studio/lib/plugin.hpp
@@ -13,6 +13,7 @@
 #include <QVector>
 #include <QWizardPage>
 
+#include "editor.hpp"
 #include "project.hpp"
 #include "wizard.hpp"
 
@@ -27,7 +28,7 @@ struct Context {
 
 struct EditorMaker {
 	QStringList fileTypes;
-	std::function<QWidget*(QString)> make;
+	std::function<Editor*(QString)> make;
 };
 
 class Plugin {
diff --git a/src/nostalgia/studio/mainwindow.cpp b/src/nostalgia/studio/mainwindow.cpp
index 79ce712a..b0ce104e 100644
--- a/src/nostalgia/studio/mainwindow.cpp
+++ b/src/nostalgia/studio/mainwindow.cpp
@@ -22,6 +22,7 @@
 #include <QTextStream>
 #include <QVector>
 
+#include "lib/editor.hpp"
 #include "lib/json.hpp"
 #include "lib/oxfstreeview.hpp"
 #include "lib/project.hpp"
@@ -61,6 +62,7 @@ MainWindow::MainWindow(QString profilePath) {
 	m_tabs->setTabsClosable(true);
 	connect(m_tabs, &QTabWidget::tabCloseRequested, this, &MainWindow::closeTab);
 	connect(tabBar, &QTabBar::tabMoved, this, &MainWindow::moveTab);
+	connect(tabBar, &QTabBar::currentChanged, this, &MainWindow::changeTab);
 	tabBar->setMovable(true);
 
 	setupMenu();
@@ -116,6 +118,7 @@ void MainWindow::loadPlugin(QString pluginPath) {
 void MainWindow::setupMenu() {
 	auto menu = menuBar();
 	auto fileMenu = menu->addMenu(tr("&File"));
+	auto editMenu = menu->addMenu(tr("&Edit"));
 	m_viewMenu = menu->addMenu(tr("&View"));
 
 	// New...
@@ -156,6 +159,16 @@ void MainWindow::setupMenu() {
 		QKeySequence::Quit,
 		QApplication::quit
 	);
+
+	// Undo
+	auto undoAction = m_undoGroup.createUndoAction(this, tr("&Undo"));
+	editMenu->addAction(undoAction);
+	undoAction->setShortcuts(QKeySequence::Undo);
+
+	// Redo
+	auto redoAction = m_undoGroup.createRedoAction(this, tr("&Redo"));
+	editMenu->addAction(redoAction);
+	redoAction->setShortcuts(QKeySequence::Redo);
 }
 
 void MainWindow::setupProjectExplorer() {
@@ -287,9 +300,10 @@ void MainWindow::openProject(QString projectPath) {
 void MainWindow::closeProject() {
 	// delete tabs
 	while (m_tabs->count()) {
-		auto t = m_tabs->widget(0);
+		auto tab = static_cast<studio::Editor*>(m_tabs->widget(0));
+		m_undoGroup.removeStack(tab->undoStack());
 		m_tabs->removeTab(0);
-		delete t;
+		delete tab;
 	}
 	
 	if (m_ctx.project) {
@@ -325,6 +339,7 @@ void MainWindow::openFile(QString path, bool force) {
 	if (m_editorMakers.contains(ext)) {
 		auto tab = m_editorMakers[ext].make(path);
 		m_tabs->addTab(tab, tabName);
+		m_undoGroup.addStack(tab->undoStack());
 		// save new tab to application state
 		auto openTabs = readTabs();
 		if (!openTabs.contains(path)) {
@@ -354,24 +369,6 @@ void MainWindow::openFileSlot(QModelIndex file) {
 	return openFile(path);
 }
 
-void MainWindow::closeTab(int idx) {
-	auto tab = m_tabs->widget(idx);
-	m_tabs->removeTab(idx);
-	delete tab;
-
-	// remove from open tabs list
-	auto tabs = readTabs();
-	tabs.removeAt(idx);
-	writeTabs(tabs);
-}
-
-void MainWindow::moveTab(int from, int to) {
-	// move tab in open tabs list
-	auto tabs = readTabs();
-	tabs.move(from, to);
-	writeTabs(tabs);
-}
-
 void MainWindow::showNewWizard() {
 	const QString ProjectName = "projectName";
 	const QString ProjectPath = "projectPath";
@@ -438,6 +435,33 @@ void MainWindow::showNewWizard() {
 	wizard.exec();
 }
 
+void MainWindow::closeTab(int idx) {
+	auto tab = static_cast<studio::Editor*>(m_tabs->widget(idx));
+	m_undoGroup.removeStack(tab->undoStack());
+	m_tabs->removeTab(idx);
+	delete tab;
+
+	// remove from open tabs list
+	auto tabs = readTabs();
+	tabs.removeAt(idx);
+	writeTabs(tabs);
+}
+
+void MainWindow::moveTab(int from, int to) {
+	// move tab in open tabs list
+	auto tabs = readTabs();
+	tabs.move(from, to);
+	writeTabs(tabs);
+}
+
+void MainWindow::changeTab(int idx) {
+	auto tab = dynamic_cast<studio::Editor*>(m_tabs->widget(idx));
+	if (!tab) {
+		return;
+	}
+	m_undoGroup.setActiveStack(tab->undoStack());
+}
+
 void MainWindow::showImportWizard() {
 	const QString TILESHEET_NAME = "projectName";
 	const QString IMPORT_PATH = "projectPath";
diff --git a/src/nostalgia/studio/mainwindow.hpp b/src/nostalgia/studio/mainwindow.hpp
index 10eb818b..9bbefd5c 100644
--- a/src/nostalgia/studio/mainwindow.hpp
+++ b/src/nostalgia/studio/mainwindow.hpp
@@ -19,6 +19,7 @@
 #include <QString>
 #include <QTabWidget>
 #include <QTreeView>
+#include <QUndoGroup>
 #include <QVector>
 
 #include <ox/std/types.hpp>
@@ -94,6 +95,7 @@ class MainWindow: public QMainWindow {
 		QHash<QString, EditorMaker> m_editorMakers;
 		QPointer<OxFSModel> m_oxfsView = nullptr;
 		QTabWidget *m_tabs = nullptr;
+		QUndoGroup m_undoGroup;
 
 	public:
 		MainWindow(QString profilePath);
@@ -158,6 +160,8 @@ class MainWindow: public QMainWindow {
 
 		void moveTab(int from, int to);
 
+		void changeTab(int idx);
+
 		void showNewWizard();
 
 		void showImportWizard();