mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-11 04:38:24 -06:00
* Moves clipboard writing from `ControlCore` to `TerminalPage`. This requires adding a bunch of event types and logic. This is technically not needed anymore after changing the direction of this PR, but I kept it because it's better. * Add a `WarnAboutMultiLinePaste` enum to differentiate between "paste without warning always/never/if-bracketed-paste-disabled". Closes #13014 Closes https://github.com/microsoft/edit/issues/279 ## Validation Steps Performed * Launch Microsoft Edit and copy text with a trailing newline * Paste it with Ctrl+Shift+V * It's pasted as it was copied ✅ * Changing the setting to "always" always warns ✅ Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
1624 lines
59 KiB
C++
1624 lines
59 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "App.h"
|
|
|
|
#include "TerminalPage.h"
|
|
#include "ScratchpadContent.h"
|
|
#include "../WinRTUtils/inc/WtExeUtils.h"
|
|
#include "../../types/inc/utils.hpp"
|
|
#include "Utils.h"
|
|
|
|
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
|
using namespace winrt::Windows::UI::Xaml;
|
|
using namespace winrt::Windows::UI::Text;
|
|
using namespace winrt::Windows::UI::Core;
|
|
using namespace winrt::Windows::Foundation::Collections;
|
|
using namespace winrt::Windows::System;
|
|
using namespace winrt::Microsoft::Terminal;
|
|
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
|
using namespace winrt::Microsoft::Terminal::Control;
|
|
using namespace winrt::Microsoft::Terminal::TerminalConnection;
|
|
using namespace ::TerminalApp;
|
|
|
|
namespace winrt
|
|
{
|
|
namespace MUX = Microsoft::UI::Xaml;
|
|
using IInspectable = Windows::Foundation::IInspectable;
|
|
}
|
|
|
|
namespace winrt::TerminalApp::implementation
|
|
{
|
|
TermControl TerminalPage::_senderOrActiveControl(const IInspectable& sender)
|
|
{
|
|
if (sender)
|
|
{
|
|
if (auto arg{ sender.try_as<TermControl>() })
|
|
{
|
|
return arg;
|
|
}
|
|
}
|
|
return _GetActiveControl();
|
|
}
|
|
winrt::com_ptr<Tab> TerminalPage::_senderOrFocusedTab(const IInspectable& sender)
|
|
{
|
|
if (sender)
|
|
{
|
|
if (auto tab = sender.try_as<TerminalApp::Tab>())
|
|
{
|
|
return _GetTabImpl(tab);
|
|
}
|
|
}
|
|
return _GetFocusedTabImpl();
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenNewTabDropdown(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_OpenNewTabDropdown();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleDuplicateTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_DuplicateFocusedTab();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleCloseTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto realArgs = args.ActionArgs().try_as<CloseTabArgs>())
|
|
{
|
|
uint32_t index;
|
|
if (realArgs.Index())
|
|
{
|
|
index = realArgs.Index().Value();
|
|
}
|
|
else if (auto focusedTabIndex = _GetFocusedTabIndex())
|
|
{
|
|
index = *focusedTabIndex;
|
|
}
|
|
else
|
|
{
|
|
args.Handled(false);
|
|
return;
|
|
}
|
|
|
|
_CloseTabAtIndex(index);
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleClosePane(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_CloseFocusedPane();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleRestoreLastClosed(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (_previouslyClosedPanesAndTabs.size() > 0)
|
|
{
|
|
const auto restoreActions = _previouslyClosedPanesAndTabs.back();
|
|
for (const auto& action : restoreActions)
|
|
{
|
|
_actionDispatch->DoAction(action);
|
|
}
|
|
_previouslyClosedPanesAndTabs.pop_back();
|
|
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleCloseWindow(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
CloseWindow();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleQuit(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
RequestQuit();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollUp(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto& realArgs = args.ActionArgs().try_as<ScrollUpArgs>();
|
|
if (realArgs)
|
|
{
|
|
_Scroll(ScrollUp, realArgs.RowsToScroll());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollDown(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto& realArgs = args.ActionArgs().try_as<ScrollDownArgs>();
|
|
if (realArgs)
|
|
{
|
|
_Scroll(ScrollDown, realArgs.RowsToScroll());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleNextTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto& realArgs = args.ActionArgs().try_as<NextTabArgs>();
|
|
if (realArgs)
|
|
{
|
|
_SelectNextTab(true, realArgs.SwitcherMode());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandlePrevTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto& realArgs = args.ActionArgs().try_as<PrevTabArgs>();
|
|
if (realArgs)
|
|
{
|
|
_SelectNextTab(false, realArgs.SwitcherMode());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSendInput(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args == nullptr)
|
|
{
|
|
args.Handled(false);
|
|
}
|
|
else if (const auto& realArgs = args.ActionArgs().try_as<SendInputArgs>())
|
|
{
|
|
if (const auto termControl{ _senderOrActiveControl(sender) })
|
|
{
|
|
termControl.SendInput(realArgs.Input());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleCloseOtherPanes(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
const auto activePane = activeTab->GetActivePane();
|
|
if (activeTab->GetRootPane() != activePane)
|
|
{
|
|
_UnZoomIfNeeded();
|
|
|
|
// Accumulate list of all unfocused leaf panes, ignore read-only panes
|
|
std::vector<uint32_t> unfocusedPaneIds;
|
|
const auto activePaneId = activePane->Id();
|
|
activeTab->GetRootPane()->WalkTree([&](auto&& p) {
|
|
const auto id = p->Id();
|
|
if (id.has_value() && id != activePaneId && !p->ContainsReadOnly())
|
|
{
|
|
unfocusedPaneIds.push_back(id.value());
|
|
}
|
|
});
|
|
|
|
if (!empty(unfocusedPaneIds))
|
|
{
|
|
// Start by removing the panes that were least recently added
|
|
sort(begin(unfocusedPaneIds), end(unfocusedPaneIds), std::less<uint32_t>());
|
|
_ClosePanes(activeTab->get_weak(), std::move(unfocusedPaneIds));
|
|
args.Handled(true);
|
|
return;
|
|
}
|
|
}
|
|
args.Handled(false);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleMovePane(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args == nullptr)
|
|
{
|
|
args.Handled(false);
|
|
}
|
|
else if (const auto& realArgs = args.ActionArgs().try_as<MovePaneArgs>())
|
|
{
|
|
const auto moved = _MovePane(realArgs);
|
|
args.Handled(moved);
|
|
}
|
|
}
|
|
|
|
// * Helper to try and get a ProfileIndex out of a NewTerminalArgs out of a
|
|
// NewContentArgs. For the new tab and split pane action, we want to _not_
|
|
// handle the event if an invalid profile index was passed.
|
|
//
|
|
// Return value:
|
|
// * True if the args are NewTerminalArgs, and the profile index was out of bounds.
|
|
// * False otherwise.
|
|
static bool _shouldBailForInvalidProfileIndex(const CascadiaSettings& settings, const INewContentArgs& args)
|
|
{
|
|
if (!args)
|
|
{
|
|
return false;
|
|
}
|
|
if (const auto& terminalArgs{ args.try_as<NewTerminalArgs>() })
|
|
{
|
|
if (const auto index = terminalArgs.ProfileIndex())
|
|
{
|
|
if (gsl::narrow<uint32_t>(index.Value()) >= settings.ActiveProfiles().Size())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TerminalPage::_HandleSplitPane(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args == nullptr)
|
|
{
|
|
args.Handled(false);
|
|
}
|
|
else if (const auto& realArgs = args.ActionArgs().try_as<SplitPaneArgs>())
|
|
{
|
|
if (_shouldBailForInvalidProfileIndex(_settings, realArgs.ContentArgs()))
|
|
{
|
|
args.Handled(false);
|
|
return;
|
|
}
|
|
|
|
const auto& duplicateFromTab{ realArgs.SplitMode() == SplitType::Duplicate ? _GetFocusedTab() : nullptr };
|
|
|
|
const auto& activeTab{ _senderOrFocusedTab(sender) };
|
|
|
|
_SplitPane(activeTab,
|
|
realArgs.SplitDirection(),
|
|
// This is safe, we're already filtering so the value is (0, 1)
|
|
realArgs.SplitSize(),
|
|
_MakePane(realArgs.ContentArgs(), duplicateFromTab));
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleSplitOrientation(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ToggleSplitOrientation();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleTogglePaneZoom(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
// Don't do anything if there's only one pane. It's already zoomed.
|
|
if (activeTab->GetLeafPaneCount() > 1)
|
|
{
|
|
// Togging the zoom on the tab will cause the tab to inform us of
|
|
// the new root Content for this tab.
|
|
activeTab->ToggleZoom();
|
|
}
|
|
}
|
|
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleTogglePaneReadOnly(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
activeTab->TogglePaneReadOnly();
|
|
}
|
|
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleEnablePaneReadOnly(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
activeTab->SetPaneReadOnly(true);
|
|
}
|
|
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleDisablePaneReadOnly(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
activeTab->SetPaneReadOnly(false);
|
|
}
|
|
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollUpPage(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ScrollPage(ScrollUp);
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollDownPage(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ScrollPage(ScrollDown);
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollToTop(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ScrollToBufferEdge(ScrollUp);
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollToBottom(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ScrollToBufferEdge(ScrollDown);
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollToMark(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<ScrollToMarkArgs>())
|
|
{
|
|
_ApplyToActiveControls([&realArgs](auto& control) {
|
|
control.ScrollToMark(realArgs.Direction());
|
|
});
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
void TerminalPage::_HandleAddMark(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<AddMarkArgs>())
|
|
{
|
|
_ApplyToActiveControls([realArgs](auto& control) {
|
|
Control::ScrollMark mark;
|
|
if (realArgs.Color())
|
|
{
|
|
mark.Color.Color = realArgs.Color().Value();
|
|
mark.Color.HasValue = true;
|
|
}
|
|
else
|
|
{
|
|
mark.Color.HasValue = false;
|
|
}
|
|
control.AddMark(mark);
|
|
});
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
void TerminalPage::_HandleClearMark(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ApplyToActiveControls([](auto& control) {
|
|
control.ClearMark();
|
|
});
|
|
args.Handled(true);
|
|
}
|
|
void TerminalPage::_HandleClearAllMarks(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ApplyToActiveControls([](auto& control) {
|
|
control.ClearAllMarks();
|
|
});
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleFindMatch(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<FindMatchArgs>())
|
|
{
|
|
if (const auto& control{ _GetActiveControl() })
|
|
{
|
|
control.SearchMatch(realArgs.Direction() == FindMatchDirection::Next);
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
void TerminalPage::_HandleOpenSettings(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<OpenSettingsArgs>())
|
|
{
|
|
_LaunchSettings(realArgs.Target());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandlePasteText(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_PasteText();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleNewTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args == nullptr)
|
|
{
|
|
LOG_IF_FAILED(_OpenNewTab(nullptr));
|
|
args.Handled(true);
|
|
}
|
|
else if (const auto& realArgs = args.ActionArgs().try_as<NewTabArgs>())
|
|
{
|
|
if (_shouldBailForInvalidProfileIndex(_settings, realArgs.ContentArgs()))
|
|
{
|
|
args.Handled(false);
|
|
return;
|
|
}
|
|
|
|
LOG_IF_FAILED(_OpenNewTab(realArgs.ContentArgs()));
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSwitchToTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SwitchToTabArgs>())
|
|
{
|
|
_SelectTab({ realArgs.TabIndex() });
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleResizePane(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<ResizePaneArgs>())
|
|
{
|
|
if (realArgs.ResizeDirection() == ResizeDirection::None)
|
|
{
|
|
// Do nothing
|
|
args.Handled(false);
|
|
}
|
|
else
|
|
{
|
|
_ResizePane(realArgs.ResizeDirection());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleMoveFocus(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<MoveFocusArgs>())
|
|
{
|
|
if (realArgs.FocusDirection() == FocusDirection::None)
|
|
{
|
|
// Do nothing
|
|
args.Handled(false);
|
|
}
|
|
else
|
|
{
|
|
// Mark as handled only when the move succeeded (e.g. when there
|
|
// is a pane to move to); otherwise, mark as unhandled so the
|
|
// keychord can propagate to the terminal (GH#6129)
|
|
const auto moveSucceeded = _MoveFocus(realArgs.FocusDirection());
|
|
args.Handled(moveSucceeded);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSwapPane(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SwapPaneArgs>())
|
|
{
|
|
if (realArgs.Direction() == FocusDirection::None)
|
|
{
|
|
// Do nothing
|
|
args.Handled(false);
|
|
}
|
|
else
|
|
{
|
|
auto swapped = _SwapPane(realArgs.Direction());
|
|
args.Handled(swapped);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleCopyText(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<CopyTextArgs>())
|
|
{
|
|
const auto copyFormatting = realArgs.CopyFormatting();
|
|
const auto format = copyFormatting ? copyFormatting.Value() : _settings.GlobalSettings().CopyFormatting();
|
|
const auto handled = _CopyText(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.WithControlSequences(), format);
|
|
args.Handled(handled);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleAdjustFontSize(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<AdjustFontSizeArgs>())
|
|
{
|
|
const auto res = _ApplyToActiveControls([&](auto& control) {
|
|
control.AdjustFontSize(realArgs.Delta());
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleFind(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
_SetFocusedTab(*activeTab);
|
|
_Find(*activeTab);
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleResetFontSize(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto res = _ApplyToActiveControls([](auto& control) {
|
|
control.ResetFontSize();
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleShaderEffects(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto res = _ApplyToActiveControls([](auto& control) {
|
|
control.ToggleShaderEffects();
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleFocusMode(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
ToggleFocusMode();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleSetFocusMode(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SetFocusModeArgs>())
|
|
{
|
|
SetFocusMode(realArgs.IsFocusMode());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleFullscreen(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
ToggleFullscreen();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleSetFullScreen(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SetFullScreenArgs>())
|
|
{
|
|
SetFullscreen(realArgs.IsFullScreen());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSetMaximized(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SetMaximizedArgs>())
|
|
{
|
|
RequestSetMaximized(realArgs.IsMaximized());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleAlwaysOnTop(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
ToggleAlwaysOnTop();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleCommandPalette(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<ToggleCommandPaletteArgs>())
|
|
{
|
|
const auto p = LoadCommandPalette();
|
|
const auto v = p.Visibility() == Visibility::Visible ? Visibility::Collapsed : Visibility::Visible;
|
|
p.EnableCommandPaletteMode(realArgs.LaunchMode());
|
|
p.Visibility(v);
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSetColorScheme(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
args.Handled(false);
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SetColorSchemeArgs>())
|
|
{
|
|
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
|
|
{
|
|
const auto res = _ApplyToActiveControls([&](auto& control) {
|
|
control.ColorScheme(scheme.ToCoreScheme());
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSetTabColor(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
Windows::Foundation::IReference<Windows::UI::Color> tabColor;
|
|
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SetTabColorArgs>())
|
|
{
|
|
tabColor = realArgs.TabColor();
|
|
}
|
|
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
if (tabColor)
|
|
{
|
|
activeTab->SetRuntimeTabColor(tabColor.Value());
|
|
}
|
|
else
|
|
{
|
|
activeTab->ResetRuntimeTabColor();
|
|
}
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenTabColorPicker(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
if (!_tabColorPicker)
|
|
{
|
|
_tabColorPicker = winrt::make<ColorPickupFlyout>();
|
|
}
|
|
|
|
activeTab->AttachColorPicker(_tabColorPicker);
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleRenameTab(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
std::optional<winrt::hstring> title;
|
|
|
|
if (const auto& realArgs = args.ActionArgs().try_as<RenameTabArgs>())
|
|
{
|
|
title = realArgs.Title();
|
|
}
|
|
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
if (title.has_value())
|
|
{
|
|
activeTab->SetTabText(title.value());
|
|
}
|
|
else
|
|
{
|
|
activeTab->ResetTabText();
|
|
}
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenTabRenamer(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
activeTab->ActivateTabRenamer();
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleExecuteCommandline(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
if (const auto& realArgs = actionArgs.ActionArgs().try_as<ExecuteCommandlineArgs>())
|
|
{
|
|
auto actions = ConvertExecuteCommandlineToActions(realArgs);
|
|
if (!actions.empty())
|
|
{
|
|
actionArgs.Handled(true);
|
|
ProcessStartupActions(std::move(actions));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleCloseOtherTabs(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
if (const auto& realArgs = actionArgs.ActionArgs().try_as<CloseOtherTabsArgs>())
|
|
{
|
|
uint32_t index;
|
|
if (realArgs.Index())
|
|
{
|
|
index = realArgs.Index().Value();
|
|
}
|
|
else if (auto focusedTabIndex = _GetFocusedTabIndex())
|
|
{
|
|
index = *focusedTabIndex;
|
|
}
|
|
else
|
|
{
|
|
// Do nothing
|
|
actionArgs.Handled(false);
|
|
return;
|
|
}
|
|
|
|
// Since _RemoveTabs is asynchronous, create a snapshot of the tabs we want to remove
|
|
std::vector<winrt::TerminalApp::Tab> tabsToRemove;
|
|
if (index > 0)
|
|
{
|
|
std::copy(begin(_tabs), begin(_tabs) + index, std::back_inserter(tabsToRemove));
|
|
}
|
|
|
|
if (index + 1 < _tabs.Size())
|
|
{
|
|
std::copy(begin(_tabs) + index + 1, end(_tabs), std::back_inserter(tabsToRemove));
|
|
}
|
|
|
|
_RemoveTabs(tabsToRemove);
|
|
|
|
actionArgs.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleCloseTabsAfter(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
if (const auto& realArgs = actionArgs.ActionArgs().try_as<CloseTabsAfterArgs>())
|
|
{
|
|
uint32_t index;
|
|
if (realArgs.Index())
|
|
{
|
|
index = realArgs.Index().Value();
|
|
}
|
|
else if (auto focusedTabIndex = _GetFocusedTabIndex())
|
|
{
|
|
index = *focusedTabIndex;
|
|
}
|
|
else
|
|
{
|
|
// Do nothing
|
|
actionArgs.Handled(false);
|
|
return;
|
|
}
|
|
|
|
// Since _RemoveTabs is asynchronous, create a snapshot of the tabs we want to remove
|
|
std::vector<winrt::TerminalApp::Tab> tabsToRemove;
|
|
std::copy(begin(_tabs) + index + 1, end(_tabs), std::back_inserter(tabsToRemove));
|
|
_RemoveTabs(tabsToRemove);
|
|
|
|
// TODO:GH#7182 For whatever reason, if you run this action
|
|
// when the tab that's currently focused is _before_ the `index`
|
|
// param, then the tabs will expand to fill the entire width of the
|
|
// tab row, until you mouse over them. Probably has something to do
|
|
// with tabs not resizing down until there's a mouse exit event.
|
|
|
|
actionArgs.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleTabSearch(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto p = LoadCommandPalette();
|
|
p.SetTabs(_tabs, _mruTabs);
|
|
p.EnableTabSearchMode();
|
|
p.Visibility(Visibility::Visible);
|
|
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleMoveTab(const IInspectable& sender,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
if (const auto& realArgs = actionArgs.ActionArgs().try_as<MoveTabArgs>())
|
|
{
|
|
const auto moved = _MoveTab(_senderOrFocusedTab(sender), realArgs);
|
|
actionArgs.Handled(moved);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleBreakIntoDebugger(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
if (_settings.GlobalSettings().DebugFeaturesEnabled())
|
|
{
|
|
actionArgs.Handled(true);
|
|
DebugBreak();
|
|
}
|
|
}
|
|
|
|
// Function Description:
|
|
// - Helper to launch a new WT instance. It can either launch the instance
|
|
// elevated or unelevated.
|
|
// - To launch elevated, it will as the shell to elevate the process for us.
|
|
// This might cause a UAC prompt. The elevation is performed on a
|
|
// background thread, as to not block the UI thread.
|
|
// Arguments:
|
|
// - newTerminalArgs: A NewTerminalArgs describing the terminal instance
|
|
// that should be spawned. The Profile should be filled in with the GUID
|
|
// of the profile we want to launch.
|
|
// Return Value:
|
|
// - <none>
|
|
// Important: Don't take the param by reference, since we'll be doing work
|
|
// on another thread.
|
|
safe_void_coroutine TerminalPage::_OpenNewWindow(const INewContentArgs newContentArgs)
|
|
{
|
|
auto terminalArgs{ newContentArgs.try_as<NewTerminalArgs>() };
|
|
|
|
// Do nothing for non-terminal panes.
|
|
//
|
|
// Theoretically, we could define a `IHasCommandline` interface, and
|
|
// stick `ToCommandline` on that interface, for any kind of pane that
|
|
// wants to be convertable to a wt commandline.
|
|
//
|
|
// Another idea we're thinking about is just `wt do {literal json for an
|
|
// action}`, which might be less leaky
|
|
if (terminalArgs == nullptr)
|
|
{
|
|
co_return;
|
|
}
|
|
|
|
// Hop to the BG thread
|
|
co_await winrt::resume_background();
|
|
|
|
// This will get us the correct exe for dev/preview/release. If you
|
|
// don't stick this in a local, it'll get mangled by ShellExecute. I
|
|
// have no idea why.
|
|
const auto exePath{ GetWtExePath() };
|
|
|
|
// Build the commandline to pass to wt for this set of NewTerminalArgs
|
|
// `-w -1` will ensure a new window is created.
|
|
const auto commandline = terminalArgs.ToCommandline();
|
|
winrt::hstring cmdline{ fmt::format(FMT_COMPILE(L"-w -1 new-tab {}"), commandline) };
|
|
|
|
// Build the args to ShellExecuteEx. We need to use ShellExecuteEx so we
|
|
// can pass the SEE_MASK_NOASYNC flag. That flag allows us to safely
|
|
// call this on the background thread, and have ShellExecute _not_ call
|
|
// back to us on the main thread. Without this, if you close the
|
|
// Terminal quickly after the UAC prompt, the elevated WT will never
|
|
// actually spawn.
|
|
SHELLEXECUTEINFOW seInfo{ 0 };
|
|
seInfo.cbSize = sizeof(seInfo);
|
|
seInfo.fMask = SEE_MASK_NOASYNC;
|
|
// `open` will just run the executable normally.
|
|
seInfo.lpVerb = L"open";
|
|
seInfo.lpFile = exePath.c_str();
|
|
seInfo.lpParameters = cmdline.c_str();
|
|
seInfo.nShow = SW_SHOWNORMAL;
|
|
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
|
|
|
|
co_return;
|
|
}
|
|
|
|
void TerminalPage::_HandleNewWindow(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
INewContentArgs newContentArgs{ nullptr };
|
|
// If the caller provided NewTerminalArgs, then try to use those
|
|
if (actionArgs)
|
|
{
|
|
if (const auto& realArgs = actionArgs.ActionArgs().try_as<NewWindowArgs>())
|
|
{
|
|
newContentArgs = realArgs.ContentArgs();
|
|
}
|
|
}
|
|
// Otherwise, if no NewTerminalArgs were provided, then just use a
|
|
// default-constructed one. The default-constructed one implies that
|
|
// nothing about the launch should be modified (just use the default
|
|
// profile).
|
|
if (!newContentArgs)
|
|
{
|
|
newContentArgs = NewTerminalArgs{};
|
|
}
|
|
|
|
if (const auto& terminalArgs{ newContentArgs.try_as<NewTerminalArgs>() })
|
|
{
|
|
const auto profile{ _settings.GetProfileForArgs(terminalArgs) };
|
|
terminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
|
|
}
|
|
|
|
// Manually fill in the evaluated profile.
|
|
_OpenNewWindow(newContentArgs);
|
|
actionArgs.Handled(true);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Raise a IdentifyWindowsRequested event. This will bubble up to the
|
|
// AppLogic, to the AppHost, to the Peasant, to the Monarch, then get
|
|
// distributed down to _all_ the Peasants, as to display info about the
|
|
// window in _every_ Peasant window.
|
|
// - This action is also buggy right now, because TeachingTips behave
|
|
// weird in XAML Islands. See microsoft-ui-xaml#4382
|
|
// Arguments:
|
|
// - <unused>
|
|
// Return Value:
|
|
// - <none>
|
|
void TerminalPage::_HandleIdentifyWindows(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
IdentifyWindowsRequested.raise(*this, nullptr);
|
|
args.Handled(true);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Display the "Toast" with the name and ID of this window.
|
|
// - Unlike _HandleIdentifyWindow**s**, this event just displays the window
|
|
// ID and name in the current window. It does not involve any bubbling
|
|
// up/down the page/logic/host/manager/peasant/monarch.
|
|
// Arguments:
|
|
// - <unused>
|
|
// Return Value:
|
|
// - <none>
|
|
void TerminalPage::_HandleIdentifyWindow(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
IdentifyWindow();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleRenameWindow(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<RenameWindowArgs>())
|
|
{
|
|
const auto newName = realArgs.Name();
|
|
const auto request = winrt::make_self<implementation::RenameWindowRequestedArgs>(newName);
|
|
RenameWindowRequested.raise(*this, *request);
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenWindowRenamer(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (WindowRenamer() == nullptr)
|
|
{
|
|
// We need to use FindName to lazy-load this object
|
|
if (auto tip{ FindName(L"WindowRenamer").try_as<MUX::Controls::TeachingTip>() })
|
|
{
|
|
tip.Closed({ get_weak(), &TerminalPage::_FocusActiveControl });
|
|
}
|
|
}
|
|
|
|
_UpdateTeachingTipTheme(WindowRenamer().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
|
|
|
|
// BODGY: GH#12021
|
|
//
|
|
// TeachingTip doesn't provide an Opened event.
|
|
// (microsoft/microsoft-ui-xaml#1607). But we want to focus the renamer
|
|
// text box when it's opened. We can't do that immediately, the TextBox
|
|
// technically isn't in the visual tree yet. We have to wait for it to
|
|
// get added some time after we call IsOpen. How do we do that reliably?
|
|
// Usually, for this kind of thing, we'd just use a one-off
|
|
// LayoutUpdated event, as a notification that the TextBox was added to
|
|
// the tree. HOWEVER:
|
|
// * The _first_ time this is fired, when the box is _first_ opened,
|
|
// tossing focus doesn't work on the first LayoutUpdated. It does
|
|
// work on the second LayoutUpdated. Okay, so we'll wait for two
|
|
// LayoutUpdated events, and focus on the second.
|
|
// * On subsequent opens: We only ever get a single LayoutUpdated.
|
|
// Period. But, you can successfully focus it on that LayoutUpdated.
|
|
//
|
|
// So, we'll keep track of how many LayoutUpdated's we've _ever_ gotten.
|
|
// If we've had at least 2, then we can focus the text box.
|
|
//
|
|
// We're also not using a ContentDialog for this, because in Xaml
|
|
// Islands a text box in a ContentDialog won't receive _any_ keypresses.
|
|
// Fun!
|
|
// WindowRenamerTextBox().Focus(FocusState::Programmatic);
|
|
_renamerLayoutUpdatedRevoker.revoke();
|
|
_renamerLayoutUpdatedRevoker = WindowRenamerTextBox().LayoutUpdated(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
|
|
if (auto self{ weakThis.get() })
|
|
{
|
|
auto& count{ self->_renamerLayoutCount };
|
|
|
|
// Don't just always increment this, we don't want to deal with overflow situations
|
|
if (count < 2)
|
|
{
|
|
count++;
|
|
}
|
|
|
|
if (count >= 2)
|
|
{
|
|
self->_renamerLayoutUpdatedRevoker.revoke();
|
|
self->WindowRenamerTextBox().Focus(FocusState::Programmatic);
|
|
}
|
|
}
|
|
});
|
|
// Make sure to mark that enter was not pressed in the renamer quite
|
|
// yet. More details in TerminalPage::_WindowRenamerKeyDown.
|
|
_renamerPressedEnter = false;
|
|
WindowRenamer().IsOpen(true);
|
|
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleDisplayWorkingDirectory(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (_settings.GlobalSettings().DebugFeaturesEnabled())
|
|
{
|
|
ShowTerminalWorkingDirectory();
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSearchForText(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto termControl{ _GetActiveControl() })
|
|
{
|
|
if (termControl.HasSelection())
|
|
{
|
|
std::wstring searchText{ termControl.SelectedText(true) };
|
|
|
|
// make it compact by replacing consecutive whitespaces with a single space
|
|
searchText = std::regex_replace(searchText, std::wregex(LR"(\s+)"), L" ");
|
|
|
|
std::wstring queryUrl;
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SearchForTextArgs>())
|
|
{
|
|
queryUrl = std::wstring_view{ realArgs.QueryUrl() };
|
|
}
|
|
}
|
|
|
|
// use global default if query URL is unspecified
|
|
if (queryUrl.empty())
|
|
{
|
|
queryUrl = std::wstring_view{ _settings.GlobalSettings().SearchWebDefaultQueryUrl() };
|
|
}
|
|
|
|
constexpr std::wstring_view queryToken{ L"%s" };
|
|
if (const auto pos{ queryUrl.find(queryToken) }; pos != std::wstring_view::npos)
|
|
{
|
|
queryUrl.replace(pos, queryToken.length(), Windows::Foundation::Uri::EscapeComponent(searchText));
|
|
}
|
|
|
|
winrt::Microsoft::Terminal::Control::OpenHyperlinkEventArgs shortcut{ queryUrl };
|
|
_OpenHyperlinkHandler(termControl, shortcut);
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenCWD(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& control{ _GetActiveControl() })
|
|
{
|
|
control.OpenCWD();
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleGlobalSummon(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
// Manually return false. These shouldn't ever get here, except for when
|
|
// we fail to register for the global hotkey. In that case, returning
|
|
// false here will let the underlying terminal still process the key, as
|
|
// if it wasn't bound at all.
|
|
args.Handled(false);
|
|
}
|
|
void TerminalPage::_HandleQuakeMode(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
// Manually return false. These shouldn't ever get here, except for when
|
|
// we fail to register for the global hotkey. In that case, returning
|
|
// false here will let the underlying terminal still process the key, as
|
|
// if it wasn't bound at all.
|
|
args.Handled(false);
|
|
}
|
|
|
|
void TerminalPage::_HandleFocusPane(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<FocusPaneArgs>())
|
|
{
|
|
const auto paneId = realArgs.Id();
|
|
|
|
// This action handler is not enlightened for _senderOrFocusedTab.
|
|
// There's currently no way for an inactive tab to be the sender of a focusPane command.
|
|
// If that ever changes, then we'll need to consider how this handler should behave.
|
|
// Should it
|
|
// * focus the tab that sent the command AND activate the requested pane?
|
|
// * or should it just activate the pane in the sender, and leave the focused tab alone?
|
|
//
|
|
// For now, we'll just focus the pane in the focused tab.
|
|
|
|
if (const auto activeTab{ _GetFocusedTabImpl() })
|
|
{
|
|
_UnZoomIfNeeded();
|
|
args.Handled(activeTab->FocusPane(paneId));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenSystemMenu(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
OpenSystemMenu.raise(*this, nullptr);
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleExportBuffer(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<ExportBufferArgs>())
|
|
{
|
|
_ExportTab(*activeTab, realArgs.Path());
|
|
args.Handled(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we didn't have args, or the args weren't ExportBufferArgs (somehow)
|
|
_ExportTab(*activeTab, L"");
|
|
if (args)
|
|
{
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleClearBuffer(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<ClearBufferArgs>())
|
|
{
|
|
const auto res = _ApplyToActiveControls([&](auto& control) {
|
|
control.ClearBuffer(realArgs.Clear());
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleMultipleActions(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<MultipleActionsArgs>())
|
|
{
|
|
for (const auto& action : realArgs.Actions())
|
|
{
|
|
_actionDispatch->DoAction(action);
|
|
}
|
|
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleAdjustOpacity(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<AdjustOpacityArgs>())
|
|
{
|
|
const auto res = _ApplyToActiveControls([&](auto& control) {
|
|
control.AdjustOpacity(realArgs.Opacity() / 100.0f, realArgs.Relative());
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSelectAll(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& control{ _senderOrActiveControl(sender) })
|
|
{
|
|
control.SelectAll();
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSaveSnippet(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if constexpr (!Feature_SaveSnippet::IsEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SaveSnippetArgs>())
|
|
{
|
|
auto commandLine = realArgs.Commandline();
|
|
if (commandLine.empty())
|
|
{
|
|
if (const auto termControl{ _GetActiveControl() })
|
|
{
|
|
if (termControl.HasSelection())
|
|
{
|
|
const auto selections{ termControl.SelectedText(true) };
|
|
const auto selection = std::accumulate(selections.begin(), selections.end(), std::wstring());
|
|
commandLine = selection;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (commandLine.empty())
|
|
{
|
|
ActionSaveFailed(L"CommandLine is Required");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
KeyChord keyChord = nullptr;
|
|
if (!realArgs.KeyChord().empty())
|
|
{
|
|
keyChord = KeyChordSerialization::FromString(winrt::to_hstring(realArgs.KeyChord()));
|
|
}
|
|
_settings.GlobalSettings().ActionMap().AddSendInputAction(realArgs.Name(), commandLine, keyChord);
|
|
_settings.WriteSettingsToDisk();
|
|
ActionSaved(commandLine, realArgs.Name(), realArgs.KeyChord());
|
|
}
|
|
catch (const winrt::hresult_error& ex)
|
|
{
|
|
auto code = ex.code();
|
|
auto message = ex.message();
|
|
ActionSaveFailed(message);
|
|
args.Handled(true);
|
|
return;
|
|
}
|
|
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord)
|
|
{
|
|
// If we haven't ever loaded the TeachingTip, then do so now and
|
|
// create the toast for it.
|
|
if (_actionSavedToast == nullptr)
|
|
{
|
|
if (auto tip{ FindName(L"ActionSavedToast").try_as<MUX::Controls::TeachingTip>() })
|
|
{
|
|
_actionSavedToast = std::make_shared<Toast>(tip);
|
|
// Make sure to use the weak ref when setting up this
|
|
// callback.
|
|
tip.Closed({ get_weak(), &TerminalPage::_FocusActiveControl });
|
|
}
|
|
}
|
|
_UpdateTeachingTipTheme(ActionSavedToast().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
|
|
|
|
SavedActionName(name);
|
|
SavedActionKeyChord(keyChord);
|
|
SavedActionCommandLine(input);
|
|
|
|
if (_actionSavedToast != nullptr)
|
|
{
|
|
_actionSavedToast->Open();
|
|
}
|
|
}
|
|
|
|
void TerminalPage::ActionSaveFailed(winrt::hstring message)
|
|
{
|
|
// If we haven't ever loaded the TeachingTip, then do so now and
|
|
// create the toast for it.
|
|
if (_actionSaveFailedToast == nullptr)
|
|
{
|
|
if (auto tip{ FindName(L"ActionSaveFailedToast").try_as<MUX::Controls::TeachingTip>() })
|
|
{
|
|
_actionSaveFailedToast = std::make_shared<Toast>(tip);
|
|
// Make sure to use the weak ref when setting up this
|
|
// callback.
|
|
tip.Closed({ get_weak(), &TerminalPage::_FocusActiveControl });
|
|
}
|
|
}
|
|
_UpdateTeachingTipTheme(ActionSaveFailedToast().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
|
|
|
|
ActionSaveFailedMessage().Text(message);
|
|
|
|
if (_actionSaveFailedToast != nullptr)
|
|
{
|
|
_actionSaveFailedToast->Open();
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSelectCommand(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SelectCommandArgs>())
|
|
{
|
|
const auto res = _ApplyToActiveControls([&](auto& control) {
|
|
control.SelectCommand(realArgs.Direction() == Settings::Model::SelectOutputDirection::Previous);
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
}
|
|
}
|
|
void TerminalPage::_HandleSelectOutput(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SelectOutputArgs>())
|
|
{
|
|
const auto res = _ApplyToActiveControls([&](auto& control) {
|
|
control.SelectOutput(realArgs.Direction() == Settings::Model::SelectOutputDirection::Previous);
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleMarkMode(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& control{ _senderOrActiveControl(sender) })
|
|
{
|
|
control.ToggleMarkMode();
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleBlockSelection(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& control{ _senderOrActiveControl(sender) })
|
|
{
|
|
const auto handled = control.ToggleBlockSelection();
|
|
args.Handled(handled);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSwitchSelectionEndpoint(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& control{ _senderOrActiveControl(sender) })
|
|
{
|
|
const auto handled = control.SwitchSelectionEndpoint();
|
|
args.Handled(handled);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSuggestions(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SuggestionsArgs>())
|
|
{
|
|
_doHandleSuggestions(realArgs);
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
safe_void_coroutine TerminalPage::_doHandleSuggestions(SuggestionsArgs realArgs)
|
|
{
|
|
const auto source = realArgs.Source();
|
|
std::vector<Command> commandsCollection;
|
|
Control::CommandHistoryContext context{ nullptr };
|
|
winrt::hstring currentCommandline;
|
|
winrt::hstring currentWorkingDirectory;
|
|
|
|
// If the user wanted to use the current commandline to filter results,
|
|
// OR they wanted command history (or some other source that
|
|
// requires context from the control)
|
|
// then get that here.
|
|
const bool shouldGetContext = realArgs.UseCommandline() ||
|
|
WI_IsAnyFlagSet(source, SuggestionsSource::CommandHistory | SuggestionsSource::QuickFixes);
|
|
if (const auto& control{ _GetActiveControl() })
|
|
{
|
|
currentWorkingDirectory = control.CurrentWorkingDirectory();
|
|
|
|
if (shouldGetContext)
|
|
{
|
|
context = control.CommandHistory();
|
|
if (context)
|
|
{
|
|
currentCommandline = context.CurrentCommandline();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Aggregate all the commands from the different sources that
|
|
// the user selected.
|
|
|
|
if (WI_IsFlagSet(source, SuggestionsSource::QuickFixes) &&
|
|
context != nullptr &&
|
|
context.QuickFixes() != nullptr)
|
|
{
|
|
// \ue74c --> OEM icon
|
|
const auto recentCommands = Command::HistoryToCommands(context.QuickFixes(), hstring{}, false, hstring{ L"\ue74c" });
|
|
for (const auto& t : recentCommands)
|
|
{
|
|
commandsCollection.push_back(t);
|
|
}
|
|
}
|
|
|
|
// Tasks are all the sendInput commands the user has saved in
|
|
// their settings file. Ask the ActionMap for those.
|
|
if (WI_IsFlagSet(source, SuggestionsSource::Tasks))
|
|
{
|
|
const auto tasks = co_await _settings.GlobalSettings().ActionMap().FilterToSnippets(currentCommandline, currentWorkingDirectory);
|
|
// ----- we may be on a background thread here -----
|
|
for (const auto& t : tasks)
|
|
{
|
|
commandsCollection.push_back(t);
|
|
}
|
|
}
|
|
|
|
// Command History comes from the commands in the buffer,
|
|
// assuming the user has enabled shell integration. Get those
|
|
// from the active control.
|
|
if (WI_IsFlagSet(source, SuggestionsSource::CommandHistory) &&
|
|
context != nullptr)
|
|
{
|
|
const auto recentCommands = Command::HistoryToCommands(context.History(), currentCommandline, false, hstring{ L"\ue81c" });
|
|
for (const auto& t : recentCommands)
|
|
{
|
|
commandsCollection.push_back(t);
|
|
}
|
|
}
|
|
|
|
co_await wil::resume_foreground(Dispatcher());
|
|
|
|
// Open the palette with all these commands in it.
|
|
_OpenSuggestions(_GetActiveControl(),
|
|
winrt::single_threaded_vector<Command>(std::move(commandsCollection)),
|
|
SuggestionsMode::Palette,
|
|
currentCommandline);
|
|
}
|
|
|
|
void TerminalPage::_HandleColorSelection(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<ColorSelectionArgs>())
|
|
{
|
|
const auto res = _ApplyToActiveControls([&](auto& control) {
|
|
control.ColorSelection(realArgs.Foreground(), realArgs.Background(), realArgs.MatchMode());
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleExpandSelectionToWord(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& control{ _GetActiveControl() })
|
|
{
|
|
const auto handled = control.ExpandSelectionToWord();
|
|
args.Handled(handled);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleBroadcastInput(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
activeTab->ToggleBroadcastInput();
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleRestartConnection(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _senderOrFocusedTab(sender) })
|
|
{
|
|
if (const auto activePane{ activeTab->GetActivePane() })
|
|
{
|
|
_restartPaneConnection(activePane->GetContent().try_as<TerminalApp::TerminalPaneContent>(), nullptr);
|
|
}
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleShowContextMenu(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& control{ _GetActiveControl() })
|
|
{
|
|
control.ShowContextMenu();
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenScratchpad(const IInspectable& sender,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (Feature_ScratchpadPane::IsEnabled())
|
|
{
|
|
const auto& scratchPane{ winrt::make_self<ScratchpadContent>() };
|
|
|
|
// This is maybe a little wacky - add our key event handler to the pane
|
|
// we made. So that we can get actions for keys that the content didn't
|
|
// handle.
|
|
scratchPane->GetRoot().KeyDown({ this, &TerminalPage::_KeyDownHandler });
|
|
|
|
const auto resultPane = std::make_shared<Pane>(*scratchPane);
|
|
_SplitPane(_senderOrFocusedTab(sender), SplitDirection::Automatic, 0.5f, resultPane);
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenAbout(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ShowAboutDialog();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleQuickFix(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& control{ _GetActiveControl() })
|
|
{
|
|
const auto handled = control.OpenQuickFixMenu();
|
|
args.Handled(handled);
|
|
}
|
|
}
|
|
}
|