Compare commits

...

31 Commits

Author SHA1 Message Date
Mike Griese
98dc5a519e some improvements 2023-03-13 14:02:49 -05:00
Mike Griese
455407ce00 Reference commit 2023-03-13 13:31:53 -05:00
Mike Griese
22c94bf10d spell 2023-03-10 14:57:56 -06:00
Mike Griese
cd2db82515 Merge branch 'main' into dev/migrie/fhl-2023/pwsh-autocomplete-demo 2023-03-10 14:57:31 -06:00
Mike Griese
20f5651224 final cleanup for review 2023-03-01 11:07:24 -06:00
Mike Griese
173a830339 Finish detaching it from the popup
(cherry picked from commit a172f53597)
2023-03-01 08:23:39 -06:00
Mike Griese
e544871893 very confident we can get all the logic into the sxnui
(cherry picked from commit 57a53279ae)
2023-03-01 08:23:32 -06:00
Mike Griese
e2cc27889a POC: We can take it out of the Popup and have it still work
(cherry picked from commit 5defde545f)
2023-03-01 08:23:16 -06:00
Mike Griese
36b5759604 [PARENT] lmao I deleted the line that sends the inpu :facepalm" 2023-02-24 10:43:30 -06:00
Mike Griese
84a41b4504 cleanup _for review_? 2023-02-23 16:34:16 -06:00
Mike Griese
7988d89c08 code cleanup 2023-02-23 14:08:07 -06:00
Mike Griese
a3c4776355 clamp horizontally 2023-02-23 14:01:36 -06:00
Mike Griese
4516b4b1b3 Properly account for the position of panes when opening the menu 2023-02-22 14:51:47 -06:00
Mike Griese
f5909d9baa Open in different directions based on available space 2023-02-22 13:56:01 -06:00
Mike Griese
8feb9098b9 bottom-up mode is basically done 2023-02-21 15:52:39 -06:00
Mike Griese
d94183b897 icons are _slick_ 2023-02-21 14:25:58 -06:00
Mike Griese
2c66c32f97 bottoms up bottoms up 2023-02-21 11:20:03 -06:00
Mike Griese
ff5eead9ea Very important that the backspaces are trimmed from the preview 2023-02-21 10:25:55 -06:00
Mike Griese
026f342bc5 Merge remote-tracking branch 'origin/dev/migrie/f/3121-wE-dOnT-hAvE-dEv-DaYs' into dev/migrie/fhl-2023/pwsh-autocomplete-demo 2023-02-21 10:00:20 -06:00
Mike Griese
48e7348f5c small updates to the protocol 2023-02-16 13:04:13 -06:00
Mike Griese
e785bfc8bb Merge branch 'main' into dev/migrie/f/12861-preview-input 2023-02-16 13:02:40 -06:00
Mike Griese
985fcdbdb6 better UX for typing 2023-02-12 15:37:36 -06:00
Mike Griese
b0fa972ec1 make the menu mode compact, and remove the search box 2023-02-12 11:07:00 -06:00
Mike Griese
f361b6c879 lots of removal of dead code from the sxnui 2023-02-10 06:55:16 -06:00
Mike Griese
7404dc3d35 zhu li, do the thing 2023-02-10 06:02:03 -06:00
Mike Griese
c97ac66d5d resart with fresh plumbing 2023-02-09 06:31:44 -06:00
Dustin L. Howett
ccfc83443b Migrate spelling-0.0.21 changes from main 2022-04-29 06:55:15 -05:00
Mike Griese
0bda66fc2f a comment I missed 2022-04-29 06:55:15 -05:00
Mike Griese
d3b5533a1e fix remaining bugs 2022-04-29 06:22:48 -05:00
Mike Griese
1449088e80 bugfixes for the demo 2022-04-28 16:17:21 -05:00
Mike Griese
c96799c6e9 Preview the input via the TSF input control. This is awesome, and should go into main 2022-04-28 16:14:51 -05:00
44 changed files with 2243 additions and 8 deletions

View File

@@ -93,6 +93,7 @@ slnt
Sos
ssh
stakeholders
sxn
timeline
timelines
timestamped

View File

@@ -311,6 +311,7 @@ CPLINFO
cplusplus
CPPCORECHECK
cppcorecheckrules
cpprest
cpprestsdk
cppwinrt
CProc
@@ -1436,6 +1437,7 @@ PPEB
ppf
ppguid
ppidl
pplx
PPROC
PPROCESS
ppropvar
@@ -2113,6 +2115,7 @@ WDDMCONSOLECONTEXT
wdm
webpage
websites
websockets
wekyb
wex
wextest

View File

@@ -50,6 +50,7 @@ namespace winrt::TerminalApp::implementation
{
case ShortcutAction::SetColorScheme:
case ShortcutAction::AdjustOpacity:
case ShortcutAction::SendInput:
{
_RunRestorePreviews();
break;
@@ -140,6 +141,22 @@ namespace winrt::TerminalApp::implementation
});
}
void TerminalPage::_PreviewSendInput(const Settings::Model::SendInputArgs& args)
{
const auto backup = _restorePreviewFuncs.empty();
_ApplyToActiveControls([&](const auto& control) {
control.PreviewInput(args.Input());
if (backup)
{
_restorePreviewFuncs.emplace_back([=]() {
// On dismiss:
control.PreviewInput(L"");
});
}
});
}
void TerminalPage::_PreviewAction(const Settings::Model::ActionAndArgs& args)
{
switch (args.Action())
@@ -150,6 +167,9 @@ namespace winrt::TerminalApp::implementation
case ShortcutAction::AdjustOpacity:
_PreviewAdjustOpacity(args.Args().try_as<AdjustOpacityArgs>());
break;
case ShortcutAction::SendInput:
_PreviewSendInput(args.Args().try_as<SendInputArgs>());
break;
}
// GH#9818 Other ideas for actions that could be preview-able:

View File

