diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index d044cbe13f2..724f7dafb70 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -1340,7 +1340,7 @@ namespace winrt::TerminalApp::implementation // requires context from the control) // then get that here. const bool shouldGetContext = realArgs.UseCommandline() || - WI_IsFlagSet(source, SuggestionsSource::CommandHistory); + WI_IsAnyFlagSet(source, SuggestionsSource::CommandHistory | SuggestionsSource::QuickFixes); if (shouldGetContext) { if (const auto& control{ _GetActiveControl() }) @@ -1373,7 +1373,20 @@ namespace winrt::TerminalApp::implementation if (WI_IsFlagSet(source, SuggestionsSource::CommandHistory) && context != nullptr) { - const auto recentCommands = Command::HistoryToCommands(context.History(), currentCommandline, false); + // \ue81c --> History icon + const auto recentCommands = Command::HistoryToCommands(context.History(), currentCommandline, false, hstring{ L"\ue81c" }); + for (const auto& t : recentCommands) + { + commandsCollection.push_back(t); + } + } + + if (WI_IsFlagSet(source, SuggestionsSource::QuickFixes) && + context != nullptr && + context.QuickFixes() != nullptr) + { + // \ue74c --> OEM icon + const auto recentCommands = Command::HistoryToCommands(context.QuickFixes(), hstring{ L"" }, false, hstring{ L"\ue74c" }); for (const auto& t : recentCommands) { commandsCollection.push_back(t); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index faad3d5ace3..aa0bf06cf23 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1735,6 +1735,8 @@ namespace winrt::TerminalApp::implementation term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler }); + term.SearchMissingCommand({ get_weak(), &TerminalPage::_SearchMissingCommandHandler }); + // Don't even register for the event if the feature is compiled off. if constexpr (Feature_ShellCompletions::IsEnabled()) { @@ -1753,6 +1755,12 @@ namespace winrt::TerminalApp::implementation page->_PopulateContextMenu(weakTerm.get(), sender.try_as(), true); } }); + term.QuickFixMenu().Opening([weak = get_weak(), weakTerm](auto&& sender, auto&& /*args*/) { + if (const auto& page{ weak.get() }) + { + page->_PopulateQuickFixMenu(weakTerm.get(), sender.try_as()); + } + }); } // Method Description: @@ -2985,6 +2993,30 @@ namespace winrt::TerminalApp::implementation ShowWindowChanged.raise(*this, args); } + winrt::fire_and_forget TerminalPage::_SearchMissingCommandHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::SearchMissingCommandEventArgs args) + { + assert(!Dispatcher().HasThreadAccess()); + + if (!Feature_QuickFix::IsEnabled()) + { + co_return; + } + + std::vector suggestions; + suggestions.reserve(1); + suggestions.emplace_back(fmt::format(FMT_COMPILE(L"winget install {}"), args.MissingCommand())); + + co_await wil::resume_foreground(Dispatcher()); + + auto term = _GetActiveControl(); + if (!term) + { + co_return; + } + term.UpdateWinGetSuggestions(single_threaded_vector(std::move(suggestions))); + term.RefreshQuickFixMenu(); + } + // Method Description: // - Paste text from the Windows Clipboard to the focused terminal void TerminalPage::_PasteText() @@ -4905,6 +4937,53 @@ namespace winrt::TerminalApp::implementation makeItem(RS_(L"TabClose"), L"\xE711", ActionAndArgs{ ShortcutAction::CloseTab, CloseTabArgs{ _GetFocusedTabIndex().value() } }); } + void TerminalPage::_PopulateQuickFixMenu(const TermControl& control, + const Controls::MenuFlyout& menu) + { + if (!control || !menu) + { + return; + } + + // Helper lambda for dispatching a SendInput ActionAndArgs onto the + // ShortcutActionDispatch. Used below to wire up each menu entry to the + // respective action. Then clear the quick fix menu. + auto weak = get_weak(); + auto makeCallback = [weak](const hstring& suggestion) { + return [weak, suggestion](auto&&, auto&&) { + if (auto page{ weak.get() }) + { + const auto actionAndArgs = ActionAndArgs{ ShortcutAction::SendInput, SendInputArgs{ hstring{ L"\u0003" } + suggestion } }; + page->_actionDispatch->DoAction(actionAndArgs); + if (auto ctrl = page->_GetActiveControl()) + { + ctrl.ClearQuickFix(); + } + } + }; + }; + + // Wire up each item to the action that should be performed. By actually + // connecting these to actions, we ensure the implementation is + // consistent. This also leaves room for customizing this menu with + // actions in the future. + + menu.Items().Clear(); + const auto quickFixes = control.CommandHistory().QuickFixes(); + for (const auto& qf : quickFixes) + { + MenuFlyoutItem item{}; + + auto iconElement = UI::IconPathConverter::IconWUX(L"\ue74c"); + Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw); + item.Icon(iconElement); + + item.Text(qf); + item.Click(makeCallback(qf)); + menu.Items().Append(item); + } + } + // Handler for our WindowProperties's PropertyChanged event. We'll use this // to pop the "Identify Window" toast when the user renames our window. winrt::fire_and_forget TerminalPage::_windowPropertyChanged(const IInspectable& /*sender*/, diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 70982c153b6..0a6f5d70957 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -522,6 +522,7 @@ namespace winrt::TerminalApp::implementation void _OpenSuggestions(const Microsoft::Terminal::Control::TermControl& sender, Windows::Foundation::Collections::IVector commandsCollection, winrt::TerminalApp::SuggestionsMode mode, winrt::hstring filterText); void _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args); + winrt::fire_and_forget _SearchMissingCommandHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::SearchMissingCommandEventArgs args); winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); @@ -539,6 +540,7 @@ namespace winrt::TerminalApp::implementation void _sendDraggedTabToWindow(const winrt::hstring& windowId, const uint32_t tabIndex, std::optional dragPoint); void _PopulateContextMenu(const Microsoft::Terminal::Control::TermControl& control, const Microsoft::UI::Xaml::Controls::CommandBarFlyout& sender, const bool withSelection); + void _PopulateQuickFixMenu(const Microsoft::Terminal::Control::TermControl& control, const Windows::UI::Xaml::Controls::MenuFlyout& sender); winrt::Windows::UI::Xaml::Controls::MenuFlyout _CreateRunAsAdminFlyout(int profileIndex); winrt::Microsoft::Terminal::Control::TermControl _senderOrActiveControl(const winrt::Windows::Foundation::IInspectable& sender); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index cd75d0277de..0cda0c81475 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -128,6 +128,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto pfnCompletionsChanged = [=](auto&& menuJson, auto&& replaceLength) { _terminalCompletionsChanged(menuJson, replaceLength); }; _terminal->CompletionsChangedCallback(pfnCompletionsChanged); + auto pfnSearchMissingCommand = [this](auto&& PH1) { _terminalSearchMissingCommand(std::forward(PH1)); }; + _terminal->SetSearchMissingCommandCallback(pfnSearchMissingCommand); + + auto pfnClearQuickFix = [this] { ClearQuickFix(); }; + _terminal->SetClearQuickFixCallback(pfnClearQuickFix); + // MSFT 33353327: Initialize the renderer in the ctor instead of Initialize(). // We need the renderer to be ready to accept new engines before the SwapChainPanel is ready to go. // If we wait, a screen reader may try to get the AutomationPeer (aka the UIA Engine), and we won't be able to attach @@ -1627,6 +1633,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation _midiAudio.PlayNote(reinterpret_cast(_owningHwnd), noteNumber, velocity, std::chrono::duration_cast(duration)); } + void ControlCore::_terminalSearchMissingCommand(std::wstring_view missingCommand) + { + SearchMissingCommand.raise(*this, make(hstring{ missingCommand })); + } + + void ControlCore::ClearQuickFix() + { + _cachedQuickFixes = nullptr; + RefreshQuickFixUI.raise(*this, nullptr); + } + bool ControlCore::HasSelection() const { const auto lock = _terminal->LockForReading(); @@ -2297,9 +2314,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto context = winrt::make_self(std::move(commands)); context->CurrentCommandline(trimmedCurrentCommand); + context->QuickFixes(_cachedQuickFixes); return *context; } + bool ControlCore::QuickFixesAvailable() const noexcept + { + return _cachedQuickFixes && _cachedQuickFixes.Size() > 0; + } + + void ControlCore::UpdateQuickFixes(const Windows::Foundation::Collections::IVector& quickFixes) + { + _cachedQuickFixes = quickFixes; + } + Core::Scheme ControlCore::ColorScheme() const noexcept { Core::Scheme s; diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 7b43bc2d1dd..59ebdc9e830 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -68,8 +68,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation { til::property> History; til::property CurrentCommandline; + til::property> QuickFixes; - CommandHistoryContext(std::vector&& history) + CommandHistoryContext(std::vector&& history) : + QuickFixes(winrt::single_threaded_vector()) { History(winrt::single_threaded_vector(std::move(history))); } @@ -153,6 +155,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void PersistToPath(const wchar_t* path) const; void RestoreFromPath(const wchar_t* path) const; + void ClearQuickFix(); + #pragma region ICoreState const size_t TaskbarState() const noexcept; const size_t TaskbarProgress() const noexcept; @@ -241,6 +245,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation hstring ReadEntireBuffer() const; Control::CommandHistoryContext CommandHistory() const; + bool QuickFixesAvailable() const noexcept; + void UpdateQuickFixes(const Windows::Foundation::Collections::IVector& quickFixes); void AdjustOpacity(const float opacity, const bool relative); @@ -285,6 +291,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::typed_event UpdateSelectionMarkers; til::typed_event OpenHyperlink; til::typed_event CompletionsChanged; + til::typed_event SearchMissingCommand; + til::typed_event<> RefreshQuickFixUI; til::typed_event<> CloseTerminalRequested; til::typed_event<> RestartTerminalRequested; @@ -354,6 +362,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::point _contextMenuBufferPosition{ 0, 0 }; + Windows::Foundation::Collections::IVector _cachedQuickFixes{ nullptr }; + void _setupDispatcherAndCallbacks(); bool _setFontSizeUnderLock(float fontSize); @@ -377,6 +387,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _terminalPlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration); + void _terminalSearchMissingCommand(std::wstring_view missingCommand); winrt::fire_and_forget _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength); diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index 4ded4e2ed89..c4a09a42122 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -70,6 +70,7 @@ namespace Microsoft.Terminal.Control { IVector History { get; }; String CurrentCommandline { get; }; + IVector QuickFixes { get; }; }; [default_interface] runtimeclass ControlCore : ICoreState @@ -155,6 +156,7 @@ namespace Microsoft.Terminal.Control String ReadEntireBuffer(); CommandHistoryContext CommandHistory(); + Boolean QuickFixesAvailable { get; }; void AdjustOpacity(Single Opacity, Boolean relative); void WindowVisibilityChanged(Boolean showOrHide); @@ -166,6 +168,8 @@ namespace Microsoft.Terminal.Control Boolean ShouldShowSelectCommand(); Boolean ShouldShowSelectOutput(); + void ClearQuickFix(); + // These events are called from some background thread event Windows.Foundation.TypedEventHandler TitleChanged; event Windows.Foundation.TypedEventHandler WarningBell; @@ -174,6 +178,8 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler TaskbarProgressChanged; event Windows.Foundation.TypedEventHandler RendererEnteredErrorState; event Windows.Foundation.TypedEventHandler ShowWindowChanged; + event Windows.Foundation.TypedEventHandler SearchMissingCommand; + event Windows.Foundation.TypedEventHandler RefreshQuickFixUI; // These events are always called from the UI thread (bugs aside) event Windows.Foundation.TypedEventHandler FontSizeChanged; diff --git a/src/cascadia/TerminalControl/EventArgs.cpp b/src/cascadia/TerminalControl/EventArgs.cpp index fb69e9e0541..d0631531a92 100644 --- a/src/cascadia/TerminalControl/EventArgs.cpp +++ b/src/cascadia/TerminalControl/EventArgs.cpp @@ -18,3 +18,4 @@ #include "KeySentEventArgs.g.cpp" #include "CharSentEventArgs.g.cpp" #include "StringSentEventArgs.g.cpp" +#include "SearchMissingCommandEventArgs.g.cpp" diff --git a/src/cascadia/TerminalControl/EventArgs.h b/src/cascadia/TerminalControl/EventArgs.h index 423e5695d73..6770da9f62d 100644 --- a/src/cascadia/TerminalControl/EventArgs.h +++ b/src/cascadia/TerminalControl/EventArgs.h @@ -18,6 +18,7 @@ #include "KeySentEventArgs.g.h" #include "CharSentEventArgs.g.h" #include "StringSentEventArgs.g.h" +#include "SearchMissingCommandEventArgs.g.h" namespace winrt::Microsoft::Terminal::Control::implementation { @@ -211,6 +212,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation WINRT_PROPERTY(winrt::hstring, Text); }; + + struct SearchMissingCommandEventArgs : public SearchMissingCommandEventArgsT + { + public: + SearchMissingCommandEventArgs(const winrt::hstring& missingCommand) : + MissingCommand(missingCommand) {} + + til::property MissingCommand; + }; } namespace winrt::Microsoft::Terminal::Control::factory_implementation diff --git a/src/cascadia/TerminalControl/EventArgs.idl b/src/cascadia/TerminalControl/EventArgs.idl index 8521f13f978..6cad9ccff7d 100644 --- a/src/cascadia/TerminalControl/EventArgs.idl +++ b/src/cascadia/TerminalControl/EventArgs.idl @@ -126,4 +126,9 @@ namespace Microsoft.Terminal.Control { String Text { get; }; } + + runtimeclass SearchMissingCommandEventArgs + { + String MissingCommand { get; }; + } } diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw index d618171e58b..90648381e1a 100644 --- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw @@ -301,6 +301,12 @@ Please either install the missing font or choose another one. Select output The tooltip for a button for selecting all of a command's output + + Quick fix + + + Quick fix + Restored "Restored" as in "This content was restored" @@ -317,6 +323,13 @@ Please either install the missing font or choose another one. invalid This brief message is displayed when a regular expression is invalid. + + Quick fix available + "Quick fix" is referencing the same feature as "QuickFixButton.ToolTipService.ToolTip". + + + Quick fix + Suggested input: {0} {Locked="{0}"} {0} will be replaced with a string of input that is suggested for the user to input diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index bc045f36381..6bc3d488b5b 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -40,6 +40,9 @@ constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds( // The minimum delay between emitting warning bells constexpr const auto TerminalWarningBellInterval = std::chrono::milliseconds(1000); +constexpr std::wstring_view StateNormal{ L"Normal" }; +constexpr std::wstring_view StateCollapsed{ L"Collapsed" }; + DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::Control::CopyFormat); DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::Control::MouseButtonState); @@ -220,9 +223,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation _revokers.CloseTerminalRequested = _core.CloseTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCloseTerminalRequested }); _revokers.CompletionsChanged = _core.CompletionsChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCompletionsChanged }); _revokers.RestartTerminalRequested = _core.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleRestartTerminalRequested }); + _revokers.SearchMissingCommand = _core.SearchMissingCommand(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleSearchMissingCommand }); _revokers.PasteFromClipboard = _interactivity.PasteFromClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubblePasteFromClipboard }); + _revokers.RefreshQuickFixUI = _core.RefreshQuickFixUI(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) { + RefreshQuickFixMenu(); + }); + // Initialize the terminal only once the swapchainpanel is loaded - that // way, we'll be able to query the real pixel size it got on layout _layoutUpdatedRevoker = SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) { @@ -334,6 +342,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation }); } + void TermControl::_QuickFixButton_PointerEntered(const IInspectable& /*sender*/, const PointerRoutedEventArgs& /*e*/) + { + if (!_IsClosing() && _quickFixButtonCollapsible) + { + VisualStateManager::GoToState(*this, StateNormal, false); + } + } + + void TermControl::_QuickFixButton_PointerExited(const IInspectable& /*sender*/, const PointerRoutedEventArgs& /*e*/) + { + if (!_IsClosing() && _quickFixButtonCollapsible) + { + VisualStateManager::GoToState(*this, StateCollapsed, false); + } + } + // Function Description: // - Static helper for building a new TermControl from an already existing // content. We'll attach the existing swapchain to this new control's @@ -832,6 +856,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation // When we hot reload the settings, the core will send us a scrollbar // update. If we enabled scrollbar marks, then great, when we handle // that message, we'll redraw them. + + // update the position of the quick fix menu (in case we changed the padding) + RefreshQuickFixMenu(); } // Method Description: @@ -2347,6 +2374,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _updateSelectionMarkers(nullptr, winrt::make(false)); } + + RefreshQuickFixMenu(); } hstring TermControl::Title() @@ -3506,7 +3535,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation const Control::FontSizeChangedArgs& args) { // scale the selection markers to be the size of a cell - auto scaleMarker = [args, dpiScale{ SwapChainPanel().CompositionScaleX() }](const Windows::UI::Xaml::Shapes::Path& shape) { + const auto dpiScale = SwapChainPanel().CompositionScaleX(); + auto scaleMarker = [&args, &dpiScale](const Windows::UI::Xaml::Shapes::Path& shape) { // The selection markers were designed to be 5x14 in size, // so use those dimensions below for the scaling const auto scaleX = args.Width() / 5.0 / dpiScale; @@ -3522,6 +3552,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation }; scaleMarker(SelectionStartMarker()); scaleMarker(SelectionEndMarker()); + + if (Feature_QuickFix::IsEnabled()) + { + auto quickFixBtn = FindName(L"QuickFixButton").as(); + quickFixBtn.Height(args.Height() / dpiScale); + QuickFixIcon().FontSize(static_cast(args.Width() / dpiScale)); + RefreshQuickFixMenu(); + } } void TermControl::_coreRaisedNotice(const IInspectable& /*sender*/, @@ -3590,6 +3628,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation return _core.CommandHistory(); } + void TermControl::UpdateWinGetSuggestions(Windows::Foundation::Collections::IVector suggestions) + { + get_self(_core)->UpdateQuickFixes(suggestions); + } + Core::Scheme TermControl::ColorScheme() const noexcept { return _core.ColorScheme(); @@ -3829,6 +3872,86 @@ namespace winrt::Microsoft::Terminal::Control::implementation _showContextMenuAt(_toControlOrigin(cursorPos)); } + double TermControl::QuickFixButtonWidth() + { + const auto leftPadding = GetPadding().Left; + if (_quickFixButtonCollapsible) + { + const auto cellWidth = CharacterDimensions().Width; + if (leftPadding == 0) + { + return cellWidth; + } + return leftPadding + (cellWidth / 2.0); + } + return leftPadding; + } + + double TermControl::QuickFixButtonCollapsedWidth() + { + return std::max(CharacterDimensions().Width * 2.0 / 3.0, GetPadding().Left); + } + + void TermControl::RefreshQuickFixMenu() + { + if (!Feature_QuickFix::IsEnabled()) + { + return; + } + + auto quickFixBtn = FindName(L"QuickFixButton").as(); + if (!_core.QuickFixesAvailable()) + { + quickFixBtn.Visibility(Visibility::Collapsed); + return; + } + + // If the gutter is narrow, display the collapsed version + const auto& termPadding = GetPadding(); + + // Make sure to update _quickFixButtonCollapsible and QuickFix button widths BEFORE updating the VisualState + _quickFixButtonCollapsible = termPadding.Left < CharacterDimensions().Width; + PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"QuickFixButtonWidth" }); + PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"QuickFixButtonCollapsedWidth" }); + VisualStateManager::GoToState(*this, !_quickFixButtonCollapsible ? StateNormal : StateCollapsed, false); + + const auto rd = get_self(_core)->GetRenderData(); + rd->LockConsole(); + const auto viewportBufferPosition = rd->GetViewport(); + const auto cursorBufferPosition = rd->GetCursorPosition(); + rd->UnlockConsole(); + if (cursorBufferPosition.y < viewportBufferPosition.Top() || cursorBufferPosition.y > viewportBufferPosition.BottomExclusive()) + { + quickFixBtn.Visibility(Visibility::Collapsed); + return; + } + + // draw the button in the gutter + const auto& cursorPosInDips = CursorPositionInDips(); + Controls::Canvas::SetLeft(quickFixBtn, -termPadding.Left); + Controls::Canvas::SetTop(quickFixBtn, cursorPosInDips.Y - termPadding.Top); + quickFixBtn.Visibility(Visibility::Visible); + + if (auto automationPeer{ FrameworkElementAutomationPeer::FromElement(*this) }) + { + automationPeer.RaiseNotificationEvent( + AutomationNotificationKind::ItemAdded, + AutomationNotificationProcessing::ImportantMostRecent, + RS_(L"QuickFixAvailable"), + L"QuickFixAvailableAnnouncement" /* unique name for this group of notifications */); + } + } + + void TermControl::_bubbleSearchMissingCommand(const IInspectable& /*sender*/, const Control::SearchMissingCommandEventArgs& args) + { + SearchMissingCommand.raise(*this, args); + } + + void TermControl::ClearQuickFix() + { + _core.ClearQuickFix(); + } + void TermControl::_PasteCommandHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/) { diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index e9494ceffa1..241eb1d6c7a 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -76,6 +76,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void PreviewInput(const winrt::hstring& text); Windows::Foundation::Point CursorPositionInDips(); + double QuickFixButtonWidth(); + double QuickFixButtonCollapsedWidth(); void WindowVisibilityChanged(const bool showOrHide); @@ -168,6 +170,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation hstring ReadEntireBuffer() const; Control::CommandHistoryContext CommandHistory() const; + void UpdateWinGetSuggestions(Windows::Foundation::Collections::IVector suggestions); winrt::Microsoft::Terminal::Core::Scheme ColorScheme() const noexcept; void ColorScheme(const winrt::Microsoft::Terminal::Core::Scheme& scheme) const noexcept; @@ -179,6 +182,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void RawWriteString(const winrt::hstring& text); void ShowContextMenu(); + void RefreshQuickFixMenu(); + void ClearQuickFix(); void Detach(); @@ -203,17 +208,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::typed_event KeySent; til::typed_event CharSent; til::typed_event StringSent; + til::typed_event SearchMissingCommand; // UNDER NO CIRCUMSTANCES SHOULD YOU ADD A (PROJECTED_)FORWARDED_TYPED_EVENT HERE // Those attach the handler to the core directly, and will explode if // the core ever gets detached & reattached to another window. - BUBBLED_FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs); - BUBBLED_FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable); - BUBBLED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable); - BUBBLED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable); - BUBBLED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs); - BUBBLED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable); - BUBBLED_FORWARDED_TYPED_EVENT(CompletionsChanged, IInspectable, Control::CompletionsChangedEventArgs); + BUBBLED_FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs); + BUBBLED_FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable); + BUBBLED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable); + BUBBLED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable); + BUBBLED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs); + BUBBLED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable); + BUBBLED_FORWARDED_TYPED_EVENT(CompletionsChanged, IInspectable, Control::CompletionsChangedEventArgs); BUBBLED_FORWARDED_TYPED_EVENT(RestartTerminalRequested, IInspectable, IInspectable); BUBBLED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs); @@ -243,6 +249,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool _closing{ false }; bool _focused{ false }; bool _initializedTerminal{ false }; + bool _quickFixButtonCollapsible{ false }; + bool _quickFixesAvailable{ false }; std::shared_ptr _playWarningBell; @@ -333,6 +341,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _MouseWheelHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e); void _ScrollbarChangeHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs& e); + void _QuickFixButton_PointerEntered(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e); + void _QuickFixButton_PointerExited(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e); + void _GotFocusHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); void _LostFocusHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); @@ -395,6 +406,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _contextMenuHandler(IInspectable sender, Control::ContextMenuRequestedEventArgs args); void _showContextMenuAt(const til::point& controlRelativePos); + void _bubbleSearchMissingCommand(const IInspectable& sender, const Control::SearchMissingCommandEventArgs& args); + void _PasteCommandHandler(const IInspectable& sender, const IInspectable& args); void _CopyCommandHandler(const IInspectable& sender, const IInspectable& args); void _SearchCommandHandler(const IInspectable& sender, const IInspectable& args); @@ -424,6 +437,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation Control::ControlCore::CloseTerminalRequested_revoker CloseTerminalRequested; Control::ControlCore::CompletionsChanged_revoker CompletionsChanged; Control::ControlCore::RestartTerminalRequested_revoker RestartTerminalRequested; + Control::ControlCore::SearchMissingCommand_revoker SearchMissingCommand; + Control::ControlCore::RefreshQuickFixUI_revoker RefreshQuickFixUI; // These are set up in _InitializeTerminal Control::ControlCore::RendererWarning_revoker RendererWarning; diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 5b444b5d9a3..bc003c3e667 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -69,10 +69,12 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler KeySent; event Windows.Foundation.TypedEventHandler CharSent; event Windows.Foundation.TypedEventHandler StringSent; + event Windows.Foundation.TypedEventHandler SearchMissingCommand; Microsoft.UI.Xaml.Controls.CommandBarFlyout ContextMenu { get; }; Microsoft.UI.Xaml.Controls.CommandBarFlyout SelectionContextMenu { get; }; + Windows.UI.Xaml.Controls.MenuFlyout QuickFixMenu { get; }; event Windows.Foundation.TypedEventHandler Initialized; // This is an event handler forwarder for the underlying connection. @@ -125,6 +127,7 @@ namespace Microsoft.Terminal.Control String ReadEntireBuffer(); CommandHistoryContext CommandHistory(); + void UpdateWinGetSuggestions(Windows.Foundation.Collections.IVector suggestions); void AdjustOpacity(Single Opacity, Boolean relative); void PreviewInput(String text); @@ -142,8 +145,12 @@ namespace Microsoft.Terminal.Control void ColorSelection(SelectionColor fg, SelectionColor bg, Microsoft.Terminal.Core.MatchMode matchMode); Windows.Foundation.Point CursorPositionInDips { get; }; + Double QuickFixButtonWidth { get; }; + Double QuickFixButtonCollapsedWidth { get; }; void ShowContextMenu(); + void RefreshQuickFixMenu(); + void ClearQuickFix(); void Detach(); } diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml index feb8508440d..bcbabf0daf4 100644 --- a/src/cascadia/TerminalControl/TermControl.xaml +++ b/src/cascadia/TerminalControl/TermControl.xaml @@ -1279,6 +1279,32 @@ + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 24327e9eb5d..963ae2c19a8 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -700,34 +700,44 @@ TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD s vkey = _VirtualKeyFromCharacter(ch); } - // GH#1527: When the user has auto mark prompts enabled, we're going to try - // and heuristically detect if this was the line the prompt was on. - // * If the key was an Enter keypress (Terminal.app also marks ^C keypresses - // as prompts. That's omitted for now.) - // * AND we're not in the alt buffer - // - // Then treat this line like it's a prompt mark. - if (_autoMarkPrompts && vkey == VK_RETURN && !_inAltBuffer()) + if (vkey == VK_RETURN && !_inAltBuffer()) { - // We need to be a little tricky here, to try and support folks that are - // auto-marking prompts, but don't necessarily have the rest of shell - // integration enabled. - // - // We'll set the current attributes to Output, so that the output after - // here is marked as the command output. But we also need to make sure - // that a mark was started. - // We can't just check if the current row has a mark - there could be a - // multiline prompt. - // - // (TextBuffer::_createPromptMarkIfNeeded does that work for us) + // Treat VK_RETURN as a new prompt, + // so we should clear the quick fix UI if it's visible. + if (_pfnClearQuickFix) + { + _pfnClearQuickFix(); + } - const bool createdMark = _activeBuffer().StartOutput(); - if (createdMark) + // GH#1527: When the user has auto mark prompts enabled, we're going to try + // and heuristically detect if this was the line the prompt was on. + // * If the key was an Enter keypress (Terminal.app also marks ^C keypresses + // as prompts. That's omitted for now.) + // * AND we're not in the alt buffer + // + // Then treat this line like it's a prompt mark. + if (_autoMarkPrompts) { - _activeBuffer().ManuallyMarkRowAsPrompt(_activeBuffer().GetCursor().GetPosition().y); + // We need to be a little tricky here, to try and support folks that are + // auto-marking prompts, but don't necessarily have the rest of shell + // integration enabled. + // + // We'll set the current attributes to Output, so that the output after + // here is marked as the command output. But we also need to make sure + // that a mark was started. + // We can't just check if the current row has a mark - there could be a + // multiline prompt. + // + // (TextBuffer::_createPromptMarkIfNeeded does that work for us) + + const bool createdMark = _activeBuffer().StartOutput(); + if (createdMark) + { + _activeBuffer().ManuallyMarkRowAsPrompt(_activeBuffer().GetCursor().GetPosition().y); - // This changed the scrollbar marks - raise a notification to update them - _NotifyScrollEvent(); + // This changed the scrollbar marks - raise a notification to update them + _NotifyScrollEvent(); + } } } @@ -1228,6 +1238,16 @@ void Microsoft::Terminal::Core::Terminal::CompletionsChangedCallback(std::functi _pfnCompletionsChanged.swap(pfn); } +void Microsoft::Terminal::Core::Terminal::SetSearchMissingCommandCallback(std::function pfn) noexcept +{ + _pfnSearchMissingCommand.swap(pfn); +} + +void Microsoft::Terminal::Core::Terminal::SetClearQuickFixCallback(std::function pfn) noexcept +{ + _pfnClearQuickFix.swap(pfn); +} + // Method Description: // - Stores the search highlighted regions in the terminal void Terminal::SetSearchHighlights(const std::vector& highlights) noexcept diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 367ee14816f..663a649dc28 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -157,6 +157,8 @@ class Microsoft::Terminal::Core::Terminal final : void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) override; + void SearchMissingCommand(const std::wstring_view command) override; + #pragma endregion void ClearMark(); @@ -229,6 +231,8 @@ class Microsoft::Terminal::Core::Terminal final : void SetShowWindowCallback(std::function pfn) noexcept; void SetPlayMidiNoteCallback(std::function pfn) noexcept; void CompletionsChangedCallback(std::function pfn) noexcept; + void SetSearchMissingCommandCallback(std::function pfn) noexcept; + void SetClearQuickFixCallback(std::function pfn) noexcept; void SetSearchHighlights(const std::vector& highlights) noexcept; void SetSearchHighlightFocused(const size_t focusedIdx); @@ -339,6 +343,8 @@ class Microsoft::Terminal::Core::Terminal final : std::function _pfnShowWindowChanged; std::function _pfnPlayMidiNote; std::function _pfnCompletionsChanged; + std::function _pfnSearchMissingCommand; + std::function _pfnClearQuickFix; RenderSettings _renderSettings; std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index aa06c101295..f916521d91a 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -333,6 +333,14 @@ void Terminal::InvokeCompletions(std::wstring_view menuJson, unsigned int replac } } +void Terminal::SearchMissingCommand(const std::wstring_view command) +{ + if (_pfnSearchMissingCommand) + { + _pfnSearchMissingCommand(command); + } +} + void Terminal::NotifyBufferRotation(const int delta) { // Update our selection, so it doesn't move as the buffer is cycled diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 3f0a922b6b7..ae3d7d50029 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -928,6 +928,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation BASIC_FACTORY(SetTabColorArgs); BASIC_FACTORY(RenameTabArgs); BASIC_FACTORY(SwapPaneArgs); + BASIC_FACTORY(SendInputArgs); BASIC_FACTORY(SplitPaneArgs); BASIC_FACTORY(SetFocusModeArgs); BASIC_FACTORY(SetFullScreenArgs); diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index af03e807176..5992f2d5217 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -119,6 +119,7 @@ namespace Microsoft.Terminal.Settings.Model Tasks = 0x1, CommandHistory = 0x2, DirectoryHistory = 0x4, + QuickFixes = 0x8, All = 0xffffffff, }; @@ -225,6 +226,8 @@ namespace Microsoft.Terminal.Settings.Model [default_interface] runtimeclass SendInputArgs : IActionArgs { + SendInputArgs(String input); + String Input { get; }; }; diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index b79c398351c..21ea3431993 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -673,7 +673,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // the command will be run as a directory change instead. IVector Command::HistoryToCommands(IVector history, winrt::hstring currentCommandline, - bool directories) + bool directories, + winrt::hstring iconPath) { std::wstring cdText = directories ? L"cd " : L""; auto result = std::vector(); @@ -705,9 +706,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation auto command = winrt::make_self(); command->_ActionAndArgs = actionAndArgs; command->_name = winrt::hstring{ line }; - command->_iconPath = directories ? - L"\ue8da" : // OpenLocal (a folder with an arrow pointing up) - L"\ue81c"; // History icon + command->_iconPath = iconPath; result.push_back(*command); foundCommands[line] = true; } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index e67b593e561..194ed7dc95e 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -79,7 +79,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static Windows::Foundation::Collections::IVector ParsePowerShellMenuComplete(winrt::hstring json, int32_t replaceLength); static Windows::Foundation::Collections::IVector HistoryToCommands(Windows::Foundation::Collections::IVector history, winrt::hstring currentCommandline, - bool directories); + bool directories, + hstring iconPath); WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None); WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs); diff --git a/src/cascadia/TerminalSettingsModel/Command.idl b/src/cascadia/TerminalSettingsModel/Command.idl index aacc2a1bd74..a3a316e1a2e 100644 --- a/src/cascadia/TerminalSettingsModel/Command.idl +++ b/src/cascadia/TerminalSettingsModel/Command.idl @@ -48,7 +48,7 @@ namespace Microsoft.Terminal.Settings.Model Windows.Foundation.Collections.IMapView NestedCommands { get; }; static IVector ParsePowerShellMenuComplete(String json, Int32 replaceLength); - static IVector HistoryToCommands(IVector commandHistory, String commandline, Boolean directories); + static IVector HistoryToCommands(IVector commandHistory, String commandline, Boolean directories, String iconPath); } } diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index 76aa31ae595..9737ef45539 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -501,11 +501,12 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::FindMatchDirecti JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::SuggestionsSource) { - static constexpr std::array mappings = { + static constexpr std::array mappings = { pair_type{ "none", AllClear }, pair_type{ "tasks", ValueType::Tasks }, pair_type{ "commandHistory", ValueType::CommandHistory }, pair_type{ "directoryHistory", ValueType::DirectoryHistory }, + pair_type{ "quickFix", ValueType::QuickFixes }, pair_type{ "all", AllSet }, }; }; diff --git a/src/features.xml b/src/features.xml index c2af5ed009a..47d86790f9d 100644 --- a/src/features.xml +++ b/src/features.xml @@ -155,4 +155,15 @@ + + Feature_QuickFix + Enables the Quick Fix menu + 16599 + AlwaysDisabled + + Dev + Canary + + + diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 151a472f69d..6a45c44c019 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -419,3 +419,7 @@ void ConhostInternalGetSet::InvokeCompletions(std::wstring_view /*menuJson*/, un { // Not implemented for conhost. } +void ConhostInternalGetSet::SearchMissingCommand(std::wstring_view /*missingCommand*/) +{ + // Not implemented for conhost. +} diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index 36283087ac2..9f083f60625 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -70,6 +70,8 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal:: void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) override; + void SearchMissingCommand(std::wstring_view missingCommand) override; + private: Microsoft::Console::IIoProvider& _io; }; diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index e3976996433..a83205b4503 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -147,6 +147,8 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch virtual bool DoVsCodeAction(const std::wstring_view string) = 0; + virtual bool DoWTAction(const std::wstring_view string) = 0; + virtual StringHandler DownloadDRCS(const VTInt fontNumber, const VTParameter startChar, const DispatchTypes::DrcsEraseControl eraseControl, diff --git a/src/terminal/adapter/ITerminalApi.hpp b/src/terminal/adapter/ITerminalApi.hpp index 2c3aec044ed..c3978b406f7 100644 --- a/src/terminal/adapter/ITerminalApi.hpp +++ b/src/terminal/adapter/ITerminalApi.hpp @@ -88,5 +88,7 @@ namespace Microsoft::Console::VirtualTerminal virtual void NotifyBufferRotation(const int delta) = 0; virtual void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) = 0; + + virtual void SearchMissingCommand(const std::wstring_view command) = 0; }; } diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 1335dccb54f..1a4d5b431c4 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -3995,6 +3995,54 @@ bool AdaptDispatch::DoVsCodeAction(const std::wstring_view string) return false; } +// Method Description: +// - Performs a Windows Terminal action +// - Currently, the actions we support are: +// * CmdNotFound: A protocol for passing commands that the shell couldn't resolve +// to the terminal. The command is then shared with WinGet to see if it can +// find a package that provides that command, which is then displayed to the +// user. +// - Not actually used in conhost +// Arguments: +// - string: contains the parameters that define which action we do +// Return Value: +// - false in conhost, true for the CmdNotFound action, otherwise false. +bool AdaptDispatch::DoWTAction(const std::wstring_view string) +{ + // This is not implemented in conhost. + if (_api.IsConsolePty()) + { + // Flush the frame manually to make sure this action happens at the right time. + _renderer->TriggerFlush(false); + return false; + } + + const auto parts = Utils::SplitString(string, L';'); + + if (parts.size() < 1) + { + return false; + } + + const auto action = til::at(parts, 0); + + if (action == L"CmdNotFound") + { + // The structure of the message is as follows: + // `e]9001; + // 0: CmdNotFound; + // 1: $($cmdNotFound.missingCmd); + if (parts.size() >= 2) + { + const std::wstring_view missingCmd = til::at(parts, 1); + _api.SearchMissingCommand(missingCmd); + } + + return true; + } + return false; +} + // Method Description: // - DECDLD - Downloads one or more characters of a dynamically redefinable // character set (DRCS) with a specified pixel pattern. The pixel array is diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 7087932a497..4c9c167e928 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -150,6 +150,8 @@ namespace Microsoft::Console::VirtualTerminal bool DoVsCodeAction(const std::wstring_view string) override; + bool DoWTAction(const std::wstring_view string) override; + StringHandler DownloadDRCS(const VTInt fontNumber, const VTParameter startChar, const DispatchTypes::DrcsEraseControl eraseControl, diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index 3a1d19a0964..1480c38fa1e 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -140,6 +140,8 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons bool DoVsCodeAction(const std::wstring_view /*string*/) override { return false; } + bool DoWTAction(const std::wstring_view /*string*/) override { return false; } + StringHandler DownloadDRCS(const VTInt /*fontNumber*/, const VTParameter /*startChar*/, const DispatchTypes::DrcsEraseControl /*eraseControl*/, diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index 81bd119abe0..8d5ca44588a 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -218,6 +218,11 @@ class TestGetSet final : public ITerminalApi VERIFY_ARE_EQUAL(_expectedReplaceLength, replaceLength); } + void SearchMissingCommand(const std::wstring_view /*command*/) override + { + Log::Comment(L"SearchMissingCommand MOCK called..."); + } + void PrepData() { PrepData(CursorDirection::UP); // if called like this, the cursor direction doesn't matter. diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 7568f154677..17c9615a726 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -913,6 +913,11 @@ bool OutputStateMachineEngine::ActionOscDispatch(const size_t parameter, const s success = _dispatch->DoVsCodeAction(string); break; } + case OscActionCodes::WTAction: + { + success = _dispatch->DoWTAction(string); + break; + } default: // If no functions to call, overall dispatch was a failure. success = false; diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index 0970e45dee2..62de23e9a4b 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -229,6 +229,7 @@ namespace Microsoft::Console::VirtualTerminal FinalTermAction = 133, VsCodeAction = 633, ITerm2Action = 1337, + WTAction = 9001, }; bool _GetOscSetColorTable(const std::wstring_view string,