Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow exporting terminal buffer into file via tab context menu #11062

Merged
4 commits merged into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/cascadia/TerminalApp/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -685,4 +685,10 @@
<data name="DropPathTabSplit.Text" xml:space="preserve">
<value>Split the window and start in given directory</value>
</data>
<data name="ExportTabText" xml:space="preserve">
<value>Export Text</value>
</data>
<data name="PlainText" xml:space="preserve">
<value>Plain Text</value>
</data>
</root>
39 changes: 39 additions & 0 deletions src/cascadia/TerminalApp/TabManagement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ 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::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Control;
using namespace winrt::Microsoft::Terminal::TerminalConnection;
Expand Down Expand Up @@ -171,6 +173,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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an aside: we may want to (in a future PR) turn this into an action, that could let the user export the pane to a file from the command palette too, not just the context menu. But now I'm thinking of all the other kinds of actions we could have for this feature...

}
});

auto tabViewItem = newTabImpl->TabViewItem();
_tabView.TabItems().Append(tabViewItem);

Expand Down Expand Up @@ -391,6 +403,33 @@ 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<IInitializeWithWindow>()->Initialize(*_hostingHwnd);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay so just to be sure, this will work when the Terminal is elevated (run as admin), right? I want to make sure we don't run into the same issue that forced #9760

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do believe that it will crash.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is pickers, of all sorts, through the W.S API that is broken, not any specific individual use. We may try/catch this one, but... the best we can do is simply not pop up a dialog when Admin.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is pickers, of all sorts, through the W.S API that is broken, not any specific individual use. We may try/catch this one, but... the best we can do is simply not pop up a dialog when Admin.

Frick. I'll stick that on #9700

savePicker.SuggestedStartLocation(PickerLocationId::Downloads);
const auto fileChoices = single_threaded_vector<hstring>({ L".txt" });
savePicker.FileTypeChoices().Insert(RS_(L"PlainText"), fileChoices);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait should "Plain Text" be localizable? I guess it should be, huh.

savePicker.SuggestedFileName(control.Title());

const StorageFile file = co_await savePicker.PickSaveFileAsync();
if (file != nullptr)
{
control.StoreEntireBuffer(file);
}
}
}
CATCH_LOG();
}

// Method Description:
// - Removes the tab (both TerminalControl and XAML) after prompting for approval
// Arguments:
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions src/cascadia/TerminalApp/TerminalTab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1211,13 +1211,31 @@ namespace winrt::TerminalApp::implementation
splitTabMenuItem.Icon(splitTabSymbol);
}

Controls::MenuFlyoutItem exportTabMenuItem;
{
// "Split Tab"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: says "split" 😁

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;
contextMenuFlyout.Items().Append(chooseColorMenuItem);
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
Expand Down Expand Up @@ -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<>);
}
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/TerminalTab.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalApp/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Microsoft.Terminal.Settings.Editor.h>
#include <winrt/Microsoft.Terminal.Settings.Model.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Storage.Provider.h>
#include <winrt/Windows.Storage.Pickers.h>

#include <windows.ui.xaml.media.dxinterop.h>

Expand Down
40 changes: 40 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1499,4 +1499,44 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_updatePatternLocations->Run();
}

winrt::fire_and_forget ControlCore::StoreEntireBuffer(const winrt::Windows::Storage::StorageFile storageFile)
{
std::wstringstream ss;
{
auto terminalLock = _terminal->LockForWriting();
const auto& textBuffer = _terminal->GetTextBuffer();
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;
}
}
}

winrt::Windows::Storage::CachedFileManager::DeferUpdates(storageFile);
co_await winrt::Windows::Storage::FileIO::WriteTextAsync(storageFile, hstring(ss.str()));

const auto status = co_await winrt::Windows::Storage::CachedFileManager::CompleteUpdatesAsync(storageFile);
switch (status)
{
case winrt::Windows::Storage::Provider::FileUpdateStatus::Complete:
case winrt::Windows::Storage::Provider::FileUpdateStatus::CompleteAndRenamed:
_RaiseNoticeHandlers(*this, std::move(winrt::make<NoticeEventArgs>(NoticeLevel::Info, RS_(L"ExportSuccess"))));
break;
default:
_RaiseNoticeHandlers(*this, std::move(winrt::make<NoticeEventArgs>(NoticeLevel::Warning, RS_(L"ExportFailure"))));
}
}

}
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool IsInReadOnlyMode() const;
void ToggleReadOnlyMode();

winrt::fire_and_forget StoreEntireBuffer(const winrt::Windows::Storage::StorageFile storageFile);
Don-Vito marked this conversation as resolved.
Show resolved Hide resolved

// -------------------------------- WinRT Events ---------------------------------
// clang-format off
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.idl
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ namespace Microsoft.Terminal.Control
Boolean CursorOn;
void EnablePainting();

void StoreEntireBuffer(Windows.Storage.StorageFile storageFile);

event FontSizeChangedEventArgs FontSizeChanged;

event Windows.Foundation.TypedEventHandler<Object, CopyToClipboardEventArgs> CopyToClipboard;
Expand Down
6 changes: 6 additions & 0 deletions src/cascadia/TerminalControl/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,10 @@ Please either install the missing font or choose another one.</value>
<data name="TermControlReadOnly" xml:space="preserve">
<value>Read-only mode is enabled.</value>
</data>
<data name="ExportFailure" xml:space="preserve">
<value>Failed to export terminal content</value>
</data>
<data name="ExportSuccess" xml:space="preserve">
<value>Successfully exported terminal content</value>
</data>
</root>
5 changes: 5 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2586,4 +2586,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_playWarningBell->Run();
}

void TermControl::StoreEntireBuffer(const winrt::Windows::Storage::StorageFile& storageFile) const
{
_core.StoreEntireBuffer(storageFile);
}
}
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
static unsigned int GetPointerUpdateKind(const winrt::Windows::UI::Input::PointerPoint point);
static Windows::UI::Xaml::Thickness ParseThicknessFromPadding(const hstring padding);

void StoreEntireBuffer(const winrt::Windows::Storage::StorageFile& storageFile) const;

// -------------------------------- WinRT Events ---------------------------------
// clang-format off
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/TermControl.idl
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,7 @@ namespace Microsoft.Terminal.Control

Boolean ReadOnly { get; };
void ToggleReadOnly();

void StoreEntireBuffer(Windows.Storage.StorageFile storageFile);
}
}