@@ -225,15 +225,24 @@ namespace winrt::TerminalApp::implementation
void CommandPalette::_selectedCommandChanged(const IInspectable& /*sender*/,
const Windows::UI::Xaml::RoutedEventArgs& /*args*/)
{
const auto currentlyVisible{ Visibility() == Visibility::Visible };
const auto selectedCommand = _filteredActionsView().SelectedItem();
const auto filteredCommand{ selectedCommand.try_as<winrt::TerminalApp::FilteredCommand>() };
if (_currentMode == CommandPaletteMode::TabSwitchMode)
{
_switchToTab(filteredCommand);
}
else if (_currentMode == CommandPaletteMode::ActionMode && filteredCommand != nullptr)
else if (_currentMode == CommandPaletteMode::ActionMode &&
currentlyVisible)
{
if (const auto actionPaletteItem{ filteredCommand.Item().try_as<winrt::TerminalApp::ActionPaletteItem>() })
// If we don't have a selected command, then end any previews we
// might currently be showing.
if (filteredCommand == nullptr)
{
_PreviewActionHandlers(*this, nullptr);
}
else if (const auto actionPaletteItem{ filteredCommand.Item().try_as<winrt::TerminalApp::ActionPaletteItem>() })
{
_PreviewActionHandlers(*this, actionPaletteItem.Command());
}
@@ -1083,7 +1092,9 @@ namespace winrt::TerminalApp::implementation
{
std::copy(begin(commandsToFilter), end(commandsToFilter), std::back_inserter(actions));
}
else if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::ActionMode || _currentMode == CommandPaletteMode::CommandlineMode)
else if (_currentMode == CommandPaletteMode::TabSearchMode ||
_currentMode == CommandPaletteMode::ActionMode ||
_currentMode == CommandPaletteMode::CommandlineMode)
{
for (const auto& action : commandsToFilter)
{

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,135 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "FilteredCommand.h"
#include "SuggestionsControl.g.h"
#include "AppCommandlineArgs.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class TabTests;
};
namespace winrt::TerminalApp::implementation
{
struct SuggestionsControl : SuggestionsControlT<SuggestionsControl>
{
SuggestionsControl();
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> FilteredActions();
void SetCommands(const Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command>& actions);
void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap);
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
void SelectNextItem(const bool moveDown);
void ScrollPageUp();
void ScrollPageDown();
void ScrollToTop();
void ScrollToBottom();
Windows::UI::Xaml::FrameworkElement SelectedItem();
TerminalApp::SuggestionsMode Mode() const;
void Mode(TerminalApp::SuggestionsMode mode);
void Anchor(Windows::Foundation::Point anchor, Windows::Foundation::Size space, float characterHeight);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, NoMatchesText, _PropertyChangedHandlers);
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SearchBoxPlaceholderText, _PropertyChangedHandlers);
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, ControlName, _PropertyChangedHandlers);
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, ParentCommandName, _PropertyChangedHandlers);
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, ParsedCommandLineText, _PropertyChangedHandlers);
TYPED_EVENT(SwitchToTabRequested, winrt::TerminalApp::SuggestionsControl, winrt::TerminalApp::TabBase);
TYPED_EVENT(CommandLineExecutionRequested, winrt::TerminalApp::SuggestionsControl, winrt::hstring);
TYPED_EVENT(DispatchCommandRequested, winrt::TerminalApp::SuggestionsControl, Microsoft::Terminal::Settings::Model::Command);
TYPED_EVENT(PreviewAction, Windows::Foundation::IInspectable, Microsoft::Terminal::Settings::Model::Command);
private:
friend struct SuggestionsControlT<SuggestionsControl>; // for Xaml to bind events
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _allCommands{ nullptr };
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _currentNestedCommands{ nullptr };
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> _filteredActions{ nullptr };
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _nestedActionStack{ nullptr };
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _commandsToFilter();
TerminalApp::SuggestionsMode _mode{ TerminalApp::SuggestionsMode::Palette };
TerminalApp::SuggestionsDirection _direction{ TerminalApp::SuggestionsDirection::TopDown };
bool _lastFilterTextWasEmpty{ true };
Windows::Foundation::Point _anchor;
Windows::Foundation::Size _space;
void _filterTextChanged(const Windows::Foundation::IInspectable& sender,
const Windows::UI::Xaml::RoutedEventArgs& args);
void _previewKeyDownHandler(const Windows::Foundation::IInspectable& sender,
const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _keyUpHandler(const Windows::Foundation::IInspectable& sender,
const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _selectedCommandChanged(const Windows::Foundation::IInspectable& sender,
const Windows::UI::Xaml::RoutedEventArgs& args);
void _updateUIForStackChange();
void _rootPointerPressed(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
void _lostFocusHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
void _backdropPointerPressed(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
void _listItemClicked(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::ItemClickEventArgs& e);
void _listItemSelectionChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& e);
void _moveBackButtonClicked(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs&);
void _updateFilteredActions();
void _updateCurrentNestedCommands(const winrt::Microsoft::Terminal::Settings::Model::Command& parentCommand);
std::vector<winrt::TerminalApp::FilteredCommand> _collectFilteredActions();
void _close();
void _switchToMode();
void _setDirection(TerminalApp::SuggestionsDirection direction);
std::wstring _getTrimmedInput();
Microsoft::Terminal::Settings::Model::IActionMapView _actionMap{ nullptr };
winrt::Windows::UI::Xaml::Controls::ListView::SizeChanged_revoker _sizeChangedRevoker;
void _dispatchCommand(const winrt::TerminalApp::FilteredCommand& command);
static std::optional<winrt::TerminalApp::FilteredCommand> _buildCommandLineCommand(const winrt::hstring& commandLine);
void _dismissPalette();
void _scrollToIndex(uint32_t index);
uint32_t _getNumVisibleItems();
void _choosingItemContainer(const Windows::UI::Xaml::Controls::ListViewBase& sender, const Windows::UI::Xaml::Controls::ChoosingItemContainerEventArgs& args);
void _containerContentChanging(const Windows::UI::Xaml::Controls::ListViewBase& sender, const Windows::UI::Xaml::Controls::ContainerContentChangingEventArgs& args);
winrt::TerminalApp::PaletteItemTemplateSelector _itemTemplateSelector{ nullptr };
std::unordered_map<Windows::UI::Xaml::DataTemplate, std::unordered_set<Windows::UI::Xaml::Controls::Primitives::SelectorItem>> _listViewItemsCache;
Windows::UI::Xaml::DataTemplate _listItemTemplate;
friend class TerminalAppLocalTests::TabTests;
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(SuggestionsControl);
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "TabBase.idl";
import "IDirectKeyListener.idl";
import "HighlightedTextControl.idl";
import "FilteredCommand.idl";
namespace TerminalApp
{
enum SuggestionsMode
{
Palette = 0,
Menu,
// Inline,
};
enum SuggestionsDirection {
TopDown,
BottomUp
};
[default_interface] runtimeclass SuggestionsControl : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged, IDirectKeyListener
{
SuggestionsControl();
String NoMatchesText { get; };
String SearchBoxPlaceholderText { get; };
String ControlName { get; };
String ParentCommandName { get; };
String ParsedCommandLineText { get; };
Windows.UI.Xaml.FrameworkElement SelectedItem { get; };
Windows.Foundation.Collections.IObservableVector<FilteredCommand> FilteredActions { get; };
SuggestionsMode Mode { get; set; };
void SetCommands(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> actions);
void SetActionMap(Microsoft.Terminal.Settings.Model.IActionMapView actionMap);
void SelectNextItem(Boolean moveDown);
void Anchor(Windows.Foundation.Point anchor, Windows.Foundation.Size space, Single characterHeight);
event Windows.Foundation.TypedEventHandler<SuggestionsControl, Microsoft.Terminal.Settings.Model.Command> DispatchCommandRequested;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Settings.Model.Command> PreviewAction;
}
}

View File

@@ -0,0 +1,328 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<UserControl x:Class="TerminalApp.SuggestionsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SettingsModel="using:Microsoft.Terminal.Settings.Model"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TerminalApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:Microsoft.Terminal.Settings.Model"
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
AllowFocusOnInteraction="True"
AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}"
IsTabStop="True"
LostFocus="_lostFocusHandler"
PointerPressed="_rootPointerPressed"
PreviewKeyDown="_previewKeyDownHandler"
PreviewKeyUp="_keyUpHandler"
TabNavigation="Cycle"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<!-- This creates an instance of our CommandKeyChordVisibilityConverter we can reference below -->
<local:EmptyStringVisibilityConverter x:Key="CommandKeyChordVisibilityConverter" />
<local:EmptyStringVisibilityConverter x:Key="ParsedCommandLineTextVisibilityConverter" />
<local:EmptyStringVisibilityConverter x:Key="ParentCommandVisibilityConverter" />
<model:IconPathConverter x:Key="IconSourceConverter" />
<DataTemplate x:Key="ListItemTemplate"
x:DataType="local:FilteredCommand">
<ListViewItem Height="32"
MinHeight="0"
Padding="16,0,12,0"
HorizontalContentAlignment="Stretch"
AutomationProperties.AcceleratorKey="{x:Bind Item.KeyChordText, Mode=OneWay}"
AutomationProperties.Name="{x:Bind Item.Name, Mode=OneWay}"
FontSize="12" />
</DataTemplate>
<DataTemplate x:Key="GeneralItemTemplate"
x:DataType="local:FilteredCommand">
<Grid HorizontalAlignment="Stretch"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<!-- icon -->
<ColumnDefinition Width="Auto" />
<!-- command label -->
<ColumnDefinition Width="*" />
<!-- key chord -->
<ColumnDefinition Width="16" />
<!-- gutter for scrollbar -->
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
Width="16"
Height="16"
Content="{x:Bind Item.ResolvedIcon, Mode=OneWay}" />
<local:HighlightedTextControl Grid.Column="1"
HorizontalAlignment="Left"
Text="{x:Bind HighlightedName, Mode=OneWay}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="NestedItemTemplate"
x:DataType="local:FilteredCommand">
<Grid HorizontalAlignment="Stretch"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<!-- icon -->
<ColumnDefinition Width="Auto" />
<!-- command label -->
<ColumnDefinition Width="*" />
<!-- key chord -->
<ColumnDefinition Width="16" />
<!-- gutter for scrollbar -->
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
Width="16"
Height="16"
Content="{x:Bind Item.ResolvedIcon, Mode=OneWay}" />
<local:HighlightedTextControl Grid.Column="1"
HorizontalAlignment="Left"
Text="{x:Bind HighlightedName, Mode=OneWay}" />
<!--
The block for the key chord is only visible
when there's actual text set as the label. See
CommandKeyChordVisibilityConverter for details.
We're setting the accessibility view on the
border and text block to Raw because otherwise,
Narrator will read out the key chord. Problem is,
it already did that because it was the list item's
"AcceleratorKey". It's redundant.
-->
<Border Grid.Column="2"
Padding="2,0,2,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
Style="{ThemeResource KeyChordBorderStyle}"
Visibility="{x:Bind Item.KeyChordText, Mode=OneWay, Converter={StaticResource CommandKeyChordVisibilityConverter}}">
<TextBlock AutomationProperties.AccessibilityView="Raw"
FontSize="12"
Style="{ThemeResource KeyChordTextBlockStyle}"
Text="{x:Bind Item.KeyChordText, Mode=OneWay}" />
</Border>
<FontIcon Grid.Column="2"
HorizontalAlignment="Right"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="12"
Glyph="&#xE76C;" />
</Grid>
</DataTemplate>
<local:PaletteItemTemplateSelector x:Key="PaletteItemTemplateSelector"
GeneralItemTemplate="{StaticResource GeneralItemTemplate}"
NestedItemTemplate="{StaticResource NestedItemTemplate}" />
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<!-- ParsedCommandLineText styles -->
<Style x:Key="ParsedCommandLineBorderStyle"
TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
</Style>
<Style x:Key="ParsedCommandLineTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<!-- ParsedCommandLineText styles -->
<Style x:Key="ParsedCommandLineBorderStyle"
TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
</Style>
<Style x:Key="ParsedCommandLineTextBlockStyle"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<!-- KeyChordText styles (use XAML defaults for High Contrast theme) -->
<Style x:Key="KeyChordBorderStyle"
TargetType="Border" />
<Style x:Key="KeyChordTextBlockStyle"
TargetType="TextBlock" />
<!-- ParsedCommandLineText styles (use XAML defaults for High Contrast theme) -->
<Style x:Key="ParsedCommandLineBorderStyle"
TargetType="Border" />
<Style x:Key="ParsedCommandLineTextBlockStyle"
TargetType="TextBlock" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="8*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<Grid x:Name="_backdrop"
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Grid.ColumnSpan="3"
MaxWidth="300"
MaxHeight="300"
Margin="0"
Padding="0,8,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{ThemeResource FlyoutPresenterBackground}"
BorderBrush="{ThemeResource FlyoutBorderThemeBrush}"
BorderThickness="{ThemeResource FlyoutBorderThemeThickness}"
CornerRadius="{ThemeResource OverlayCornerRadius}"
PointerPressed="_backdropPointerPressed"
Shadow="{StaticResource SharedShadow}"
Translation="0,0,32">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<!-- Top-down _searchBox -->
<RowDefinition Height="Auto" />
<!-- Top-down ParentCommandName -->
<RowDefinition Height="Auto" />
<!-- Top-down UNUSED???????? -->
<RowDefinition Height="*" />
<!-- _filteredActionsView -->
<RowDefinition Height="Auto" />
<!-- bottom-up _searchBox -->
</Grid.RowDefinitions>
<TextBox x:Name="_searchBox"
Grid.Row="0"
Margin="8,0,8,8"
Padding="18,8,8,8"
IsSpellCheckEnabled="False"
PlaceholderText="{x:Bind SearchBoxPlaceholderText, Mode=OneWay}"
Text=""
TextChanged="_filterTextChanged"
Visibility="Collapsed" />
<StackPanel Grid.Row="1"
Margin="8,0,8,8"
Orientation="Horizontal"
Visibility="{x:Bind ParentCommandName, Mode=OneWay, Converter={StaticResource ParentCommandVisibilityConverter}}">
<Button x:Name="_parentCommandBackButton"
x:Uid="ParentCommandBackButton"
VerticalAlignment="Center"
Click="_moveBackButtonClicked"
ClickMode="Press">
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="11"
Glyph="&#xE76b;" />
</Button>
<TextBlock x:Name="_parentCommandText"
Padding="16,4"
VerticalAlignment="Center"
FontStyle="Italic"
Text="{x:Bind ParentCommandName, Mode=OneWay}" />
</StackPanel>
<Border Grid.Row="1"
Margin="8,0,8,8"
Padding="16,12"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Style="{ThemeResource ParsedCommandLineBorderStyle}"
Visibility="{x:Bind ParsedCommandLineText, Mode=OneWay, Converter={StaticResource ParsedCommandLineTextVisibilityConverter}}">
<ScrollViewer MaxHeight="200"
VerticalScrollBarVisibility="Auto">
<TextBlock FontStyle="Italic"
Text="{x:Bind ParsedCommandLineText, Mode=OneWay}"
TextWrapping="Wrap" />
</ScrollViewer>
</Border>
<Border x:Name="_noMatchesText"
Grid.Row="3"
Height="36"
Margin="8,0,8,8"
Visibility="Collapsed">
<TextBlock Padding="12,0"
VerticalAlignment="Center"
FontStyle="Italic"
Text="{x:Bind NoMatchesText, Mode=OneWay}" />
</Border>
<ListView x:Name="_filteredActionsView"
Grid.Row="3"
Padding="4,-2,4,6"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AllowDrop="False"
CanReorderItems="False"
ChoosingItemContainer="_choosingItemContainer"
ContainerContentChanging="_containerContentChanging"
IsItemClickEnabled="True"
ItemClick="_listItemClicked"
ItemsSource="{x:Bind FilteredActions}"
SelectionChanged="_listItemSelectionChanged"
SelectionMode="Single" />
</Grid>
</Grid>
</UserControl>

View File

@@ -60,6 +60,9 @@
<Page Include="CommandPalette.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="SuggestionsControl.xaml">
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<!-- ========================= Headers ======================== -->
<ItemGroup>
@@ -139,6 +142,9 @@
<DependentUpon>AppLogic.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Toast.h" />
<ClInclude Include="SuggestionsControl.h">
<DependentUpon>SuggestionsControl.xaml</DependentUpon>
</ClInclude>
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
@@ -233,6 +239,9 @@
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="Toast.cpp" />
<ClCompile Include="SuggestionsControl.cpp">
<DependentUpon>SuggestionsControl.xaml</DependentUpon>
</ClCompile>
</ItemGroup>
<!-- ========================= idl Files ======================== -->
<ItemGroup>
@@ -292,6 +301,10 @@
<DependentUpon>CommandPalette.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="SuggestionsControl.idl">
<DependentUpon>SuggestionsControl.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="FilteredCommand.idl" />
<Midl Include="EmptyStringVisibilityConverter.idl" />
</ItemGroup>

View File

@@ -287,6 +287,20 @@ namespace winrt::TerminalApp::implementation
CommandPalette().SwitchToTabRequested({ this, &TerminalPage::_OnSwitchToTabRequested });
CommandPalette().PreviewAction({ this, &TerminalPage::_PreviewActionHandler });
{
const auto& sxnUi{ SuggestionsUI() };
// sxnUi.PositionManually(Windows::Foundation::Point{ 0, 0 }, Windows::Foundation::Size{ 200, 300 });
sxnUi.RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
if (SuggestionsUI().Visibility() == Visibility::Collapsed)
{
// SuggestionsPopup().IsOpen(false);
_FocusActiveControl(nullptr, nullptr);
}
});
sxnUi.DispatchCommandRequested({ this, &TerminalPage::_OnDispatchCommandRequested });
sxnUi.PreviewAction({ this, &TerminalPage::_PreviewActionHandler });
}
// Settings AllowDependentAnimations will affect whether animations are
// enabled application-wide, so we don't need to check it each time we
// want to create an animation.
@@ -1469,7 +1483,13 @@ namespace winrt::TerminalApp::implementation
return;
}
if (const auto p = CommandPalette(); p.Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
if (const auto p = CommandPalette(); p.Visibility() == Visibility::Visible &&
cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
{
p.Visibility(Visibility::Collapsed);
}
if (const auto p = SuggestionsUI(); p.Visibility() == Visibility::Visible &&
cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
{
p.Visibility(Visibility::Collapsed);
}
@@ -1660,6 +1680,8 @@ namespace winrt::TerminalApp::implementation
});
term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler });
term.MenuChanged({ get_weak(), &TerminalPage::_ControlMenuChangedHandler });
}
// Method Description:
@@ -4452,4 +4474,74 @@ namespace winrt::TerminalApp::implementation
_activated = activated;
_updateThemeColors();
}
winrt::fire_and_forget TerminalPage::_ControlMenuChangedHandler(const IInspectable /*sender*/,
const MenuChangedEventArgs args)
{
if constexpr (!Feature_ShellCompletions::IsEnabled())
{
co_return;
}
auto weakThis{ get_weak() };
co_await winrt::resume_background();
if (const auto& page{ weakThis.get() })
{
// `this` is safe to use
// Parse the json
try
{
auto commandsCollection = Command::ParsePowerShellMenuComplete(args.MenuJson(),
args.ReplacementLength());
// Open the Suggestions UI with the commands from the control
_OpenSuggestions(commandsCollection, SuggestionsMode::Menu);
}
CATCH_LOG();
}
}
winrt::fire_and_forget TerminalPage::_OpenSuggestions(IVector<Command> commandsCollection,
winrt::TerminalApp::SuggestionsMode mode)
{
if (commandsCollection == nullptr)
{
co_return;
}
if (commandsCollection.Size() == 0)
{
SuggestionsUI().Visibility(Visibility::Collapsed);
co_return;
}
auto weakThis{ get_weak() };
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
const auto& page{ weakThis.get() };
if (!page)
{
co_return;
}
// page is now keeping `this` alive to use safely
const auto& control{ _GetActiveControl() };
if (!control)
{
co_return;
}
const auto& sxnUi{ SuggestionsUI() };
sxnUi.Mode(mode);
sxnUi.SetCommands(commandsCollection);
sxnUi.Visibility(commandsCollection.Size() > 0 ? Visibility::Visible : Visibility::Collapsed);
const auto characterSize{ control.CharacterDimensions() };
// This is in control-relative space. We'll need to convert it to page-relative space.
const til::point cursorPos{ control.CursorPositionInDips() };
const auto controlTransform = control.TransformToVisual(this->Root());
const til::point controlOrigin{ til::math::rounding, controlTransform.TransformPoint(Windows::Foundation::Point{ 0, 0 }) };
const til::point realCursorPos = controlOrigin + cursorPos;
const til::size windowDimensions{ til::math::rounding, ActualWidth(), ActualHeight() };
sxnUi.Anchor(realCursorPos.to_winrt_point(), windowDimensions.to_winrt_size(), characterSize.Height);
}
}

