diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
index f9e8cbf1265..9f50d6718c2 100644
--- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
@@ -685,4 +685,16 @@
Split the window and start in given directory
+
+ Export Text
+
+
+ Failed to export terminal content
+
+
+ Successfully exported terminal content
+
+
+ Plain Text
+
diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp
index 849bfcc69d3..0a1f91e4616 100644
--- a/src/cascadia/TerminalApp/TabManagement.cpp
+++ b/src/cascadia/TerminalApp/TabManagement.cpp
@@ -28,6 +28,9 @@ using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::System;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt::Windows::UI::Text;
+using namespace winrt::Windows::Storage;
+using namespace winrt::Windows::Storage::Pickers;
+using namespace winrt::Windows::Storage::Provider;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Control;
using namespace winrt::Microsoft::Terminal::TerminalConnection;
@@ -171,6 +174,16 @@ namespace winrt::TerminalApp::implementation
}
});
+ newTabImpl->ExportTabRequested([weakTab, weakThis{ get_weak() }]() {
+ auto page{ weakThis.get() };
+ auto tab{ weakTab.get() };
+
+ if (page && tab)
+ {
+ page->_ExportTab(*tab);
+ }
+ });
+
auto tabViewItem = newTabImpl->TabViewItem();
_tabView.TabItems().Append(tabViewItem);
@@ -391,6 +404,45 @@ namespace winrt::TerminalApp::implementation
CATCH_LOG();
}
+ // Method Description:
+ // - Exports the content of the Terminal Buffer inside the tab
+ // Arguments:
+ // - tab: tab to export
+ winrt::fire_and_forget TerminalPage::_ExportTab(const TerminalTab& tab)
+ {
+ try
+ {
+ if (const auto control{ tab.GetActiveTerminalControl() })
+ {
+ const FileSavePicker savePicker;
+ savePicker.as()->Initialize(*_hostingHwnd);
+ savePicker.SuggestedStartLocation(PickerLocationId::Downloads);
+ const auto fileChoices = single_threaded_vector({ L".txt" });
+ savePicker.FileTypeChoices().Insert(RS_(L"PlainText"), fileChoices);
+ savePicker.SuggestedFileName(control.Title());
+
+ const StorageFile file = co_await savePicker.PickSaveFileAsync();
+ if (file != nullptr)
+ {
+ const auto buffer = control.ReadEntireBuffer();
+ CachedFileManager::DeferUpdates(file);
+ co_await FileIO::WriteTextAsync(file, buffer);
+ const auto status = co_await CachedFileManager::CompleteUpdatesAsync(file);
+ switch (status)
+ {
+ case FileUpdateStatus::Complete:
+ case FileUpdateStatus::CompleteAndRenamed:
+ _ShowControlNoticeDialog(RS_(L"NoticeInfo"), RS_(L"ExportSuccess"));
+ break;
+ default:
+ _ShowControlNoticeDialog(RS_(L"NoticeError"), RS_(L"ExportFailure"));
+ }
+ }
+ }
+ }
+ CATCH_LOG();
+ }
+
// Method Description:
// - Removes the tab (both TerminalControl and XAML) after prompting for approval
// Arguments:
diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h
index 0bdcf4aaeb1..d598d095556 100644
--- a/src/cascadia/TerminalApp/TerminalPage.h
+++ b/src/cascadia/TerminalApp/TerminalPage.h
@@ -220,6 +220,7 @@ namespace winrt::TerminalApp::implementation
void _DuplicateTab(const TerminalTab& tab);
void _SplitTab(TerminalTab& tab);
+ winrt::fire_and_forget _ExportTab(const TerminalTab& tab);
winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::TabBase tab);
void _CloseTabAtIndex(uint32_t index);
diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp
index 6f415754c38..a3a2a1651b8 100644
--- a/src/cascadia/TerminalApp/TerminalTab.cpp
+++ b/src/cascadia/TerminalApp/TerminalTab.cpp
@@ -1211,6 +1211,23 @@ namespace winrt::TerminalApp::implementation
splitTabMenuItem.Icon(splitTabSymbol);
}
+ Controls::MenuFlyoutItem exportTabMenuItem;
+ {
+ // "Split Tab"
+ Controls::FontIcon exportTabSymbol;
+ exportTabSymbol.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" });
+ exportTabSymbol.Glyph(L"\xE74E"); // Save
+
+ exportTabMenuItem.Click([weakThis](auto&&, auto&&) {
+ if (auto tab{ weakThis.get() })
+ {
+ tab->_ExportTabRequestedHandlers();
+ }
+ });
+ exportTabMenuItem.Text(RS_(L"ExportTabText"));
+ exportTabMenuItem.Icon(exportTabSymbol);
+ }
+
// Build the menu
Controls::MenuFlyout contextMenuFlyout;
Controls::MenuFlyoutSeparator menuSeparator;
@@ -1218,6 +1235,7 @@ namespace winrt::TerminalApp::implementation
contextMenuFlyout.Items().Append(renameTabMenuItem);
contextMenuFlyout.Items().Append(duplicateTabMenuItem);
contextMenuFlyout.Items().Append(splitTabMenuItem);
+ contextMenuFlyout.Items().Append(exportTabMenuItem);
contextMenuFlyout.Items().Append(menuSeparator);
// GH#5750 - When the context menu is dismissed with ESC, toss the focus
@@ -1592,4 +1610,5 @@ namespace winrt::TerminalApp::implementation
DEFINE_EVENT(TerminalTab, TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, SplitTabRequested, _SplitTabRequestedHandlers, winrt::delegate<>);
+ DEFINE_EVENT(TerminalTab, ExportTabRequested, _ExportTabRequestedHandlers, winrt::delegate<>);
}
diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h
index 4952fad4518..b1824d98eaa 100644
--- a/src/cascadia/TerminalApp/TerminalTab.h
+++ b/src/cascadia/TerminalApp/TerminalTab.h
@@ -103,6 +103,7 @@ namespace winrt::TerminalApp::implementation
DECLARE_EVENT(TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>);
DECLARE_EVENT(DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>);
DECLARE_EVENT(SplitTabRequested, _SplitTabRequestedHandlers, winrt::delegate<>);
+ DECLARE_EVENT(ExportTabRequested, _ExportTabRequestedHandlers, winrt::delegate<>);
TYPED_EVENT(TaskbarProgressChanged, IInspectable, IInspectable);
private:
diff --git a/src/cascadia/TerminalApp/pch.h b/src/cascadia/TerminalApp/pch.h
index 1f84f821e7d..66026a78dfd 100644
--- a/src/cascadia/TerminalApp/pch.h
+++ b/src/cascadia/TerminalApp/pch.h
@@ -56,6 +56,9 @@
#include
#include
#include
+#include
+#include
+#include
#include
diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp
index 3dcf284a7e5..c5e0cfac584 100644
--- a/src/cascadia/TerminalControl/ControlCore.cpp
+++ b/src/cascadia/TerminalControl/ControlCore.cpp
@@ -1499,4 +1499,32 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_updatePatternLocations->Run();
}
+ hstring ControlCore::ReadEntireBuffer() const
+ {
+ auto terminalLock = _terminal->LockForWriting();
+
+ const auto& textBuffer = _terminal->GetTextBuffer();
+
+ std::wstringstream ss;
+ const auto lastRow = textBuffer.GetLastNonSpaceCharacter().Y;
+ for (auto rowIndex = 0; rowIndex <= lastRow; rowIndex++)
+ {
+ const auto& row = textBuffer.GetRowByOffset(rowIndex);
+ auto rowText = row.GetText();
+ const auto strEnd = rowText.find_last_not_of(UNICODE_SPACE);
+ if (strEnd != std::string::npos)
+ {
+ rowText.erase(strEnd + 1);
+ ss << rowText;
+ }
+
+ if (!row.WasWrapForced())
+ {
+ ss << UNICODE_CARRIAGERETURN << UNICODE_LINEFEED;
+ }
+ }
+
+ return hstring(ss.str());
+ }
+
}
diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h
index 3bb321f0b55..57052cdd054 100644
--- a/src/cascadia/TerminalControl/ControlCore.h
+++ b/src/cascadia/TerminalControl/ControlCore.h
@@ -144,6 +144,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool IsInReadOnlyMode() const;
void ToggleReadOnlyMode();
+ hstring ReadEntireBuffer() const;
+
// -------------------------------- WinRT Events ---------------------------------
// clang-format off
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl
index fa4746ca404..154fa869b0d 100644
--- a/src/cascadia/TerminalControl/ControlCore.idl
+++ b/src/cascadia/TerminalControl/ControlCore.idl
@@ -81,6 +81,8 @@ namespace Microsoft.Terminal.Control
Boolean CursorOn;
void EnablePainting();
+ String ReadEntireBuffer();
+
event FontSizeChangedEventArgs FontSizeChanged;
event Windows.Foundation.TypedEventHandler