View File

@@ -429,6 +429,8 @@ namespace winrt::TerminalApp::implementation
void _RunRestorePreviews();
void _PreviewColorScheme(const Microsoft::Terminal::Settings::Model::SetColorSchemeArgs& args);
void _PreviewAdjustOpacity(const Microsoft::Terminal::Settings::Model::AdjustOpacityArgs& args);
void _PreviewSendInput(const Microsoft::Terminal::Settings::Model::SendInputArgs& args);
winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs _lastPreviewedAction{ nullptr };
std::vector<std::function<void()>> _restorePreviewFuncs{};
@@ -460,6 +462,9 @@ namespace winrt::TerminalApp::implementation
void _updateThemeColors();
void _updateTabCloseButton(const winrt::Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem);
winrt::fire_and_forget _ControlMenuChangedHandler(const winrt::Windows::Foundation::IInspectable sender, const winrt::Microsoft::Terminal::Control::MenuChangedEventArgs args);
winrt::fire_and_forget _OpenSuggestions(Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::Command> commandsCollection, winrt::TerminalApp::SuggestionsMode mode);
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
#pragma region ActionHandlers

View File

@@ -86,11 +86,11 @@
ContentDialog a Row, I believe it's assigned Row 0 by default. So,
when the dialog gets opened, the dialog seemingly causes a giant
hole to appear in the body of the app.
Assigning all the dialogs to Row 2 (where the rest of the content
is) makes the "hole" appear in the same space as the rest of the
TabContent, fixing the issue.
Note that the actual content in a content dialog gets parented to
the PopupRoot, so it actually always appeared in the correct place, it's
just this weird hole that appeared in Row 0.
@@ -195,6 +195,13 @@
PreviewKeyDown="_KeyDownHandler"
Visibility="Collapsed" />
<local:SuggestionsControl x:Name="SuggestionsUI"
Grid.Row="2"
HorizontalAlignment="Left"
VerticalAlignment="Top"
PreviewKeyDown="_KeyDownHandler"
Visibility="Collapsed" />
<!--
A TeachingTip with IsLightDismissEnabled="True" will immediately
dismiss itself if the window is unfocused (In Xaml Islands). This is

View File

@@ -125,6 +125,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto pfnPlayMidiNote = std::bind(&ControlCore::_terminalPlayMidiNote, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
_terminal->SetPlayMidiNoteCallback(pfnPlayMidiNote);
auto pfnMenuChanged = std::bind(&ControlCore::_terminalMenuChanged, this, std::placeholders::_1, std::placeholders::_2);
_terminal->MenuChangedCallback(pfnMenuChanged);
// 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
@@ -2097,6 +2100,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
void ControlCore::_terminalMenuChanged(std::wstring_view menuJson,
int32_t replaceLength)
{
auto args = winrt::make_self<MenuChangedEventArgs>(winrt::hstring{ menuJson },
replaceLength);
_MenuChangedHandlers(*this, *args);
}
void ControlCore::ColorSelection(const Control::SelectionColor& fg, const Control::SelectionColor& bg, Core::MatchMode matchMode)
{
if (HasSelection())

View File

@@ -232,6 +232,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs);
TYPED_EVENT(UpdateSelectionMarkers, IInspectable, Control::UpdateSelectionMarkersEventArgs);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(MenuChanged, IInspectable, Control::MenuChangedEventArgs);
TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable);
// clang-format on
@@ -307,6 +309,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _terminalPlayMidiNote(const int noteNumber,
const int velocity,
const std::chrono::microseconds duration);
void _terminalMenuChanged(std::wstring_view menuJson, int32_t replaceLength);
#pragma endregion
MidiAudio _midiAudio;

View File

@@ -162,5 +162,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, UpdateSelectionMarkersEventArgs> UpdateSelectionMarkers;
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;
event Windows.Foundation.TypedEventHandler<Object, MenuChangedEventArgs> MenuChanged;
};
}

View File

@@ -14,3 +14,4 @@
#include "FoundResultsArgs.g.cpp"
#include "ShowWindowArgs.g.cpp"
#include "UpdateSelectionMarkersEventArgs.g.cpp"
#include "MenuChangedEventArgs.g.cpp"

View File

@@ -14,6 +14,7 @@
#include "FoundResultsArgs.g.h"
#include "ShowWindowArgs.g.h"
#include "UpdateSelectionMarkersEventArgs.g.h"
#include "MenuChangedEventArgs.g.h"
namespace winrt::Microsoft::Terminal::Control::implementation
{
@@ -169,4 +170,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_PROPERTY(bool, ClearMarkers, false);
};
struct MenuChangedEventArgs : public MenuChangedEventArgsT<MenuChangedEventArgs>
{
public:
MenuChangedEventArgs(const winrt::hstring menuJson, const int32_t replaceLength) :
_MenuJson(menuJson),
_ReplacementLength(replaceLength)
{
}
WINRT_PROPERTY(winrt::hstring, MenuJson, L"");
WINRT_PROPERTY(int32_t, ReplacementLength, 0);
};
}

View File

@@ -83,4 +83,10 @@ namespace Microsoft.Terminal.Control
{
Boolean ClearMarkers { get; };
}
runtimeclass MenuChangedEventArgs
{
String MenuJson { get; };
Int32 ReplacementLength { get; };
}
}

View File

@@ -437,4 +437,26 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TSFInputControl::_formatUpdatingHandler(CoreTextEditContext sender, const CoreTextFormatUpdatingEventArgs& /*args*/)
{
}
void TSFInputControl::ManuallyDisplayText(const winrt::hstring& text)
{
_focused = !text.empty();
Canvas().Visibility(text.empty() ? Visibility::Collapsed : Visibility::Visible);
_inputBuffer.clear();
_activeTextStart = 0;
_inComposition = false;
// HACK trim off leading DEL chars.
std::wstring_view view{ text.c_str() };
const auto strBegin = view.find_first_not_of(L"\x7f");
if (strBegin != std::wstring::npos)
{
view = view.substr(strBegin * 2);
}
TextBlock().Text(winrt::hstring{ view });
TextBlock().UpdateLayout();
TryRedrawCanvas();
}
}

View File

@@ -40,6 +40,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ClearBuffer();
void TryRedrawCanvas();
void ManuallyDisplayText(const winrt::hstring& text);
void Close();
// -------------------------------- WinRT Events ---------------------------------

View File

@@ -31,6 +31,9 @@ namespace Microsoft.Terminal.Control
void ClearBuffer();
void TryRedrawCanvas();
void ManuallyDisplayText(String text);
void Close();
}
}

View File

@@ -388,6 +388,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - <none>
void TermControl::SendInput(const winrt::hstring& wstr)
{
PreviewInput(L"");
_core.SendInput(wstr);
}
void TermControl::ClearBuffer(Control::ClearBufferType clearType)
@@ -3157,6 +3158,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core.OwningHwnd();
}
void TermControl::PreviewInput(const winrt::hstring& text)
{
TSFInputControl().ManuallyDisplayText(text);
}
void TermControl::AddMark(const Control::ScrollMark& mark)
{
_core.AddMark(mark);
@@ -3174,4 +3180,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_core.ColorSelection(fg, bg, matchMode);
}
// Returns the text cursor's position relative to our origin, in DIPs.
Microsoft::Terminal::Core::Point TermControl::CursorPositionInDips()
{
const til::point cursorPos{ _core.CursorPosition() };
const til::size fontSize{ til::math::flooring, CharacterDimensions() };
// Convert text buffer cursor position to client coordinate position
// within the window. This point is in _pixels_
const til::point clientCursorPos{ cursorPos * fontSize };
// Get scale factor for view
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
const til::point clientCursorInDips{ til::math::flooring, clientCursorPos.x / scaleFactor, clientCursorPos.y / scaleFactor };
auto padding{ GetPadding() };
til::point relativeToOrigin{ til::math::flooring,
clientCursorInDips.x + padding.Left,
clientCursorInDips.y + padding.Top };
return relativeToOrigin.to_core_point();
}
}

View File

@@ -47,6 +47,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Windows::Foundation::Size MinimumSize();
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension);
void PreviewInput(const winrt::hstring& text);
Microsoft::Terminal::Core::Point CursorPositionInDips();
void WindowVisibilityChanged(const bool showOrHide);
void ColorSelection(Control::SelectionColor fg, Control::SelectionColor bg, Core::MatchMode matchMode);
@@ -150,6 +154,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
PROJECTED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs, _core, ShowWindowChanged);
PROJECTED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable, _core, CloseTerminalRequested);
PROJECTED_FORWARDED_TYPED_EVENT(MenuChanged , IInspectable, Control::MenuChangedEventArgs, _core, MenuChanged);
PROJECTED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs, _interactivity, PasteFromClipboard);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);

View File

@@ -44,6 +44,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ReadOnlyChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FocusFollowMouseRequested;
event Windows.Foundation.TypedEventHandler<Object, MenuChangedEventArgs> MenuChanged;
event Windows.Foundation.TypedEventHandler<TermControl, Windows.UI.Xaml.RoutedEventArgs> Initialized;
// This is an event handler forwarder for the underlying connection.
@@ -97,8 +98,13 @@ namespace Microsoft.Terminal.Control
// opacity set by the settings should call this instead.
Double BackgroundOpacity { get; };
void PreviewInput(String text);
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
void ColorSelection(SelectionColor fg, SelectionColor bg, Microsoft.Terminal.Core.MatchMode matchMode);
Microsoft.Terminal.Core.Point CursorPositionInDips { get; };
}
}

View File

@@ -1411,6 +1411,11 @@ const size_t Microsoft::Terminal::Core::Terminal::GetTaskbarProgress() const noe
return _taskbarProgress;
}
void Microsoft::Terminal::Core::Terminal::MenuChangedCallback(std::function<void(std::wstring_view, int32_t)> pfn) noexcept
{
_pfnMenuChanged.swap(pfn);
}
Scheme Terminal::GetColorScheme() const
{
Scheme s;

View File

@@ -140,6 +140,9 @@ public:
bool IsConsolePty() const noexcept override;
bool IsVtInputEnabled() const noexcept override;
void NotifyAccessibilityChange(const til::rect& changedRect) noexcept override;
void InvokeMenu(std::wstring_view menuJson, int32_t replaceLength) override;
#pragma endregion
void ClearMark();
@@ -213,6 +216,7 @@ public:
void TaskbarProgressChangedCallback(std::function<void()> pfn) noexcept;
void SetShowWindowCallback(std::function<void(bool)> pfn) noexcept;
void SetPlayMidiNoteCallback(std::function<void(const int, const int, const std::chrono::microseconds)> pfn) noexcept;
void MenuChangedCallback(std::function<void(std::wstring_view, int32_t)> pfn) noexcept;
void SetCursorOn(const bool isOn);
bool IsCursorBlinkingAllowed() const noexcept;
@@ -310,6 +314,7 @@ private:
std::function<void()> _pfnTaskbarProgressChanged;
std::function<void(bool)> _pfnShowWindowChanged;
std::function<void(const int, const int, const std::chrono::microseconds)> _pfnPlayMidiNote;
std::function<void(std::wstring_view, int32_t)> _pfnMenuChanged;
RenderSettings _renderSettings;
std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;

View File

@@ -467,3 +467,10 @@ void Terminal::NotifyAccessibilityChange(const til::rect& /*changedRect*/) noexc
{
// This is only needed in conhost. Terminal handles accessibility in another way.
}
void Terminal::InvokeMenu(std::wstring_view menuJson, int32_t replaceLength)
{
if (_pfnMenuChanged)
{
_pfnMenuChanged(menuJson, replaceLength);
}
}

View File

@@ -750,6 +750,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
BASIC_FACTORY(RenameTabArgs);
BASIC_FACTORY(SwapPaneArgs);
BASIC_FACTORY(SplitPaneArgs);
BASIC_FACTORY(SendInputArgs);
BASIC_FACTORY(SetFocusModeArgs);
BASIC_FACTORY(SetFullScreenArgs);
BASIC_FACTORY(SetMaximizedArgs);

View File

@@ -192,6 +192,8 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass SendInputArgs : IActionArgs
{
SendInputArgs(String input);
String Input { get; };
};

View File

@@ -635,4 +635,98 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return newCommands;
}
winrt::Windows::Foundation::Collections::IVector<Model::Command> Command::ParsePowerShellMenuComplete(winrt::hstring json, int32_t replaceLength)
{
if (json.empty())
{
return nullptr;
}
auto data = winrt::to_string(json);
std::string errs;
static std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
Json::Value root;
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
{
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
}
auto result = winrt::single_threaded_vector<Model::Command>();
auto backspaces = std::wstring(::base::saturated_cast<size_t>(replaceLength), L'\x7f');
const auto parseElement = [&](const auto& element) {
winrt::hstring completionText;
winrt::hstring listText;
JsonUtils::GetValueForKey(element, "CompletionText", completionText);
JsonUtils::GetValueForKey(element, "ListItemText", listText);
Model::SendInputArgs args{ winrt::hstring{ fmt::format(L"{}{}", backspaces, completionText.c_str()) } };
Model::ActionAndArgs actionAndArgs{ ShortcutAction::SendInput, args };
auto c = winrt::make_self<Command>();
c->_name = listText;
c->_ActionAndArgs = actionAndArgs;
// Try to assign a sensible icon based on the result type. These are
// roughly chosen to align with the icons in
// https://github.com/PowerShell/PowerShellEditorServices/pull/1738
// as best as possible.
if (const auto resultType{ JsonUtils::GetValueForKey<int>(element, "ResultType") })
{
// PowerShell completion result -> Segoe Fluent icon value & name
switch (resultType)
{
case 1: // History -> 0xe81c History
c->_iconPath = L"\ue81c";
break;
case 2: // Command -> 0xecaa AppIconDefault
c->_iconPath = L"\uecaa";
break;
case 3: // ProviderItem -> 0xe8e4 AlignLeft
c->_iconPath = L"\ue8e4";
break;
case 4: // ProviderContainer -> 0xe838 FolderOpen
c->_iconPath = L"\ue838";
break;
case 5: // Property -> 0xe7c1 Flag
c->_iconPath = L"\ue7c1";
break;
case 6: // Method -> 0xecaa AppIconDefault
c->_iconPath = L"\uecaa";
break;
case 7: // ParameterName -> 0xe7c1 Flag
c->_iconPath = L"\ue7c1";
break;
case 8: // ParameterValue -> 0xf000 KnowledgeArticle
c->_iconPath = L"\uf000";
break;
case 10: // Namespace -> 0xe943 Code
c->_iconPath = L"\ue943";
break;
case 13: // DynamicKeyword -> 0xe945 LightningBolt
c->_iconPath = L"\ue945";
break;
}
}
result.Append(*c);
};
if (root.isArray())
{
// If we got a whole array of suggestions, parse each one.
for (const auto& element : root)
{
parseElement(element);
}
}
else if (root.isObject())
{
// If we instead only got a single element back, just parse the root element.
parseElement(root);
}
return result;
}
}

View File

@@ -67,6 +67,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
hstring IconPath() const noexcept;
void IconPath(const hstring& val);
static Windows::Foundation::Collections::IVector<Model::Command> ParsePowerShellMenuComplete(winrt::hstring json, int32_t replaceLength);
winrt::Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker propertyChangedRevoker;
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);

View File

@@ -47,5 +47,8 @@ namespace Microsoft.Terminal.Settings.Model
Windows.Foundation.Collections.IVectorView<Profile> profiles,
Windows.Foundation.Collections.IVectorView<ColorScheme> schemes,
Windows.Foundation.Collections.IVector<SettingsLoadWarnings> warnings);
static IVector<Command> ParsePowerShellMenuComplete(String json, Int32 replaceLength);
}
}

View File

@@ -155,4 +155,14 @@
</alwaysEnabledBrandingTokens>
</feature>
<feature>
<name>Feature_ShellCompletions</name>
<description>An experimental escape sequence for client applications to request the Terminal display a list of suggestions.</description>
<id>3121</id>
<stage>AlwaysDisabled</stage>
<alwaysEnabledBrandingTokens>
<brandingToken>Dev</brandingToken>
</alwaysEnabledBrandingTokens>
</feature>
</featureStaging>

View File

@@ -493,3 +493,7 @@ void ConhostInternalGetSet::MarkCommandFinish(std::optional<unsigned int> /*erro
{
// Not implemented for conhost.
}
void ConhostInternalGetSet::InvokeMenu(std::wstring_view /*menuJson*/, int32_t /*replaceLength*/)
{
// Not implemented for conhost.
}

View File

@@ -80,6 +80,8 @@ public:
void MarkOutputStart() override;
void MarkCommandFinish(std::optional<unsigned int> error) override;
void InvokeMenu(std::wstring_view menuJson, int32_t replaceLength) override;
private:
Microsoft::Console::IIoProvider& _io;
bool _bracketedPasteMode{ false };

View File

@@ -131,6 +131,8 @@ public:
virtual bool DoFinalTermAction(const std::wstring_view string) = 0;
virtual bool DoXtermJsAction(const std::wstring_view string) = 0;
virtual StringHandler DownloadDRCS(const VTInt fontNumber,
const VTParameter startChar,
const DispatchTypes::DrcsEraseControl eraseControl,

View File

@@ -82,5 +82,7 @@ namespace Microsoft::Console::VirtualTerminal
virtual void MarkCommandStart() = 0;
virtual void MarkOutputStart() = 0;
virtual void MarkCommandFinish(std::optional<unsigned int> error) = 0;
virtual void InvokeMenu(std::wstring_view menuJson, int32_t replaceLength) = 0;
};
}

View File

@@ -3132,6 +3132,73 @@ bool AdaptDispatch::DoFinalTermAction(const std::wstring_view string)
// modify the state of that mark as we go.
return false;
}
// Method Description:
// - Performs a XtermJs action
// - Ascribes to the ITermDispatch interface
// - Currently, the actions we support are:
// * Completions: An experimental protocol for passing shell completion
// information from the shell to the terminal. This sequence is still under
// active development, and subject to change.
// - Not actually used in conhost
// Arguments:
// - string: contains the parameters that define which action we do
// Return Value:
// - false in conhost, true for the SetMark action, otherwise false.
bool AdaptDispatch::DoXtermJsAction(const std::wstring_view string)
{
// This is not implemented in conhost.
if (_api.IsConsolePty())
{
// Flush the frame manually, to make sure marks end up on the right line, like the alt buffer sequence.
_renderer.TriggerFlush(false);
return false;
}
if constexpr (!Feature_ShellCompletions::IsEnabled())
{
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"Completions")
{
// The structure of the message is as follows:
// `e]633;
// 0: Completions;
// 1: $($completions.ReplacementIndex);
// 2: $($completions.ReplacementLength);
// 3: $($cursorIndex);
// 4: $completions.CompletionMatches | ConvertTo-Json
unsigned int replacementIndex = 0;
unsigned int replacementLength = 0;
unsigned int cursorIndex = 0;
bool succeeded = (parts.size() >= 2) &&
(Utils::StringToUint(til::at(parts, 1), replacementIndex));
succeeded &= (parts.size() >= 3) &&
(Utils::StringToUint(til::at(parts, 2), replacementLength));
succeeded &= (parts.size() >= 4) &&
(Utils::StringToUint(til::at(parts, 3), cursorIndex));
// VsCode is using cursorIndex and replacementIndex, but we aren't currently.
if (succeeded)
{
_api.InvokeMenu(parts.size() < 5 ? L"" : til::at(parts, 4),
static_cast<int32_t>(replacementLength));
}
return true;
}
return false;
}
// Method Description:
// - DECDLD - Downloads one or more characters of a dynamically redefinable

View File

@@ -132,6 +132,8 @@ namespace Microsoft::Console::VirtualTerminal
bool DoFinalTermAction(const std::wstring_view string) override;
bool DoXtermJsAction(const std::wstring_view string) override;
StringHandler DownloadDRCS(const VTInt fontNumber,
const VTParameter startChar,
const DispatchTypes::DrcsEraseControl eraseControl,

View File

@@ -922,6 +922,11 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
success = _dispatch->DoFinalTermAction(string);
break;
}
case OscActionCodes::XtermJsAction:
{
success = _dispatch->DoXtermJsAction(string);
break;
}
default:
// If no functions to call, overall dispatch was a failure.
success = false;

View File

@@ -208,6 +208,7 @@ namespace Microsoft::Console::VirtualTerminal
ResetBackgroundColor = 111, // Not implemented
ResetCursorColor = 112,
FinalTermAction = 133,
XtermJsAction = 633,
ITerm2Action = 1337,
};

View File

@@ -0,0 +1,5 @@
## Narrator Buddy
This is a sample of how we might implement Narrator Buddy. This was an internal tool for taking what narrator would read aloud, and logging it to the console.
Currently this builds as a scratch project in the Terminal solution. It's provided as a reference implementation for how a real narrator buddy might be implemented in the future.

View File

@@ -9,6 +9,7 @@
<ConfigurationType>Application</ConfigurationType>
</PropertyGroup>
<Import Project="..\..\common.build.pre.props" />
<Import Project="..\..\common.nugetversions.props" />
<ItemGroup>
<ClCompile Include="main.cpp" />
</ItemGroup>
@@ -32,4 +33,5 @@
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="..\..\common.build.post.props" />
<Import Project="..\..\common.build.tests.props" />
<Import Project="..\..\common.nugetversions.targets" />
</Project>

View File

@@ -2,9 +2,162 @@
// Licensed under the MIT license.
#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <array>
#include <thread>
#include <sstream>
// This wmain exists for help in writing scratch programs while debugging.
int __cdecl wmain(int /*argc*/, WCHAR* /*argv[]*/)
#include <wrl.h>
namespace wrl = Microsoft::WRL;
#include <wil\result.h>
#include <wil\resource.h>
#include <wil\com.h>
#include <evntcons.h>
#include <evntrace.h>
namespace
{
wil::unique_event g_stopEvent{ wil::EventOptions::None };
} // namespace anonymous
void WINAPI ProcessEtwEvent(_In_ PEVENT_RECORD rawEvent)
{
// This is the task id from srh.man (with the same name).
constexpr auto InitiateSpeaking = 5;
auto data = rawEvent->UserData;
auto dataLen = rawEvent->UserDataLength;
auto processId = rawEvent->EventHeader.ProcessId;
processId;
auto task = rawEvent->EventHeader.EventDescriptor.Task;
if (task == InitiateSpeaking)
{
// The payload first has an int32 representing the channel we're writing to. This is basically always writing to the
// default channel, so just skip over those bytes... the rest is the string payload we want to speak,
// as a null terminated string.
if (dataLen <= 4)
{
return;
}
const auto stringPayloadSize = (dataLen - 4) / sizeof(wchar_t);
// We don't need the null terminator, because wstring intends to handle that on its own.
const auto stringLen = stringPayloadSize - 1;
const auto payload = std::wstring(reinterpret_cast<wchar_t*>(static_cast<char*>(data) + 4), stringLen);
// wprintf(L"[Narrator pid=%d]: %s\n", processId, payload.c_str());
wprintf(L"%s\n", payload.c_str());
fflush(stdout);
if (payload == L"Exiting Narrator")
{
g_stopEvent.SetEvent();
}
}
}
int __cdecl wmain(int argc, wchar_t* argv[])
{
wprintf(L"Welcome to Narrator Buddy. Start up Narrator to start logging narrator output.\n");
wprintf(L" Press ^C to exit\n");
wprintf(L" If you start Narrator and don't see any output, make sure there's no leaked ETW traces with:\n\n");
wprintf(L" logman query -ets\n\n");
const bool runForever = (argc == 2) &&
std::wstring{ argv[1] } == L"-forever";
GUID sessionGuid;
FAIL_FAST_IF_FAILED(::CoCreateGuid(&sessionGuid));
std::array<wchar_t, 64> traceSessionName{};
FAIL_FAST_IF_FAILED(StringCchPrintf(traceSessionName.data(), static_cast<DWORD>(traceSessionName.size()), L"NarratorTraceSession_%d", ::GetCurrentProcessId()));
unsigned int traceSessionNameBytes = static_cast<unsigned int>((wcslen(traceSessionName.data()) + 1) * sizeof(wchar_t));
// Now, to get tracing. Most settings below are defaults from MSDN, except where noted.
// First, set up a session (StartTrace) - which requires a PROPERTIES struct. This has to have
// the session name after it in the same block of memory...
const unsigned int propertiesByteSize = sizeof(EVENT_TRACE_PROPERTIES) + traceSessionNameBytes;
std::vector<char> eventTracePropertiesBuffer(propertiesByteSize);
auto properties = reinterpret_cast<EVENT_TRACE_PROPERTIES*>(eventTracePropertiesBuffer.data());
// Set up properties struct for a real-time session...
properties->Wnode.BufferSize = propertiesByteSize;
properties->Wnode.Guid = sessionGuid;
properties->Wnode.ClientContext = 1;
properties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
properties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE | EVENT_TRACE_USE_PAGED_MEMORY;
properties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
properties->FlushTimer = 1;
// Finally, copy the session name...
memcpy(properties + 1, traceSessionName.data(), traceSessionNameBytes);
std::thread traceThread;
auto joinTraceThread = wil::scope_exit([&]() {
if (traceThread.joinable())
{
traceThread.join();
}
});
TRACEHANDLE session{};
const auto rc = ::StartTrace(&session, traceSessionName.data(), properties);
FAIL_FAST_IF(rc != ERROR_SUCCESS);
auto stopTrace = wil::scope_exit([&]() {
EVENT_TRACE_PROPERTIES properties{};
properties.Wnode.BufferSize = sizeof(properties);
properties.Wnode.Guid = sessionGuid;
properties.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
::ControlTrace(session, nullptr, &properties, EVENT_TRACE_CONTROL_STOP);
});
constexpr GUID narratorProviderGuid = { 0x835b79e2, 0xe76a, 0x44c4, 0x98, 0x85, 0x26, 0xad, 0x12, 0x2d, 0x3b, 0x4d };
auto hr = ::EnableTrace(TRUE /* enable */, 0 /* enableFlag */, TRACE_LEVEL_VERBOSE, &narratorProviderGuid, session);
if (hr == ERROR_NO_SYSTEM_RESOURCES)
{
wprintf(L"Looks like you ran out of ETW trace slots. You'll need to free one up\n");
return hr;
}
FAIL_FAST_IF(ERROR_SUCCESS != hr);
auto disableTrace = wil::scope_exit([&]() {
::EnableTrace(FALSE /* enable */,
0 /* enableFlag */,
TRACE_LEVEL_VERBOSE,
&narratorProviderGuid,
session);
});
// Finally, start listening (OpenTrace/ProcessTrace/CloseTrace)...
EVENT_TRACE_LOGFILE trace{};
trace.LoggerName = traceSessionName.data();
trace.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_REAL_TIME;
trace.EventRecordCallback = ProcessEtwEvent;
using unique_tracehandle = wil::unique_any<TRACEHANDLE, decltype(::CloseTrace), ::CloseTrace>;
unique_tracehandle traceHandle{ ::OpenTrace(&trace) };
// Since the actual call to ProcessTrace blocks while it's working,
// we spin up a separate thread to do that.
traceThread = std::thread([traceHandle(std::move(traceHandle))]() mutable {
::ProcessTrace(traceHandle.addressof(), 1 /* handleCount */, nullptr /* startTime */, nullptr /* endTime */);
});
if (runForever)
{
::Sleep(INFINITE);
}
else
{
g_stopEvent.wait(INFINITE);
}
return 0;
}