From 59590fc6657ece06ef7d2006f5fd5907b1ba1828 Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Fri, 30 May 2025 10:06:39 -0700 Subject: [PATCH 01/74] fix: don't render bidi isolates LRI, RLI, FSI, PDI (#18942) Skips rendering LRI, RLI, FSI, and PDI "glyphs" in the terminal. Does not implement BIDI/RTL; that is out of scope, see #538. This is just a hotfix to stop spamming the console with undesired character printouts. Once BIDI support is implemented, this change will (maybe?) no longer be necessary. Fixes #16574. --- src/renderer/atlas/AtlasEngine.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 856b46f0fb..d2e9f8bf26 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -500,8 +500,13 @@ try { for (const auto& cluster : clusters) { - for (const auto& ch : cluster.GetText()) + for (auto ch : cluster.GetText()) { + // Render Unicode directional isolate characters (U+2066..U+2069) as zero-width spaces. + if (ch >= L'\u2066' && ch <= L'\u2069') + { + ch = L'\u200B'; + } _api.bufferLine.emplace_back(ch); _api.bufferLineColumn.emplace_back(columnEnd); } From 36162ae6b110622bfe29770eb2eca5b257e44360 Mon Sep 17 00:00:00 2001 From: Windows Console Service Bot <14666831+consvc@users.noreply.github.com> Date: Fri, 30 May 2025 22:39:32 -0500 Subject: [PATCH 02/74] Localization Updates - main - 05/29/2025 03:05:25 (#18980) --- .../Resources/de-DE/Resources.resw | 47 ++++++++++++ .../Resources/es-ES/Resources.resw | 47 ++++++++++++ .../Resources/fr-FR/Resources.resw | 47 ++++++++++++ .../Resources/it-IT/Resources.resw | 47 ++++++++++++ .../Resources/ja-JP/Resources.resw | 47 ++++++++++++ .../Resources/ko-KR/Resources.resw | 47 ++++++++++++ .../Resources/pt-BR/Resources.resw | 47 ++++++++++++ .../Resources/qps-ploc/Resources.resw | 47 ++++++++++++ .../Resources/qps-ploca/Resources.resw | 47 ++++++++++++ .../Resources/qps-plocm/Resources.resw | 47 ++++++++++++ .../Resources/ru-RU/Resources.resw | 47 ++++++++++++ .../Resources/zh-CN/Resources.resw | 47 ++++++++++++ .../Resources/zh-TW/Resources.resw | 47 ++++++++++++ .../Resources/de-DE/Resources.resw | 74 +++++++++++------- .../Resources/es-ES/Resources.resw | 74 +++++++++++------- .../Resources/fr-FR/Resources.resw | 74 +++++++++++------- .../Resources/it-IT/Resources.resw | 74 +++++++++++------- .../Resources/ja-JP/Resources.resw | 74 +++++++++++------- .../Resources/ko-KR/Resources.resw | 74 +++++++++++------- .../Resources/pt-BR/Resources.resw | 74 +++++++++++------- .../Resources/qps-ploc/Resources.resw | 76 ++++++++++++------- .../Resources/qps-ploca/Resources.resw | 76 ++++++++++++------- .../Resources/qps-plocm/Resources.resw | 76 ++++++++++++------- .../Resources/ru-RU/Resources.resw | 74 +++++++++++------- .../Resources/zh-CN/Resources.resw | 74 +++++++++++------- .../Resources/zh-TW/Resources.resw | 74 +++++++++++------- 26 files changed, 1225 insertions(+), 354 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw index 4f291d928b..968d594762 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw @@ -684,6 +684,10 @@ Aktionen Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Erweiterungen + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Hintergrunddeckkraft Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Diese Option wird durch eine Unternehmensrichtlinie verwaltet und kann hier nicht geändert werden. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Aktive Erweiterungen + + + Geänderte Profile + + + Hinzugefügte Profile + + + Hinzugefügte Farbschemas + + + Weitere Informationen zu Erweiterungen + + + Zum Profil navigieren + + + Zum Profil navigieren + + + Zum Farbschema navigieren + + + Zum Farbschema navigieren + + + Aktueller Benutzer + Label for the installation scope of an extension. + + + Alle Benutzer + Label for the installation scope of an extension + + + Bereich + Header for the installation scope of the extension + + + NEU + Text is used on an info badge for new navigation items. Must be all caps. + Weitere Informationen zu regulären Ausdrücken diff --git a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw index 35a70e5a4b..f3204f013f 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw @@ -684,6 +684,10 @@ Acciones Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Extensiones + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Opacidad del fondo Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Esta opción está administrada por una directiva de empresa y no se puede cambiar aquí. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Extensiones activas + + + Perfiles modificados + + + Perfiles agregados + + + Combinaciones de colores agregadas + + + Más información acerca de las extensiones + + + Navegar al perfil + + + Navegar al perfil + + + Ir a la combinación de colores + + + Ir a la combinación de colores + + + Usuario actual + Label for the installation scope of an extension. + + + Todos los usuarios + Label for the installation scope of an extension + + + Ámbito + Header for the installation scope of the extension + + + NUEVO + Text is used on an info badge for new navigation items. Must be all caps. + Más información sobre las expresiones regulares diff --git a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw index 3c5f48dc49..ac90f141ab 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw @@ -684,6 +684,10 @@ Actions Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Extensions + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Opacité de l’arrière-plan Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Cette option est gérée par la stratégie d’entreprise et ne peut pas être modifiée ici. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Extensions actives + + + Profils modifiés + + + Profils ajoutés + + + Ajout de modèles de couleurs + + + En savoir plus sur les extensions + + + Accéder au profil + + + Accéder au profil + + + Accéder au modèle de couleurs + + + Accéder au modèle de couleurs + + + Utilisateur actuel + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + Étendue + Header for the installation scope of the extension + + + NOUVEAU + Text is used on an info badge for new navigation items. Must be all caps. + En savoir plus sur les expressions régulières diff --git a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw index 7eb7211d70..b6aebaa918 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw @@ -684,6 +684,10 @@ Azioni Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Estensioni + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Opacità sfondo Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Questa opzione è gestita da criteri aziendali e non può essere modificata qui. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Estensioni attive + + + Profili modificati + + + Profili aggiunti + + + Combinazioni colori aggiunte + + + Altre informazioni sulle estensioni + + + Passa al profilo + + + Passa al profilo + + + Passa alla combinazione colori + + + Passa alla combinazione colori + + + Utente corrente + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + Ambito + Header for the installation scope of the extension + + + NUOVO + Text is used on an info badge for new navigation items. Must be all caps. + Scopri di più sulle espressioni regolari diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw index 97a17fa9d7..10514c94e6 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw @@ -684,6 +684,10 @@ 操作 Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + 拡張機能 + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + 背景の不透明度 Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ このオプションはエンタープライズ ポリシーによって管理されているため、ここで変更することはできません。 This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + アクティブな拡張機能 + + + 変更されたプロファイル + + + 追加されたプロファイル + + + 追加された配色 + + + 拡張機能に関する詳細情報 + + + プロファイルに移動 + + + プロファイルに移動 + + + 配色に移動 + + + 配色に移動 + + + 現在のユーザー + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + スコープ + Header for the installation scope of the extension + + + 新規 + Text is used on an info badge for new navigation items. Must be all caps. + 正規表現について詳細を表示する diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw index 6061445a4c..711a533182 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw @@ -684,6 +684,10 @@ 작업 Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + 확장 + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + 배경 불투명도 Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ 이 옵션은 엔터프라이즈 정책에 의해 관리되므로 여기에서 변경할 수 없습니다. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + 활성 확장 + + + 수정된 프로필 + + + 추가된 프로필 + + + 추가된 색 구성표 + + + 확장에 대한 자세한 정보 + + + 프로필로 이동 + + + 프로필로 이동 + + + 색 구성표로 이동 + + + 색 구성표로 이동 + + + 현재 사용자 + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + 범위 + Header for the installation scope of the extension + + + 새 기능 + Text is used on an info badge for new navigation items. Must be all caps. + 정규식에 대한 자세한 정보 diff --git a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw index 522b910a92..faf76cce95 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw @@ -684,6 +684,10 @@ Ações Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Extensões + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Opacidade da tela de fundo Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Esta opção é gerenciada pela política corporativa e não pode ser alterada aqui. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Extensões Ativas + + + Perfis Modificados + + + Perfis Adicionados + + + Esquemas de Cores Adicionados + + + Saiba mais sobre extensões + + + Navegar para o perfil + + + Navegar para o perfil + + + Navegar para o esquema de cores + + + Navegar para o esquema de cores + + + Usuário Atual + Label for the installation scope of an extension. + + + Todos os Usuários + Label for the installation scope of an extension + + + Escopo + Header for the installation scope of the extension + + + NOVO + Text is used on an info badge for new navigation items. Must be all caps. + Saiba mais sobre expressões regulares diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw index 970ddb22f5..800e7b85cd 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw @@ -684,6 +684,10 @@ Ăςтιòñš !! Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Ěжτëⁿśïōʼnş !!! + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + βά¢ĸĝŕõúήđ ǿрãĉĩŧÿ !!! !! Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2344,6 +2348,49 @@ Ţĥîŝ όρтįοñ íş мαпªģéð ъý ĕŋτéřрŗĭšз рôľĩсу алð çąňηōт ье ċħâήğèď ћēяē. !!! !!! !!! !!! !!! !!! !!! This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Ãčŧινε ∑хťėŋѕιόйš !!! !! + + + Мőδіƒí℮δ Ρѓòƒίŀέŝ !!! !! + + + Ǻđδєδ Ρѓбƒïℓέŝ !!! ! + + + Áδďéđ Čǿļöŗ Šсħèm℮ŝ !!! !!! + + + Ļ℮ªѓń môг℮ åъоûţ ē×ţепѕîõπѕ !!! !!! !! + + + Ŋãνīġáťё ţσ ргŏƒĭℓз !!! !!! + + + Ŋãνιġǻťě ťǿ рґøƒĩŀę !!! !!! + + + Ňąνїģǻţė ŧŏ ċőľόŗ şċĥêме !!! !!! ! + + + Ñąνīġåŧє ťò ςŏŀόŕ ѕсђεмė !!! !!! ! + + + Ćύřřëńť Ŭѕέѓ !!! + Label for the installation scope of an extension. + + + Ăļł Ůşεŕś !!! + Label for the installation scope of an extension + + + Şĉòрэ ! + Header for the installation scope of the extension + + + ∏ÉŴ + Text is used on an info badge for new navigation items. Must be all caps. + Ľэаŕη mθřє άъőùţ řέĝцŀªř ℮×рřеѕšïоπş !!! !!! !!! ! diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw index 970ddb22f5..800e7b85cd 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw @@ -684,6 +684,10 @@ Ăςтιòñš !! Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Ěжτëⁿśïōʼnş !!! + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + βά¢ĸĝŕõúήđ ǿрãĉĩŧÿ !!! !! Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2344,6 +2348,49 @@ Ţĥîŝ όρтįοñ íş мαпªģéð ъý ĕŋτéřрŗĭšз рôľĩсу алð çąňηōт ье ċħâήğèď ћēяē. !!! !!! !!! !!! !!! !!! !!! This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Ãčŧινε ∑хťėŋѕιόйš !!! !! + + + Мőδіƒí℮δ Ρѓòƒίŀέŝ !!! !! + + + Ǻđδєδ Ρѓбƒïℓέŝ !!! ! + + + Áδďéđ Čǿļöŗ Šсħèm℮ŝ !!! !!! + + + Ļ℮ªѓń môг℮ åъоûţ ē×ţепѕîõπѕ !!! !!! !! + + + Ŋãνīġáťё ţσ ргŏƒĭℓз !!! !!! + + + Ŋãνιġǻťě ťǿ рґøƒĩŀę !!! !!! + + + Ňąνїģǻţė ŧŏ ċőľόŗ şċĥêме !!! !!! ! + + + Ñąνīġåŧє ťò ςŏŀόŕ ѕсђεмė !!! !!! ! + + + Ćύřřëńť Ŭѕέѓ !!! + Label for the installation scope of an extension. + + + Ăļł Ůşεŕś !!! + Label for the installation scope of an extension + + + Şĉòрэ ! + Header for the installation scope of the extension + + + ∏ÉŴ + Text is used on an info badge for new navigation items. Must be all caps. + Ľэаŕη mθřє άъőùţ řέĝцŀªř ℮×рřеѕšïоπş !!! !!! !!! ! diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw index 970ddb22f5..800e7b85cd 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw @@ -684,6 +684,10 @@ Ăςтιòñš !! Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Ěжτëⁿśïōʼnş !!! + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + βά¢ĸĝŕõúήđ ǿрãĉĩŧÿ !!! !! Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2344,6 +2348,49 @@ Ţĥîŝ όρтįοñ íş мαпªģéð ъý ĕŋτéřрŗĭšз рôľĩсу алð çąňηōт ье ċħâήğèď ћēяē. !!! !!! !!! !!! !!! !!! !!! This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Ãčŧινε ∑хťėŋѕιόйš !!! !! + + + Мőδіƒí℮δ Ρѓòƒίŀέŝ !!! !! + + + Ǻđδєδ Ρѓбƒïℓέŝ !!! ! + + + Áδďéđ Čǿļöŗ Šсħèm℮ŝ !!! !!! + + + Ļ℮ªѓń môг℮ åъоûţ ē×ţепѕîõπѕ !!! !!! !! + + + Ŋãνīġáťё ţσ ргŏƒĭℓз !!! !!! + + + Ŋãνιġǻťě ťǿ рґøƒĩŀę !!! !!! + + + Ňąνїģǻţė ŧŏ ċőľόŗ şċĥêме !!! !!! ! + + + Ñąνīġåŧє ťò ςŏŀόŕ ѕсђεмė !!! !!! ! + + + Ćύřřëńť Ŭѕέѓ !!! + Label for the installation scope of an extension. + + + Ăļł Ůşεŕś !!! + Label for the installation scope of an extension + + + Şĉòрэ ! + Header for the installation scope of the extension + + + ∏ÉŴ + Text is used on an info badge for new navigation items. Must be all caps. + Ľэаŕη mθřє άъőùţ řέĝцŀªř ℮×рřеѕšïоπş !!! !!! !!! ! diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw index ed6f9e8e29..28113389e5 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw @@ -684,6 +684,10 @@ Действия Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + Расширения + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + Прозрачность фона Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ Этот параметр управляется политикой предприятия и не может быть изменен здесь. This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + Активные расширения + + + Измененные профили + + + Добавленные профили + + + Добавленные цветовые схемы + + + Узнайте больше о расширениях + + + Перейти к профилю + + + Перейти к профилю + + + Перейти к цветовой схеме + + + Перейти к цветовой схеме + + + Текущий пользователь + Label for the installation scope of an extension. + + + Все пользователи + Label for the installation scope of an extension + + + Объем + Header for the installation scope of the extension + + + НОВОЕ + Text is used on an info badge for new navigation items. Must be all caps. + Подробнее о регулярных выражениях diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw index 096fdca93a..7f46d38030 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw @@ -684,6 +684,10 @@ 操作 Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + 扩展 + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + 背景不透明度 Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ 此选项由企业策略管理,无法在此处更改。 This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + 活动扩展 + + + 已修改配置文件 + + + 已添加配置文件 + + + 已添加配色方案 + + + 了解有关扩展的详细信息 + + + 导航到个人资料 + + + 导航到个人资料 + + + 导航到配色方案 + + + 导航到配色方案 + + + 当前用户 + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + 范围 + Header for the installation scope of the extension + + + 新建 + Text is used on an info badge for new navigation items. Must be all caps. + 了解有关正则表达式的更多信息 diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw index 4666ecd933..ed1ee7b18e 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw @@ -684,6 +684,10 @@ 動作 Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + + 延伸模組 + Header for the "extensions" menu item. This navigates to a page that lets you see and modify extensions for the app. + 背景不透明度 Name for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -2340,6 +2344,49 @@ 此選項由企業原則管理,無法在此變更。 This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting. + + 作用中擴充功能 + + + 已修改的設定檔 + + + 已新增設定檔 + + + 已新增色彩配置 + + + 深入了解延伸項目 + + + 瀏覽至設定檔 + + + 瀏覽至設定檔 + + + 瀏覽至色彩配置 + + + 瀏覽至色彩配置 + + + 目前使用者 + Label for the installation scope of an extension. + + + All Users + Label for the installation scope of an extension + + + 範圍 + Header for the installation scope of the extension + + + 新增 + Text is used on an info badge for new navigation items. Must be all caps. + 深入了解規則運算式 diff --git a/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw index 85c516cfee..feea4df3ac 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Aktuelles Arbeitsverzeichnis öffnen + + WSL-Verteilungsprofilgenerator + The display name of a dynamic profile generator for WSL distros + + + PowerShell-Profilgenerator + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell-Profilgenerator + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio-Profilgenerator + The display name of a dynamic profile generator for Visual Studio + + + SSH-Hostprofilgenerator + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw index ec1e158743..479bad532c 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Abrir directorio de trabajo actual + + Generador de perfiles de distribución WSL + The display name of a dynamic profile generator for WSL distros + + + Generador de perfiles de PowerShell + The display name of a dynamic profile generator for PowerShell + + + Generador de perfiles de Azure Cloud Shell + The display name of a dynamic profile generator for Azure Cloud Shell + + + Generador de perfiles de Visual Studio + The display name of a dynamic profile generator for Visual Studio + + + Generador de perfiles de host SSH + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw index 6cf1eba684..19dade5fce 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Ouvrir le répertoire de travail actif + + Générateur de profil de distribution WSL + The display name of a dynamic profile generator for WSL distros + + + Générateur de profil PowerShell + The display name of a dynamic profile generator for PowerShell + + + Générateur de profil Azure Cloud Shell + The display name of a dynamic profile generator for Azure Cloud Shell + + + Générateur de profil Visual Studio + The display name of a dynamic profile generator for Visual Studio + + + Générateur de profil d’hôte SSH + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw index 1f4a1e65a7..cb79ec9849 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Apri directory di lavoro corrente + + Generatore di profili di distribuzione WSL + The display name of a dynamic profile generator for WSL distros + + + Generatore di profili PowerShell + The display name of a dynamic profile generator for PowerShell + + + Generatore di profili di Azure Cloud Shell + The display name of a dynamic profile generator for Azure Cloud Shell + + + Generatore di profili Visual Studio + The display name of a dynamic profile generator for Visual Studio + + + Generatore di profili host SSH + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw index 9743e14162..e8b0d12ca4 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ 現在の作業ディレクトリを開く + + WSL ディストリビューション プロファイル ジェネレーター + The display name of a dynamic profile generator for WSL distros + + + PowerShell プロファイル ジェネレーター + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell プロファイル ジェネレーター + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio プロファイル ジェネレーター + The display name of a dynamic profile generator for Visual Studio + + + SSH ホスト プロファイル ジェネレーター + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw index 378d4d04bf..afe633e362 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ 현재 작업 디렉터리 열기 + + WSL 배포 프로필 생성기 + The display name of a dynamic profile generator for WSL distros + + + PowerShell 프로필 생성기 + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell 프로필 생성기 + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio 프로필 생성기 + The display name of a dynamic profile generator for Visual Studio + + + SSH 호스트 프로필 생성기 + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw index 4a28f1aa00..205bac0483 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Abrir o diretório de trabalho atual + + Gerador de Perfil de Distribuição WSL + The display name of a dynamic profile generator for WSL distros + + + Gerador de Perfil do PowerShell + The display name of a dynamic profile generator for PowerShell + + + Gerador de Perfil do Azure Cloud Shell + The display name of a dynamic profile generator for Azure Cloud Shell + + + Gerador de Perfil do Visual Studio + The display name of a dynamic profile generator for Visual Studio + + + Gerador de Perfil de Host SSH + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw index afcdd02d71..508f979745 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw @@ -1,17 +1,17 @@ - @@ -740,4 +740,24 @@ Öφěп сùяŗĕʼnŧ ώθяќìⁿġ ðїгéćţοŗу !!! !!! !!! - + + ẄŠ₤ Ďΐşţŗĭьúţĭοй Рŗоƒįĺé Ĝèŋëѓάтбŕ !!! !!! !!! ! + The display name of a dynamic profile generator for WSL distros + + + РöшєřŜђěłŀ Ρѓòƒĭļз Ġèпéгâŧθѓ !!! !!! !! + The display name of a dynamic profile generator for PowerShell + + + Δżúѓ℮ Ċℓǿύď Śнéļļ Рŗôƒìŀё Ğēņεřǻŧōґ !!! !!! !!! ! + The display name of a dynamic profile generator for Azure Cloud Shell + + + Vΐšџаℓ Şτϋδίǿ Ρѓőƒϊĺĕ Ğёйěřąтοŕ !!! !!! !!! + The display name of a dynamic profile generator for Visual Studio + + + ŠŚΗ Ħøŝť Рґǿƒιŀέ Ġеπèŕăťθґ !!! !!! ! + The display name of a dynamic profile generator for SSH hosts + + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw index afcdd02d71..508f979745 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw @@ -1,17 +1,17 @@ - @@ -740,4 +740,24 @@ Öφěп сùяŗĕʼnŧ ώθяќìⁿġ ðїгéćţοŗу !!! !!! !!! - + + ẄŠ₤ Ďΐşţŗĭьúţĭοй Рŗоƒįĺé Ĝèŋëѓάтбŕ !!! !!! !!! ! + The display name of a dynamic profile generator for WSL distros + + + РöшєřŜђěłŀ Ρѓòƒĭļз Ġèпéгâŧθѓ !!! !!! !! + The display name of a dynamic profile generator for PowerShell + + + Δżúѓ℮ Ċℓǿύď Śнéļļ Рŗôƒìŀё Ğēņεřǻŧōґ !!! !!! !!! ! + The display name of a dynamic profile generator for Azure Cloud Shell + + + Vΐšџаℓ Şτϋδίǿ Ρѓőƒϊĺĕ Ğёйěřąтοŕ !!! !!! !!! + The display name of a dynamic profile generator for Visual Studio + + + ŠŚΗ Ħøŝť Рґǿƒιŀέ Ġеπèŕăťθґ !!! !!! ! + The display name of a dynamic profile generator for SSH hosts + + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw index afcdd02d71..508f979745 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw @@ -1,17 +1,17 @@ - @@ -740,4 +740,24 @@ Öφěп сùяŗĕʼnŧ ώθяќìⁿġ ðїгéćţοŗу !!! !!! !!! - + + ẄŠ₤ Ďΐşţŗĭьúţĭοй Рŗоƒįĺé Ĝèŋëѓάтбŕ !!! !!! !!! ! + The display name of a dynamic profile generator for WSL distros + + + РöшєřŜђěłŀ Ρѓòƒĭļз Ġèпéгâŧθѓ !!! !!! !! + The display name of a dynamic profile generator for PowerShell + + + Δżúѓ℮ Ċℓǿύď Śнéļļ Рŗôƒìŀё Ğēņεřǻŧōґ !!! !!! !!! ! + The display name of a dynamic profile generator for Azure Cloud Shell + + + Vΐšџаℓ Şτϋδίǿ Ρѓőƒϊĺĕ Ğёйěřąтοŕ !!! !!! !!! + The display name of a dynamic profile generator for Visual Studio + + + ŠŚΗ Ħøŝť Рґǿƒιŀέ Ġеπèŕăťθґ !!! !!! ! + The display name of a dynamic profile generator for SSH hosts + + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw index 147a457dac..bffaaee59c 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ Открыть текущую рабочую папку + + Генератор профилей дистрибутивов WSL + The display name of a dynamic profile generator for WSL distros + + + Генератор профилей PowerShell + The display name of a dynamic profile generator for PowerShell + + + Генератор профилей Azure Cloud Shell + The display name of a dynamic profile generator for Azure Cloud Shell + + + Генератор профилей Visual Studio + The display name of a dynamic profile generator for Visual Studio + + + Генератор профилей узлов SSH + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw index abef778111..649af7ffbc 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ 打开当前工作目录 + + WSL 发行版配置文件生成器 + The display name of a dynamic profile generator for WSL distros + + + PowerShell 配置文件生成器 + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell 配置文件生成器 + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio 配置文件生成器 + The display name of a dynamic profile generator for Visual Studio + + + SSH 主机配置文件生成器 + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw index 75edbf8518..f97fadd75c 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw @@ -1,17 +1,17 @@  - @@ -740,4 +740,24 @@ 開啟目前的工作目錄 + + WSL 發佈版設定檔產生器 + The display name of a dynamic profile generator for WSL distros + + + PowerShell 設定檔產生器 + The display name of a dynamic profile generator for PowerShell + + + Azure Cloud Shell 設定檔產生器 + The display name of a dynamic profile generator for Azure Cloud Shell + + + Visual Studio 設定檔產生器 + The display name of a dynamic profile generator for Visual Studio + + + SSH 主機設定檔產生器 + The display name of a dynamic profile generator for SSH hosts + \ No newline at end of file From 3ae6bbf2df7919071fe8477676d6b7f5d838d558 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Sat, 31 May 2025 12:50:18 -0700 Subject: [PATCH 03/74] Enable SSH Generator Feature Flag and polish UI (#18814) --- ...d500-50ad-8a1a-c400c3262db3}.scale-100.png | Bin 647 -> 0 bytes ...d500-50ad-8a1a-c400c3262db3}.scale-200.png | Bin 787 -> 0 bytes .../TerminalSettingsEditor/Extensions.cpp | 6 ++ .../TerminalSettingsEditor/Extensions.h | 1 + .../TerminalSettingsEditor/Extensions.idl | 1 + .../TerminalSettingsEditor/Extensions.xaml | 55 ++++++++++++++++++ .../SshHostGenerator.cpp | 4 +- src/features.xml | 5 ++ 8 files changed, 70 insertions(+), 2 deletions(-) delete mode 100644 src/cascadia/CascadiaPackage/ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.scale-100.png delete mode 100644 src/cascadia/CascadiaPackage/ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.scale-200.png diff --git a/src/cascadia/CascadiaPackage/ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.scale-100.png b/src/cascadia/CascadiaPackage/ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.scale-100.png deleted file mode 100644 index 6d57b166f20df38330a9dc881ac4334922b64b00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 647 zcmV;20(kw2P)EX>4Tx04R}tkv&MmKpe$iQ%j3f9Lyl%kfAzR5EXHhDi*;)X)CnqU~=gfG%+M8 zE{=k0!NHHks)LKOt`4q(Aou~|;_9U6A|?JWDYS_3;J6>}?mh0_0YbCJG^=ME&~)3( zBok7;up)(D5k&|hB8bV%GE&)v9DK*uJpz2ai}Ni1bAOILwP-OQAQ8_p!?cOliKjPh zgY!PI$SSf*d`>)O(glehxvqHp#<}dWz%xZNmzg6LiKTKED_zVgrbawP98)!&@`b#| zD(5ZETCL97_v9~(7WL&U*J%zTiAAK4h6Dw5R8fPCIITJ<7BaM-@bM41{xrE1a#g{| zF^>&skX=9cAN=mtDosv!NznuldU2eO5g@z^v>J}{ee5``6Cn5uT(Ud*lat9cEGGtSBr65hAR07`5=$i__z%9_b>h;#%$LRx*qpp^2fP+I| zv`pD+KJSin_xA6Zc7H!o6mptvbX6w+000JJOGiWi{{a60|De66lK=n!32;bRa{vG? zBLDy{BLR4&KXw2B00(qQO+^Re3e)v3~guj`k$pCPyTlEmg z3gq!a0ZEXsjJY-Vpw_ybND?G_uHD*0kd!1{WREX>4Tx04R}tkv&MmKpe$iQ%j3f9Lyl%kfAzR5EXHhDi*;)X)CnqU~=gfG%+M8 zE{=k0!NHHks)LKOt`4q(Aou~|;_9U6A|?JWDYS_3;J6>}?mh0_0YbCJG^=ME&~)3( zBok7;up)(D5k&|hB8bV%GE&)v9DK*uJpz2ai}Ni1bAOILwP-OQAQ8_p!?cOliKjPh zgY!PI$SSf*d`>)O(glehxvqHp#<}dWz%xZNmzg6LiKTKED_zVgrbawP98)!&@`b#| zD(5ZETCL97_v9~(7WL&U*J%zTiAAK4h6Dw5R8fPCIITJ<7BaM-@bM41{xrE1a#g{| zF^>&skX=9cAN=mtDosv!NznuldU2eO5g@z^v>J}{ee5``6Cn5uT(Ud*lat9cEGGtSBr65hAR07`5=$i__z%9_b>h;#%$LRx*qpp^2fP+I| zv`pD+KJSin_xA6Zc7H!o6mptvbX6w+000JJOGiWi{{a60|De66lK=n!32;bRa{vG? zBLDy{BLR4&KXw2B00(qQO+^Re3Wk>caVsHnX#M&ZX=9kV?EeWm_RDb^43GNX&YFB4EzsN!($xm=8KNC&h> zT$mZmjFb{`&M$Tg0JoJ`d3e9NA%#czjWJf6^bj!cxB9;!WZ;&8D!)B_{$5|uPzqWz zUNbP%90Qrn={YL`>>*8SiFw1U^Qo`9wgZvXI8&>c(YvIFh* RdH4VT002ovPDHLkV1ff0TR{K- diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.cpp b/src/cascadia/TerminalSettingsEditor/Extensions.cpp index ffaff9a33d..a3aec0ad7c 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Extensions.cpp @@ -500,6 +500,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { if (!extPkgVM.Package().DisplayName().empty()) { + // Check if the first char of the icon is in the Segoe MDL2 Icons list + const auto ch = til::at(extPkgVM.Package().Icon(), 0); + if (ch >= L'\uE700' && ch <= L'\uF8FF') + { + return ComplexTemplateWithFontIcon(); + } return ComplexTemplate(); } return DefaultTemplate(); diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.h b/src/cascadia/TerminalSettingsEditor/Extensions.h index 6b7eafad57..a9379829d2 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.h +++ b/src/cascadia/TerminalSettingsEditor/Extensions.h @@ -183,6 +183,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, DefaultTemplate, nullptr); WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, ComplexTemplate, nullptr); + WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, ComplexTemplateWithFontIcon, nullptr); }; }; diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.idl b/src/cascadia/TerminalSettingsEditor/Extensions.idl index 4dc3018c36..6093471014 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.idl +++ b/src/cascadia/TerminalSettingsEditor/Extensions.idl @@ -77,5 +77,6 @@ namespace Microsoft.Terminal.Settings.Editor Windows.UI.Xaml.DataTemplate DefaultTemplate; Windows.UI.Xaml.DataTemplate ComplexTemplate; + Windows.UI.Xaml.DataTemplate ComplexTemplateWithFontIcon; } } diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.xaml b/src/cascadia/TerminalSettingsEditor/Extensions.xaml index eec3e69290..a3f80cb2e7 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Extensions.xaml @@ -42,6 +42,7 @@ + + + + + + + + + + + + + + + + + + + + + Enables the dynamic profile generator for OpenSSH config files 9031 AlwaysDisabled + + Dev + Canary + Preview + From 9dbcf4b9dd33d36ac0145db195db7563f5a86c05 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 2 Jun 2025 13:27:22 -0500 Subject: [PATCH 04/74] build: try even harder to find a working VC tools version (#18996) I can't explain this, but VS 17.14 ships with VisualCpp.Tools.Core version 14.44.35208 but the files say 14.44.35207. --- build/scripts/Set-LatestVCToolsVersion.ps1 | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/build/scripts/Set-LatestVCToolsVersion.ps1 b/build/scripts/Set-LatestVCToolsVersion.ps1 index 3156fff1bc..4ebf3a3eec 100644 --- a/build/scripts/Set-LatestVCToolsVersion.ps1 +++ b/build/scripts/Set-LatestVCToolsVersion.ps1 @@ -1,8 +1,28 @@ $VSInstances = ([xml](& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -include packages -format xml)) $VSPackages = $VSInstances.instances.instance.packages.package -$LatestVCPackage = ($VSInstances.instances.instance.packages.package | ? { $_.id -eq "Microsoft.VisualCpp.Tools.Core" }) +$LatestVCPackage = ($VSPackages | ? { $_.id -eq "Microsoft.VisualCpp.Tools.Core" }) $LatestVCToolsVersion = $LatestVCPackage.version; +$VSRoot = (& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property 'resolvedInstallationPath') +$VCToolsRoot = Join-Path $VSRoot "VC\Tools\MSVC" + +# We have observed a few instances where the VC tools package version actually +# differs from the version on the files themselves. We might as well check +# whether the version we just found _actually exists_ before we use it. +# We'll use whichever highest version exists. +$PackageVCToolPath = Join-Path $VCToolsRoot $LatestVCToolsVersion +If ($Null -Eq (Get-Item $PackageVCToolPath -ErrorAction:Ignore)) { + $VCToolsVersions = Get-ChildItem $VCToolsRoot | ForEach-Object { + [Version]$_.Name + } | Sort -Descending + $LatestActualVCToolsVersion = $VCToolsVersions | Select -First 1 + + If ([Version]$LatestVCToolsVersion -Ne $LatestActualVCToolsVersion) { + Write-Output "VC Tools Mismatch: Directory = $LatestActualVCToolsVersion, Package = $LatestVCToolsVersion" + $LatestVCToolsVersion = $LatestActualVCToolsVersion.ToString(3) + } +} + Write-Output "Latest VCToolsVersion: $LatestVCToolsVersion" Write-Output "Updating VCToolsVersion environment variable for job" Write-Output "##vso[task.setvariable variable=VCToolsVersion]$LatestVCToolsVersion" From 37d5aec1cff01e27763d15ca2b2f8f587005f849 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 2 Jun 2025 12:12:48 -0700 Subject: [PATCH 05/74] Replace extensions page hyperlink with fwlink (#18997) --- src/cascadia/TerminalSettingsEditor/Extensions.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsEditor/Extensions.xaml b/src/cascadia/TerminalSettingsEditor/Extensions.xaml index a3f80cb2e7..db1637b42c 100644 --- a/src/cascadia/TerminalSettingsEditor/Extensions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Extensions.xaml @@ -451,7 +451,7 @@ + NavigateUri="https://go.microsoft.com/fwlink/?linkid=2321753" /> Date: Mon, 2 Jun 2025 17:22:24 -0700 Subject: [PATCH 06/74] Update command palette search to prioritize "longest substring" match. (#18700) It's the fzf algorithm! Repurposed work from #16586 - I think the fzf algo fits here where it optimizes to find the optimal match based on consecutive chars and word boundaries. - There are some edge cases where a match with a small gap could get a higher score than a match of consecutive chars when the match with a gap has other bonuses (FirstChar * Boundary Bonus). This can be adjusted by adjusting the bonuses or removing them if needed. - From reading the thread in #6693 it looked like you guys were leaning towards something like the fzf algo. - License file is now updated in https://github.com/nvim-telescope/telescope-fzf-native.nvim repository - https://github.com/nvim-telescope/telescope-fzf-native.nvim/pull/148 - https://github.com/junegunn/fzf/issues/4310 - Removed the following from the original implementation to minimize complexity and the size of the PR. (Let me know if any of these should be added back). - Query expressions "$:StartsWith ^:EndsWith |:Or !:Not etc" - Slab to avoid allocating the scoring matrix. This felt like overkill for the number of items in the command pallete. - Fallback to V1 algorithm for very long strings. I want to say that the command palette won't have strings this long. - Added the logic from GH#9941 that copies pattern and text chars to string for comparision with lstrcmpi - It does this twice now which isn't great... Closes #6693 --------- Co-authored-by: Leonard Hecker --- .github/actions/spelling/excludes.txt | 1 + .github/actions/spelling/expect/expect.txt | 4 + NOTICE.md | 32 + .../FilteredCommandTests.cpp | 147 ++--- src/cascadia/TerminalApp/CommandPalette.cpp | 5 +- src/cascadia/TerminalApp/FilteredCommand.cpp | 207 ++----- src/cascadia/TerminalApp/FilteredCommand.h | 8 +- src/cascadia/TerminalApp/FilteredCommand.idl | 3 - .../TerminalApp/SnippetsPaneContent.cpp | 3 +- .../TerminalApp/SnippetsPaneContent.h | 10 +- .../TerminalApp/SuggestionsControl.cpp | 5 +- .../TerminalApp/TerminalAppLib.vcxproj | 2 + .../TerminalAppLib.vcxproj.filters | 12 + src/cascadia/TerminalApp/fzf/LICENSE | 22 + src/cascadia/TerminalApp/fzf/fzf.cpp | 432 +++++++++++++ src/cascadia/TerminalApp/fzf/fzf.h | 27 + src/cascadia/ut_app/FzfTests.cpp | 568 ++++++++++++++++++ .../ut_app/TerminalApp.UnitTests.vcxproj | 1 + src/inc/til.h | 4 +- src/inc/til/type_traits.h | 11 + 20 files changed, 1235 insertions(+), 269 deletions(-) create mode 100644 src/cascadia/TerminalApp/fzf/LICENSE create mode 100644 src/cascadia/TerminalApp/fzf/fzf.cpp create mode 100644 src/cascadia/TerminalApp/fzf/fzf.h create mode 100644 src/cascadia/ut_app/FzfTests.cpp diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index 896211b8f1..c142f11548 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -133,3 +133,4 @@ Resources/(?!en) ^\Qsrc/terminal/parser/ft_fuzzwrapper/run.bat\E$ ^\Qsrc/tools/lnkd/lnkd.bat\E$ ^\Qsrc/tools/pixels/pixels.bat\E$ +^\Qsrc/cascadia/ut_app/FzfTests.cpp\E$ diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index cc53f71e5f..318903fe17 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -651,6 +651,7 @@ FONTSTRING FONTTYPE FONTWIDTH FONTWINDOW +foob FORCEOFFFEEDBACK FORCEONFEEDBACK FRAMECHANGED @@ -668,9 +669,11 @@ fuzzer fuzzmain fuzzmap fuzzwrapper +fuzzyfinder fwdecl fwe fwlink +fzf gci gcx gdi @@ -1248,6 +1251,7 @@ onecoreuuid ONECOREWINDOWS onehalf oneseq +oob openbash opencode opencon diff --git a/NOTICE.md b/NOTICE.md index bd99dd939c..efe962bc0d 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -285,6 +285,8 @@ specific language governing permissions and limitations under the License. **Source**: [https://github.com/commonmark/cmark](https://github.com/commonmark/cmark) ### License + +``` Copyright (c) 2014, John MacFarlane All rights reserved. @@ -455,6 +457,36 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## fzf + +### License + +``` +The MIT License (MIT) + +Copyright (c) 2013-2024 Junegunn Choi +Copyright (c) 2021-2025 Simon Hauser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` # Microsoft Open Source diff --git a/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp b/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp index e96675fd32..023ac04702 100644 --- a/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp @@ -32,37 +32,35 @@ namespace TerminalAppLocalTests { auto result = RunOnUIThread([]() { const auto paletteItem{ winrt::make(L"AAAAAABBBBBBCCC") }; + const auto filteredCommand = winrt::make_self(paletteItem); + { Log::Comment(L"Testing command name segmentation with no filter"); - const auto filteredCommand = winrt::make_self(paletteItem); - auto segments = filteredCommand->_computeHighlightedName().Segments(); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 1u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); } { Log::Comment(L"Testing command name segmentation with empty filter"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L""; - auto segments = filteredCommand->_computeHighlightedName().Segments(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L""))); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 1u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); } { Log::Comment(L"Testing command name segmentation with filter equal to the string"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"AAAAAABBBBBBCCC"; - auto segments = filteredCommand->_computeHighlightedName().Segments(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"AAAAAABBBBBBCCC"))); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 1u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); } { Log::Comment(L"Testing command name segmentation with filter with first character matching"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"A"; - auto segments = filteredCommand->_computeHighlightedName().Segments(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"A"))); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 2u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A"); VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); @@ -71,9 +69,8 @@ namespace TerminalAppLocalTests } { Log::Comment(L"Testing command name segmentation with filter with other case"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"a"; - auto segments = filteredCommand->_computeHighlightedName().Segments(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"a"))); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 2u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A"); VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); @@ -82,24 +79,20 @@ namespace TerminalAppLocalTests } { Log::Comment(L"Testing command name segmentation with filter matching several characters"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"ab"; - auto segments = filteredCommand->_computeHighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 4u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A"); - VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAA"); - VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(2).TextSegment(), L"B"); - VERIFY_IS_TRUE(segments.GetAt(2).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(3).TextSegment(), L"BBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(3).IsHighlighted()); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"ab"))); + auto segments = filteredCommand->HighlightedName().Segments(); + VERIFY_ARE_EQUAL(segments.Size(), 3u); + VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAA"); + VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); + VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AB"); + VERIFY_IS_TRUE(segments.GetAt(1).IsHighlighted()); + VERIFY_ARE_EQUAL(segments.GetAt(2).TextSegment(), L"BBBBBCCC"); + VERIFY_IS_FALSE(segments.GetAt(2).IsHighlighted()); } { Log::Comment(L"Testing command name segmentation with non matching filter"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"abcd"; - auto segments = filteredCommand->_computeHighlightedName().Segments(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"abcd"))); + auto segments = filteredCommand->HighlightedName().Segments(); VERIFY_ARE_EQUAL(segments.Size(), 1u); VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); @@ -113,53 +106,37 @@ namespace TerminalAppLocalTests { auto result = RunOnUIThread([]() { const auto paletteItem{ winrt::make(L"AAAAAABBBBBBCCC") }; - { - Log::Comment(L"Testing weight of command with no filter"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 0); - } - { - Log::Comment(L"Testing weight of command with empty filter"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L""; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 0); - } - { - Log::Comment(L"Testing weight of command with filter equal to the string"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"AAAAAABBBBBBCCC"; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 30); // 1 point for the first char and 2 points for the 14 consequent ones + 1 point for the beginning of the word - } - { - Log::Comment(L"Testing weight of command with filter with first character matching"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"A"; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 2); // 1 point for the first char match + 1 point for the beginning of the word - } - { - Log::Comment(L"Testing weight of command with filter with other case"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"a"; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 2); // 1 point for the first char match + 1 point for the beginning of the word - } - { - Log::Comment(L"Testing weight of command with filter matching several characters"); - const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"ab"; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - auto weight = filteredCommand->_computeWeight(); - VERIFY_ARE_EQUAL(weight, 3); // 1 point for the first char match + 1 point for the beginning of the word + 1 point for the match of "b" - } + const auto filteredCommand = winrt::make_self(paletteItem); + + const auto weigh = [&](const wchar_t* str) { + std::shared_ptr pattern; + if (str) + { + pattern = std::make_shared(fzf::matcher::ParsePattern(str)); + } + filteredCommand->UpdateFilter(std::move(pattern)); + return filteredCommand->Weight(); + }; + + const auto null = weigh(nullptr); + const auto empty = weigh(L""); + const auto full = weigh(L"AAAAAABBBBBBCCC"); + const auto firstChar = weigh(L"A"); + const auto otherCase = weigh(L"a"); + const auto severalChars = weigh(L"ab"); + + VERIFY_ARE_EQUAL(null, 0); + VERIFY_ARE_EQUAL(empty, 0); + VERIFY_IS_GREATER_THAN(full, 100); + + VERIFY_IS_GREATER_THAN(firstChar, 0); + VERIFY_IS_LESS_THAN(firstChar, full); + + VERIFY_IS_GREATER_THAN(otherCase, 0); + VERIFY_IS_LESS_THAN(otherCase, full); + + VERIFY_IS_GREATER_THAN(severalChars, otherCase); + VERIFY_IS_LESS_THAN(severalChars, full); }); VERIFY_SUCCEEDED(result); @@ -181,14 +158,10 @@ namespace TerminalAppLocalTests { Log::Comment(L"Testing comparison of commands with empty filter"); const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L""; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - filteredCommand->_Weight = filteredCommand->_computeWeight(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L""))); const auto filteredCommand2 = winrt::make_self(paletteItem2); - filteredCommand2->_Filter = L""; - filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName(); - filteredCommand2->_Weight = filteredCommand2->_computeWeight(); + filteredCommand2->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L""))); VERIFY_ARE_EQUAL(filteredCommand->Weight(), filteredCommand2->Weight()); VERIFY_IS_TRUE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2)); @@ -196,16 +169,12 @@ namespace TerminalAppLocalTests { Log::Comment(L"Testing comparison of commands with different weights"); const auto filteredCommand = winrt::make_self(paletteItem); - filteredCommand->_Filter = L"B"; - filteredCommand->_HighlightedName = filteredCommand->_computeHighlightedName(); - filteredCommand->_Weight = filteredCommand->_computeWeight(); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"B"))); const auto filteredCommand2 = winrt::make_self(paletteItem2); - filteredCommand2->_Filter = L"B"; - filteredCommand2->_HighlightedName = filteredCommand2->_computeHighlightedName(); - filteredCommand2->_Weight = filteredCommand2->_computeWeight(); + filteredCommand2->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"B"))); - VERIFY_IS_TRUE(filteredCommand->Weight() < filteredCommand2->Weight()); // Second command gets more points due to the beginning of the word + VERIFY_IS_LESS_THAN(filteredCommand->Weight(), filteredCommand2->Weight()); // Second command gets more points due to the beginning of the word VERIFY_IS_FALSE(winrt::TerminalApp::implementation::FilteredCommand::Compare(*filteredCommand, *filteredCommand2)); } }); diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index 6d5540703f..e2e7526db8 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -1174,12 +1174,15 @@ namespace winrt::TerminalApp::implementation } else if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::ActionMode || _currentMode == CommandPaletteMode::CommandlineMode) { + auto pattern = std::make_shared(fzf::matcher::ParsePattern(searchText)); + for (const auto& action : commandsToFilter) { // Update filter for all commands // This will modify the highlighting but will also lead to re-computation of weight (and consequently sorting). // Pay attention that it already updates the highlighting in the UI - action.UpdateFilter(searchText); + auto impl = winrt::get_self(action); + impl->UpdateFilter(pattern); // if there is active search we skip commands with 0 weight if (searchText.empty() || action.Weight() > 0) diff --git a/src/cascadia/TerminalApp/FilteredCommand.cpp b/src/cascadia/TerminalApp/FilteredCommand.cpp index d6c6c38a28..9d91ded825 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.cpp +++ b/src/cascadia/TerminalApp/FilteredCommand.cpp @@ -5,6 +5,7 @@ #include "CommandPalette.h" #include "HighlightedText.h" #include +#include "fzf/fzf.h" #include "FilteredCommand.g.cpp" @@ -35,197 +36,75 @@ namespace winrt::TerminalApp::implementation void FilteredCommand::_constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item) { _Item = item; - _Filter = L""; _Weight = 0; - _HighlightedName = _computeHighlightedName(); + + _update(); // Recompute the highlighted name if the item name changes _itemChangedRevoker = _Item.PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](auto& /*sender*/, auto& e) { auto filteredCommand{ weakThis.get() }; if (filteredCommand && e.PropertyName() == L"Name") { - filteredCommand->HighlightedName(filteredCommand->_computeHighlightedName()); - filteredCommand->Weight(filteredCommand->_computeWeight()); + filteredCommand->_update(); } }); } - void FilteredCommand::UpdateFilter(const winrt::hstring& filter) + void FilteredCommand::UpdateFilter(std::shared_ptr pattern) { // If the filter was not changed we want to prevent the re-computation of matching // that might result in triggering a notification event - if (filter != _Filter) + if (pattern != _pattern) { - Filter(filter); - HighlightedName(_computeHighlightedName()); - Weight(_computeWeight()); + _pattern = pattern; + _update(); } } - // Method Description: - // - Looks up the filter characters within the item name. - // Iterating through the filter and the item name it tries to associate the next filter character - // with the first appearance of this character in the item name suffix. - // - // E.g., for filter="c l t s" and name="close all tabs after this", the match will be "CLose TabS after this". - // - // The item name is then split into segments (groupings of matched and non matched characters). - // - // E.g., the segments were the example above will be "CL", "ose ", "T", "ab", "S", "after this". - // - // The segments matching the filter characters are marked as highlighted. - // - // E.g., ("CL", true) ("ose ", false), ("T", true), ("ab", false), ("S", true), ("after this", false) - // - // TODO: we probably need to merge this logic with _getWeight computation? - // - // Return Value: - // - The HighlightedText object initialized with the segments computed according to the algorithm above. - winrt::TerminalApp::HighlightedText FilteredCommand::_computeHighlightedName() + void FilteredCommand::_update() { - const auto segments = winrt::single_threaded_observable_vector(); - auto commandName = _Item.Name(); - auto isProcessingMatchedSegment = false; - uint32_t nextOffsetToReport = 0; - uint32_t currentOffset = 0; + std::vector segments; + const auto commandName = _Item.Name(); + int32_t weight = 0; - for (const auto searchChar : _Filter) + if (!_pattern || _pattern->terms.empty()) { - const WCHAR searchCharAsString[] = { searchChar, L'\0' }; - while (true) + segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(commandName, false)); + } + else if (auto match = fzf::matcher::Match(commandName, *_pattern.get()); !match) + { + segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(commandName, false)); + } + else + { + auto& matchResult = *match; + weight = matchResult.Score; + + size_t lastPos = 0; + for (const auto& run : matchResult.Runs) { - if (currentOffset == commandName.size()) + const auto& [start, end] = run; + if (start > lastPos) { - // There are still unmatched filter characters but we finished scanning the name. - // In this case we return the entire item name as unmatched - auto entireNameSegment{ winrt::make(commandName, false) }; - segments.Clear(); - segments.Append(entireNameSegment); - return winrt::make(segments); + hstring nonMatch{ til::safe_slice_abs(commandName, lastPos, start) }; + segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(nonMatch, false)); } - // GH#9941: search should be locale-aware as well - // We use the same comparison method as upon sorting to guarantee consistent behavior - const WCHAR currentCharAsString[] = { commandName[currentOffset], L'\0' }; - auto isCurrentCharMatched = lstrcmpi(searchCharAsString, currentCharAsString) == 0; - if (isProcessingMatchedSegment != isCurrentCharMatched) - { - // We reached the end of the region (matched character came after a series of unmatched or vice versa). - // Conclude the segment and add it to the list. - // Skip segment if it is empty (might happen when the first character of the name is matched) - auto sizeToReport = currentOffset - nextOffsetToReport; - if (sizeToReport > 0) - { - winrt::hstring segment{ commandName.data() + nextOffsetToReport, sizeToReport }; - auto highlightedSegment{ winrt::make(segment, isProcessingMatchedSegment) }; - segments.Append(highlightedSegment); - nextOffsetToReport = currentOffset; - } - isProcessingMatchedSegment = isCurrentCharMatched; - } + hstring matchSeg{ til::safe_slice_abs(commandName, start, end + 1) }; + segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(matchSeg, true)); - currentOffset++; + lastPos = end + 1; + } - if (isCurrentCharMatched) - { - // We have matched this filter character, let's move to matching the next filter char - break; - } + if (lastPos < commandName.size()) + { + hstring tail{ til::safe_slice_abs(commandName, lastPos, SIZE_T_MAX) }; + segments.emplace_back(winrt::TerminalApp::HighlightedTextSegment(tail, false)); } } - // Either the filter or the item name were fully processed. - // If we were in the middle of the matched segment - add it. - if (isProcessingMatchedSegment) - { - auto sizeToReport = currentOffset - nextOffsetToReport; - if (sizeToReport > 0) - { - winrt::hstring segment{ commandName.data() + nextOffsetToReport, sizeToReport }; - auto highlightedSegment{ winrt::make(segment, true) }; - segments.Append(highlightedSegment); - nextOffsetToReport = currentOffset; - } - } - - // Now create a segment for all remaining characters. - // We will have remaining characters as long as the filter is shorter than the item name. - auto sizeToReport = commandName.size() - nextOffsetToReport; - if (sizeToReport > 0) - { - winrt::hstring segment{ commandName.data() + nextOffsetToReport, sizeToReport }; - auto highlightedSegment{ winrt::make(segment, false) }; - segments.Append(highlightedSegment); - } - - return winrt::make(segments); - } - - // Function Description: - // - Calculates a "weighting" by which should be used to order a item - // name relative to other names, given a specific search string. - // Currently, this is based off of two factors: - // * The weight is incremented once for each matched character of the - // search text. - // * If a matching character from the search text was found at the start - // of a word in the name, then we increment the weight again. - // * For example, for a search string "sp", we want "Split Pane" to - // appear in the list before "Close Pane" - // * Consecutive matches will be weighted higher than matches with - // characters in between the search characters. - // - This will return 0 if the item should not be shown. If all the - // characters of search text appear in order in `name`, then this function - // will return a positive number. There can be any number of characters - // separating consecutive characters in searchText. - // * For example: - // "name": "New Tab" - // "name": "Close Tab" - // "name": "Close Pane" - // "name": "[-] Split Horizontal" - // "name": "[ | ] Split Vertical" - // "name": "Next Tab" - // "name": "Prev Tab" - // "name": "Open Settings" - // "name": "Open Media Controls" - // * "open" should return both "**Open** Settings" and "**Open** Media Controls". - // * "Tab" would return "New **Tab**", "Close **Tab**", "Next **Tab**" and "Prev - // **Tab**". - // * "P" would return "Close **P**ane", "[-] S**p**lit Horizontal", "[ | ] - // S**p**lit Vertical", "**P**rev Tab", "O**p**en Settings" and "O**p**en Media - // Controls". - // * "sv" would return "[ | ] Split Vertical" (by matching the **S** in - // "Split", then the **V** in "Vertical"). - // Arguments: - // - searchText: the string of text to search for in `name` - // - name: the name to check - // Return Value: - // - the relative weight of this match - int FilteredCommand::_computeWeight() - { - auto result = 0; - auto isNextSegmentWordBeginning = true; - - for (const auto& segment : _HighlightedName.Segments()) - { - const auto& segmentText = segment.TextSegment(); - const auto segmentSize = segmentText.size(); - - if (segment.IsHighlighted()) - { - // Give extra point for each consecutive match - result += (segmentSize <= 1) ? segmentSize : 1 + 2 * (segmentSize - 1); - - // Give extra point if this segment is at the beginning of a word - if (isNextSegmentWordBeginning) - { - result++; - } - } - - isNextSegmentWordBeginning = segmentSize > 0 && segmentText[segmentSize - 1] == L' '; - } - - return result; + HighlightedName(winrt::make(winrt::single_threaded_observable_vector(std::move(segments)))); + Weight(weight); } // Function Description: @@ -243,9 +122,9 @@ namespace winrt::TerminalApp::implementation if (firstWeight == secondWeight) { - std::wstring_view firstName{ first.Item().Name() }; - std::wstring_view secondName{ second.Item().Name() }; - return lstrcmpi(firstName.data(), secondName.data()) < 0; + const auto firstName = first.Item().Name(); + const auto secondName = second.Item().Name(); + return til::compare_linguistic_insensitive(firstName, secondName) < 0; } return firstWeight > secondWeight; diff --git a/src/cascadia/TerminalApp/FilteredCommand.h b/src/cascadia/TerminalApp/FilteredCommand.h index f304ad032a..ff6f200c58 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.h +++ b/src/cascadia/TerminalApp/FilteredCommand.h @@ -5,6 +5,7 @@ #include "HighlightedTextControl.h" #include "FilteredCommand.g.h" +#include "fzf/fzf.h" // fwdecl unittest classes namespace TerminalAppLocalTests @@ -19,13 +20,12 @@ namespace winrt::TerminalApp::implementation FilteredCommand() = default; FilteredCommand(const winrt::TerminalApp::PaletteItem& item); - virtual void UpdateFilter(const winrt::hstring& filter); + virtual void UpdateFilter(std::shared_ptr pattern); static int Compare(const winrt::TerminalApp::FilteredCommand& first, const winrt::TerminalApp::FilteredCommand& second); til::property_changed_event PropertyChanged; WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::PaletteItem, Item, PropertyChanged.raise, nullptr); - WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Filter, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::HighlightedText, HighlightedName, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(int, Weight, PropertyChanged.raise); @@ -33,8 +33,8 @@ namespace winrt::TerminalApp::implementation void _constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item); private: - winrt::TerminalApp::HighlightedText _computeHighlightedName(); - int _computeWeight(); + std::shared_ptr _pattern; + void _update(); Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _itemChangedRevoker; friend class TerminalAppLocalTests::FilteredCommandTests; diff --git a/src/cascadia/TerminalApp/FilteredCommand.idl b/src/cascadia/TerminalApp/FilteredCommand.idl index a63e6e8110..a5a1e34cf4 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.idl +++ b/src/cascadia/TerminalApp/FilteredCommand.idl @@ -12,10 +12,7 @@ namespace TerminalApp FilteredCommand(PaletteItem item); PaletteItem Item { get; }; - String Filter; HighlightedText HighlightedName { get; }; Int32 Weight; - - void UpdateFilter(String filter); } } diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.cpp b/src/cascadia/TerminalApp/SnippetsPaneContent.cpp index 415e5d8201..d2dd9fb03e 100644 --- a/src/cascadia/TerminalApp/SnippetsPaneContent.cpp +++ b/src/cascadia/TerminalApp/SnippetsPaneContent.cpp @@ -32,6 +32,7 @@ namespace winrt::TerminalApp::implementation void SnippetsPaneContent::_updateFilteredCommands() { const auto& queryString = _filterBox().Text(); + auto pattern = std::make_shared(fzf::matcher::ParsePattern(queryString)); // DON'T replace the itemSource here. If you do, it'll un-expand all the // nested items the user has expanded. Instead, just update the filter. @@ -39,7 +40,7 @@ namespace winrt::TerminalApp::implementation for (const auto& t : _allTasks) { auto impl = winrt::get_self(t); - impl->UpdateFilter(queryString); + impl->UpdateFilter(pattern); } } diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.h b/src/cascadia/TerminalApp/SnippetsPaneContent.h index c469e83fcc..2732522f3e 100644 --- a/src/cascadia/TerminalApp/SnippetsPaneContent.h +++ b/src/cascadia/TerminalApp/SnippetsPaneContent.h @@ -77,13 +77,14 @@ namespace winrt::TerminalApp::implementation } } - void UpdateFilter(const winrt::hstring& filter) + void UpdateFilter(std::shared_ptr pattern) { - _filteredCommand->UpdateFilter(filter); + _pattern = std::move(pattern); + _filteredCommand->UpdateFilter(_pattern); for (const auto& c : _children) { auto impl = winrt::get_self(c); - impl->UpdateFilter(filter); + impl->UpdateFilter(_pattern); } PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Visibility" }); @@ -108,6 +109,7 @@ namespace winrt::TerminalApp::implementation bool HasChildren() { return _children.Size() > 0; } winrt::Microsoft::Terminal::Settings::Model::Command Command() { return _command; } winrt::TerminalApp::FilteredCommand FilteredCommand() { return *_filteredCommand; } + std::shared_ptr _pattern; int32_t Row() { return HasChildren() ? 2 : 1; } // See the BODGY comment in the .XAML for explanation @@ -117,7 +119,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Visibility Visibility() { // Is there no filter, or do we match it? - if (_filteredCommand->Filter().empty() || _filteredCommand->Weight() > 0) + if ((!_pattern || _pattern->terms.empty() || _filteredCommand->Weight() > 0)) { return winrt::Windows::UI::Xaml::Visibility::Visible; } diff --git a/src/cascadia/TerminalApp/SuggestionsControl.cpp b/src/cascadia/TerminalApp/SuggestionsControl.cpp index 442b80243c..3ebc7ca158 100644 --- a/src/cascadia/TerminalApp/SuggestionsControl.cpp +++ b/src/cascadia/TerminalApp/SuggestionsControl.cpp @@ -936,12 +936,15 @@ namespace winrt::TerminalApp::implementation auto commandsToFilter = _commandsToFilter(); { + auto pattern = std::make_shared(fzf::matcher::ParsePattern(searchText)); + for (const auto& action : commandsToFilter) { // Update filter for all commands // This will modify the highlighting but will also lead to re-computation of weight (and consequently sorting). // Pay attention that it already updates the highlighting in the UI - action.UpdateFilter(searchText); + auto impl = winrt::get_self(action); + impl->UpdateFilter(pattern); // if there is active search we skip commands with 0 weight if (searchText.empty() || action.Weight() > 0) diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 0896bc114a..ac4357554c 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -138,6 +138,7 @@ + @@ -212,6 +213,7 @@ TabBase.idl + TaskbarState.idl diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters index 12b3fa2add..6dbf62453d 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters @@ -41,6 +41,9 @@ highlightedText + + fzf + @@ -77,6 +80,12 @@ highlightedText + + fzf + + + fzf + @@ -176,6 +185,9 @@ {e490f626-547d-4b5b-b22d-c6d33c9e3210} + + {e4588ff4-c80a-40f7-be57-3e81f570a93d} + diff --git a/src/cascadia/TerminalApp/fzf/LICENSE b/src/cascadia/TerminalApp/fzf/LICENSE new file mode 100644 index 0000000000..04ac2144c3 --- /dev/null +++ b/src/cascadia/TerminalApp/fzf/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2013-2024 Junegunn Choi +Copyright (c) 2021-2025 Simon Hauser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/cascadia/TerminalApp/fzf/fzf.cpp b/src/cascadia/TerminalApp/fzf/fzf.cpp new file mode 100644 index 0000000000..6f2ac764cf --- /dev/null +++ b/src/cascadia/TerminalApp/fzf/fzf.cpp @@ -0,0 +1,432 @@ +#include "pch.h" +#include "fzf.h" + +#undef CharLower +#undef CharUpper + +using namespace fzf::matcher; + +constexpr int16_t ScoreMatch = 16; +constexpr int16_t ScoreGapStart = -3; +constexpr int16_t ScoreGapExtension = -1; +constexpr int16_t BoundaryBonus = ScoreMatch / 2; +constexpr int16_t NonWordBonus = ScoreMatch / 2; +constexpr int16_t CamelCaseBonus = BoundaryBonus + ScoreGapExtension; +constexpr int16_t BonusConsecutive = -(ScoreGapStart + ScoreGapExtension); +constexpr int16_t BonusFirstCharMultiplier = 2; +constexpr size_t npos = std::numeric_limits::max(); + +enum class CharClass : uint8_t +{ + NonWord = 0, + CharLower = 1, + CharUpper = 2, + Digit = 3, +}; + +static std::vector utf16ToUtf32(std::wstring_view text) +{ + const UChar* data = reinterpret_cast(text.data()); + int32_t dataLen = static_cast(text.size()); + int32_t cpCount = u_countChar32(data, dataLen); + + std::vector out(cpCount); + + UErrorCode status = U_ZERO_ERROR; + u_strToUTF32(out.data(), static_cast(out.size()), nullptr, data, dataLen, &status); + THROW_HR_IF(E_UNEXPECTED, status > U_ZERO_ERROR); + + return out; +} + +static void foldStringUtf32(std::vector& str) +{ + for (auto& cp : str) + { + cp = u_foldCase(cp, U_FOLD_CASE_DEFAULT); + } +} + +static size_t trySkip(const std::vector& input, const UChar32 searchChar, size_t startIndex) +{ + for (size_t i = startIndex; i < input.size(); ++i) + { + if (input[i] == searchChar) + { + return i; + } + } + return npos; +} + +// Unlike the equivalent in fzf, this one does more than Unicode. +static size_t asciiFuzzyIndex(const std::vector& input, const std::vector& pattern) +{ + size_t idx = 0; + size_t firstIdx = 0; + for (size_t pi = 0; pi < pattern.size(); ++pi) + { + idx = trySkip(input, pattern[pi], idx); + if (idx == npos) + { + return npos; + } + + if (pi == 0 && idx > 0) + { + firstIdx = idx - 1; + } + + idx++; + } + return firstIdx; +} + +static int16_t calculateBonus(CharClass prevClass, CharClass currentClass) +{ + if (prevClass == CharClass::NonWord && currentClass != CharClass::NonWord) + { + return BoundaryBonus; + } + if ((prevClass == CharClass::CharLower && currentClass == CharClass::CharUpper) || + (prevClass != CharClass::Digit && currentClass == CharClass::Digit)) + { + return CamelCaseBonus; + } + if (currentClass == CharClass::NonWord) + { + return NonWordBonus; + } + return 0; +} + +static constexpr auto s_charClassLut = []() { + std::array lut{}; + lut.fill(CharClass::NonWord); + lut[U_UPPERCASE_LETTER] = CharClass::CharUpper; + lut[U_LOWERCASE_LETTER] = CharClass::CharLower; + lut[U_MODIFIER_LETTER] = CharClass::CharLower; + lut[U_OTHER_LETTER] = CharClass::CharLower; + lut[U_DECIMAL_DIGIT_NUMBER] = CharClass::Digit; + return lut; +}(); + +static CharClass classOf(UChar32 ch) +{ + return s_charClassLut[u_charType(ch)]; +} + +static int32_t fzfFuzzyMatchV2(const std::vector& text, const std::vector& pattern, std::vector* pos) +{ + if (pattern.size() == 0) + { + return 0; + } + + auto foldedText = text; + foldStringUtf32(foldedText); + + size_t firstIndexOf = asciiFuzzyIndex(foldedText, pattern); + if (firstIndexOf == npos) + { + return 0; + } + + auto initialScores = std::vector(text.size()); + auto consecutiveScores = std::vector(text.size()); + auto firstOccurrenceOfEachChar = std::vector(pattern.size()); + auto bonusesSpan = std::vector(text.size()); + + int16_t maxScore = 0; + size_t maxScorePos = 0; + size_t patternIndex = 0; + size_t lastIndex = 0; + UChar32 firstPatternChar = pattern[0]; + UChar32 currentPatternChar = pattern[0]; + int16_t previousInitialScore = 0; + CharClass previousClass = CharClass::NonWord; + bool inGap = false; + + std::span lowerText(foldedText); + auto lowerTextSlice = lowerText.subspan(firstIndexOf); + auto initialScoresSlice = std::span(initialScores).subspan(firstIndexOf); + auto consecutiveScoresSlice = std::span(consecutiveScores).subspan(firstIndexOf); + auto bonusesSlice = std::span(bonusesSpan).subspan(firstIndexOf, text.size() - firstIndexOf); + + for (size_t i = 0; i < lowerTextSlice.size(); i++) + { + const auto currentChar = lowerTextSlice[i]; + const auto currentClass = classOf(text[i + firstIndexOf]); + const auto bonus = calculateBonus(previousClass, currentClass); + bonusesSlice[i] = bonus; + previousClass = currentClass; + + //currentPatternChar was already folded in ParsePattern + if (currentChar == currentPatternChar) + { + if (patternIndex < pattern.size()) + { + firstOccurrenceOfEachChar[patternIndex] = firstIndexOf + i; + patternIndex++; + if (patternIndex < pattern.size()) + { + currentPatternChar = pattern[patternIndex]; + } + } + lastIndex = firstIndexOf + i; + } + if (currentChar == firstPatternChar) + { + int16_t score = ScoreMatch + bonus * BonusFirstCharMultiplier; + initialScoresSlice[i] = score; + consecutiveScoresSlice[i] = 1; + if (pattern.size() == 1 && (score > maxScore)) + { + maxScore = score; + maxScorePos = firstIndexOf + i; + if (bonus == BoundaryBonus) + { + break; + } + } + inGap = false; + } + else + { + initialScoresSlice[i] = std::max(previousInitialScore + (inGap ? ScoreGapExtension : ScoreGapStart), 0); + consecutiveScoresSlice[i] = 0; + inGap = true; + } + previousInitialScore = initialScoresSlice[i]; + } + + if (patternIndex != pattern.size()) + { + return 0; + } + + if (pattern.size() == 1) + { + if (pos) + { + pos->push_back(maxScorePos); + } + return maxScore; + } + + const auto firstOccurrenceOfFirstChar = firstOccurrenceOfEachChar[0]; + const auto width = lastIndex - firstOccurrenceOfFirstChar + 1; + const auto rows = pattern.size(); + auto consecutiveCharMatrixSize = width * pattern.size(); + + std::vector scoreMatrix(width * rows); + std::copy_n(initialScores.begin() + firstOccurrenceOfFirstChar, width, scoreMatrix.begin()); + std::span scoreSpan(scoreMatrix); + + std::vector consecutiveCharMatrix(width * rows); + std::copy_n(consecutiveScores.begin() + firstOccurrenceOfFirstChar, width, consecutiveCharMatrix.begin()); + std::span consecutiveCharMatrixSpan(consecutiveCharMatrix); + + auto patternSliceStr = std::span(pattern).subspan(1); + + for (size_t off = 0; off < pattern.size() - 1; off++) + { + auto patternCharOffset = firstOccurrenceOfEachChar[off + 1]; + auto sliceLen = lastIndex - patternCharOffset + 1; + currentPatternChar = patternSliceStr[off]; + patternIndex = off + 1; + auto row = patternIndex * width; + inGap = false; + std::span textSlice = lowerText.subspan(patternCharOffset, sliceLen); + std::span bonusSlice(bonusesSpan.begin() + patternCharOffset, textSlice.size()); + std::span consecutiveCharMatrixSlice = consecutiveCharMatrixSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar, textSlice.size()); + std::span consecutiveCharMatrixDiagonalSlice = consecutiveCharMatrixSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar - 1 - width, textSlice.size()); + std::span scoreMatrixSlice = scoreSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar, textSlice.size()); + std::span scoreMatrixDiagonalSlice = scoreSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar - 1 - width, textSlice.size()); + std::span scoreMatrixLeftSlice = scoreSpan.subspan(row + patternCharOffset - firstOccurrenceOfFirstChar - 1, textSlice.size()); + + if (!scoreMatrixLeftSlice.empty()) + { + scoreMatrixLeftSlice[0] = 0; + } + + for (size_t j = 0; j < textSlice.size(); j++) + { + const auto currentChar = textSlice[j]; + const auto column = patternCharOffset + j; + const int16_t score = inGap ? scoreMatrixLeftSlice[j] + ScoreGapExtension : scoreMatrixLeftSlice[j] + ScoreGapStart; + int16_t diagonalScore = 0; + int16_t consecutive = 0; + if (currentChar == currentPatternChar) + { + diagonalScore = scoreMatrixDiagonalSlice[j] + ScoreMatch; + int16_t bonus = bonusSlice[j]; + consecutive = consecutiveCharMatrixDiagonalSlice[j] + 1; + if (bonus == BoundaryBonus) + { + consecutive = 1; + } + else if (consecutive > 1) + { + bonus = std::max({ bonus, BonusConsecutive, (bonusesSpan[column - consecutive + 1]) }); + } + if (diagonalScore + bonus < score) + { + diagonalScore += bonusSlice[j]; + consecutive = 0; + } + else + { + diagonalScore += bonus; + } + } + consecutiveCharMatrixSlice[j] = consecutive; + inGap = (diagonalScore < score); + int16_t cellScore = std::max(int16_t{ 0 }, std::max(diagonalScore, score)); + if (off + 2 == pattern.size() && cellScore > maxScore) + { + maxScore = cellScore; + maxScorePos = column; + } + scoreMatrixSlice[j] = cellScore; + } + } + + size_t currentColIndex = maxScorePos; + if (pos) + { + patternIndex = pattern.size() - 1; + bool preferCurrentMatch = true; + while (true) + { + const auto rowStartIndex = patternIndex * width; + const auto colOffset = currentColIndex - firstOccurrenceOfFirstChar; + const auto cellScore = scoreMatrix[rowStartIndex + colOffset]; + int32_t diagonalCellScore = 0; + int32_t leftCellScore = 0; + + if (patternIndex > 0 && currentColIndex >= firstOccurrenceOfEachChar[patternIndex]) + { + diagonalCellScore = scoreMatrix[rowStartIndex - width + colOffset - 1]; + } + if (currentColIndex > firstOccurrenceOfEachChar[patternIndex]) + { + leftCellScore = scoreMatrix[rowStartIndex + colOffset - 1]; + } + + if (cellScore > diagonalCellScore && + (cellScore > leftCellScore || (cellScore == leftCellScore && preferCurrentMatch))) + { + pos->push_back(currentColIndex); + if (patternIndex == 0) + { + break; + } + patternIndex--; + } + + currentColIndex--; + if (rowStartIndex + colOffset >= consecutiveCharMatrixSize) + { + break; + } + + preferCurrentMatch = (consecutiveCharMatrix[rowStartIndex + colOffset] > 1) || + ((rowStartIndex + width + colOffset + 1 < + consecutiveCharMatrixSize) && + (consecutiveCharMatrix[rowStartIndex + width + colOffset + 1] > 0)); + } + } + return maxScore; +} + +Pattern fzf::matcher::ParsePattern(const std::wstring_view patternStr) +{ + Pattern patObj; + size_t pos = 0; + + while (true) + { + const auto beg = patternStr.find_first_not_of(L' ', pos); + if (beg == std::wstring_view::npos) + { + break; // No more non-space characters + } + + const auto end = std::min(patternStr.size(), patternStr.find_first_of(L' ', beg)); + const auto word = patternStr.substr(beg, end - beg); + auto codePoints = utf16ToUtf32(word); + foldStringUtf32(codePoints); + patObj.terms.push_back(std::move(codePoints)); + pos = end; + } + + return patObj; +} + +std::optional fzf::matcher::Match(std::wstring_view text, const Pattern& pattern) +{ + if (pattern.terms.empty()) + { + return MatchResult{}; + } + + const auto textCodePoints = utf16ToUtf32(text); + + int32_t totalScore = 0; + std::vector allUtf32Pos; + + for (const auto& term : pattern.terms) + { + std::vector termPos; + auto score = fzfFuzzyMatchV2(textCodePoints, term, &termPos); + if (score <= 0) + { + return std::nullopt; + } + + totalScore += score; + allUtf32Pos.insert(allUtf32Pos.end(), termPos.begin(), termPos.end()); + } + + std::ranges::sort(allUtf32Pos); + allUtf32Pos.erase(std::ranges::unique(allUtf32Pos).begin(), allUtf32Pos.end()); + + std::vector runs; + std::size_t nextCodePointPos = 0; + size_t utf16Offset = 0; + + bool inRun = false; + size_t runStart = 0; + + for (size_t cpIndex = 0; cpIndex < textCodePoints.size(); cpIndex++) + { + const auto cp = textCodePoints[cpIndex]; + const size_t cpWidth = U16_LENGTH(cp); + + const bool isMatch = (nextCodePointPos < allUtf32Pos.size() && allUtf32Pos[nextCodePointPos] == cpIndex); + if (isMatch) + { + if (!inRun) + { + runStart = utf16Offset; + inRun = true; + } + nextCodePointPos++; + } + else if (inRun) + { + runs.push_back({ runStart, utf16Offset - 1 }); + inRun = false; + } + + utf16Offset += cpWidth; + } + + if (inRun) + { + runs.push_back({ runStart, utf16Offset - 1 }); + } + + return MatchResult{ totalScore, std::move(runs) }; +} diff --git a/src/cascadia/TerminalApp/fzf/fzf.h b/src/cascadia/TerminalApp/fzf/fzf.h new file mode 100644 index 0000000000..2af4b75f91 --- /dev/null +++ b/src/cascadia/TerminalApp/fzf/fzf.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace fzf::matcher +{ + struct TextRun + { + size_t Start; + size_t End; + }; + + struct MatchResult + { + int32_t Score = 0; + std::vector Runs; + }; + + struct Pattern + { + std::vector> terms; + }; + + Pattern ParsePattern(std::wstring_view patternStr); + std::optional Match(std::wstring_view text, const Pattern& pattern); +} diff --git a/src/cascadia/ut_app/FzfTests.cpp b/src/cascadia/ut_app/FzfTests.cpp new file mode 100644 index 0000000000..e4f1fd1903 --- /dev/null +++ b/src/cascadia/ut_app/FzfTests.cpp @@ -0,0 +1,568 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" +#include "..\TerminalApp\fzf\fzf.h" + +using namespace Microsoft::Console; +using namespace WEX::Logging; +using namespace WEX::TestExecution; +using namespace WEX::Common; + +namespace TerminalAppUnitTests +{ + typedef enum + { + ScoreMatch = 16, + ScoreGapStart = -3, + ScoreGapExtension = -1, + BonusBoundary = ScoreMatch / 2, + BonusNonWord = ScoreMatch / 2, + BonusCamel123 = BonusBoundary + ScoreGapExtension, + BonusConsecutive = -(ScoreGapStart + ScoreGapExtension), + BonusFirstCharMultiplier = 2, + } score_t; + + class FzfTests + { + BEGIN_TEST_CLASS(FzfTests) + END_TEST_CLASS() + + TEST_METHOD(AllPatternCharsDoNotMatch); + TEST_METHOD(ConsecutiveChars); + TEST_METHOD(ConsecutiveChars_FirstCharBonus); + TEST_METHOD(NonWordBonusBoundary_ConsecutiveChars); + TEST_METHOD(MatchOnNonWordChars_CaseInSensitive); + TEST_METHOD(MatchOnNonWordCharsWithGap); + TEST_METHOD(BonusForCamelCaseMatch); + TEST_METHOD(BonusBoundaryAndFirstCharMultiplier); + TEST_METHOD(MatchesAreCaseInSensitive); + TEST_METHOD(MultipleTerms); + TEST_METHOD(MultipleTerms_AllCharsMatch); + TEST_METHOD(MultipleTerms_NotAllTermsMatch); + TEST_METHOD(MatchesAreCaseInSensitive_BonusBoundary); + TEST_METHOD(TraceBackWillPickTheFirstMatchIfBothHaveTheSameScore); + TEST_METHOD(TraceBackWillPickTheMatchWithTheHighestScore); + TEST_METHOD(TraceBackWillPickTheMatchWithTheHighestScore_Gaps); + TEST_METHOD(TraceBackWillPickEarlierCharsWhenNoBonus); + TEST_METHOD(MatchWithGapCanAHaveHigherScoreThanConsecutiveWhenGapMatchHasBoundaryBonus); + TEST_METHOD(ConsecutiveMatchWillScoreHigherThanMatchWithGapWhenBothHaveFirstCharBonus); + TEST_METHOD(ConsecutiveMatchWillScoreHigherThanMatchWithGapWhenBothDontHaveBonus); + TEST_METHOD(MatchWithGapCanHaveHigherScoreThanConsecutiveWhenGapHasFirstCharBonus); + TEST_METHOD(MatchWithGapThatMatchesOnTheFirstCharWillNoLongerScoreHigherThanConsecutiveCharsWhenTheGapIs3_NoConsecutiveChar_4CharPattern); + TEST_METHOD(MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs11_2CharPattern); + TEST_METHOD(MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs11_3CharPattern_1ConsecutiveChar); + TEST_METHOD(MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs5_NoConsecutiveChars_3CharPattern); + TEST_METHOD(Russian_CaseMisMatch); + TEST_METHOD(Russian_CaseMatch); + TEST_METHOD(English_CaseMatch); + TEST_METHOD(English_CaseMisMatch); + TEST_METHOD(SurrogatePair); + TEST_METHOD(French_CaseMatch); + TEST_METHOD(French_CaseMisMatch); + TEST_METHOD(German_CaseMatch); + TEST_METHOD(German_CaseMisMatch_FoldResultsInMultipleCodePoints); + TEST_METHOD(Greek_CaseMisMatch); + TEST_METHOD(Greek_CaseMatch); + TEST_METHOD(SurrogatePair_ToUtf16Pos_ConsecutiveChars); + TEST_METHOD(SurrogatePair_ToUtf16Pos_PreferConsecutiveChars); + TEST_METHOD(SurrogatePair_ToUtf16Pos_GapAndBoundary); + }; + + void AssertScoreAndRuns(std::wstring_view patternText, std::wstring_view text, int expectedScore, const std::vector& expectedRuns) + { + const auto pattern = fzf::matcher::ParsePattern(patternText); + const auto match = fzf::matcher::Match(text, pattern); + + if (expectedScore == 0 && expectedRuns.empty()) + { + VERIFY_ARE_EQUAL(std::nullopt, match); + return; + } + + VERIFY_IS_TRUE(match.has_value()); + VERIFY_ARE_EQUAL(expectedScore, match->Score); + + const auto& runs = match->Runs; + VERIFY_ARE_EQUAL(expectedRuns.size(), runs.size()); + + for (size_t i = 0; i < expectedRuns.size(); ++i) + { + VERIFY_ARE_EQUAL(expectedRuns[i].Start, runs[i].Start); + VERIFY_ARE_EQUAL(expectedRuns[i].End, runs[i].End); + } + } + + void FzfTests::AllPatternCharsDoNotMatch() + { + AssertScoreAndRuns( + L"fbb", + L"foo bar", + 0, + {}); + } + + void FzfTests::ConsecutiveChars() + { + AssertScoreAndRuns( + L"oba", + L"foobar", + ScoreMatch * 3 + BonusConsecutive * 2, + { { 2, 4 } }); + } + + void FzfTests::ConsecutiveChars_FirstCharBonus() + { + AssertScoreAndRuns( + L"foo", + L"foobar", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 2, + { { 0, 2 } }); + } + + void FzfTests::NonWordBonusBoundary_ConsecutiveChars() + { + AssertScoreAndRuns( + L"zshc", + L"/man1/zshcompctl.1", + ScoreMatch * 4 + BonusBoundary * BonusFirstCharMultiplier + BonusFirstCharMultiplier * BonusConsecutive * 3, + { { 6, 9 } }); + } + + void FzfTests::Russian_CaseMisMatch() + { + AssertScoreAndRuns( + L"новая", + L"Новая вкладка", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::Russian_CaseMatch() + { + AssertScoreAndRuns( + L"Новая", + L"Новая вкладка", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::German_CaseMatch() + { + AssertScoreAndRuns( + L"fuß", + L"Fußball", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 2, + { { 0, 2 } }); + } + + void FzfTests::German_CaseMisMatch_FoldResultsInMultipleCodePoints() + { + //This doesn't currently pass, I think ucase_toFullFolding would give the number of code points that resulted from the fold. + //I wasn't sure how to reference that + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"Ignore", L"true") + END_TEST_METHOD_PROPERTIES() + + AssertScoreAndRuns( + L"fuss", + L"Fußball", + //I think ScoreMatch * 4 is correct in this case since it matches 4 codepoints pattern??? fuss + ScoreMatch * 4 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 3, + //Only 3 positions in the text were matched + { { 0, 2 } }); + } + + void FzfTests::French_CaseMatch() + { + AssertScoreAndRuns( + L"Éco", + L"École", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 2, + { { 0, 2 } }); + } + + void FzfTests::French_CaseMisMatch() + { + AssertScoreAndRuns( + L"Éco", + L"école", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 2, + { { 0, 2 } }); + } + + void FzfTests::Greek_CaseMatch() + { + AssertScoreAndRuns( + L"λόγος", + L"λόγος", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::Greek_CaseMisMatch() + { + //I think this tests validates folding (σ, ς) + AssertScoreAndRuns( + L"λόγοσ", + L"λόγος", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::English_CaseMatch() + { + AssertScoreAndRuns( + L"Newer", + L"Newer tab", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::English_CaseMisMatch() + { + AssertScoreAndRuns( + L"newer", + L"Newer tab", + ScoreMatch * 5 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 4, + { { 0, 4 } }); + } + + void FzfTests::SurrogatePair() + { + AssertScoreAndRuns( + L"N😀ewer", + L"N😀ewer tab", + ScoreMatch * 6 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 5, + { { 0, 6 } }); + } + + void FzfTests::SurrogatePair_ToUtf16Pos_ConsecutiveChars() + { + AssertScoreAndRuns( + L"N𠀋N😀𝄞e𐐷", + L"N𠀋N😀𝄞e𐐷 tab", + ScoreMatch * 7 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 6, + { { 0, 10 } }); + } + + void FzfTests::SurrogatePair_ToUtf16Pos_PreferConsecutiveChars() + { + AssertScoreAndRuns( + L"𠀋😀", + L"N𠀋😀wer 😀b𐐷 ", + ScoreMatch * 2 + BonusConsecutive * 2, + { { 1, 4 } }); + } + + void FzfTests::SurrogatePair_ToUtf16Pos_GapAndBoundary() + { + AssertScoreAndRuns( + L"𠀋😀", + L"N𠀋wer 😀b𐐷 ", + ScoreMatch * 2 + ScoreGapStart + ScoreGapExtension * 3 + BonusBoundary, + { { 1, 2 }, { 7, 8 } }); + } + + void FzfTests::MatchOnNonWordChars_CaseInSensitive() + { + AssertScoreAndRuns( + L"foo-b", + L"xFoo-Bar Baz", + (ScoreMatch + BonusCamel123 * BonusFirstCharMultiplier) + + (ScoreMatch + BonusCamel123) + + (ScoreMatch + BonusCamel123) + + (ScoreMatch + BonusBoundary) + + (ScoreMatch + BonusNonWord), + { { 1, 5 } }); + } + + void FzfTests::MatchOnNonWordCharsWithGap() + { + AssertScoreAndRuns( + L"12356", + L"abc123 456", + (ScoreMatch + BonusCamel123 * BonusFirstCharMultiplier) + + (ScoreMatch + BonusCamel123) + + (ScoreMatch + BonusCamel123) + + ScoreGapStart + + ScoreGapExtension + + ScoreMatch + + ScoreMatch + BonusConsecutive, + { { 3, 5 }, { 8, 9 } }); + } + + void FzfTests::BonusForCamelCaseMatch() + { + AssertScoreAndRuns( + L"def56", + L"abcDEF 456", + (ScoreMatch + BonusCamel123 * BonusFirstCharMultiplier) + + (ScoreMatch + BonusCamel123) + + (ScoreMatch + BonusCamel123) + + ScoreGapStart + + ScoreGapExtension + + ScoreMatch + + (ScoreMatch + BonusConsecutive), + { { 3, 5 }, { 8, 9 } }); + } + + void FzfTests::BonusBoundaryAndFirstCharMultiplier() + { + AssertScoreAndRuns( + L"fbb", + L"foo bar baz", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusBoundary * 2 + 2 * ScoreGapStart + 4 * ScoreGapExtension, + { { 0, 0 }, { 4, 4 }, { 8, 8 } }); + } + + void FzfTests::MatchesAreCaseInSensitive() + { + AssertScoreAndRuns( + L"FBB", + L"foo bar baz", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusBoundary * 2 + 2 * ScoreGapStart + 4 * ScoreGapExtension, + { { 0, 0 }, { 4, 4 }, { 8, 8 } }); + } + + void FzfTests::MultipleTerms() + { + auto term1Score = ScoreMatch * 2 + BonusBoundary * BonusFirstCharMultiplier + (BonusFirstCharMultiplier * BonusConsecutive); + auto term2Score = ScoreMatch * 4 + BonusBoundary * BonusFirstCharMultiplier + (BonusFirstCharMultiplier * BonusConsecutive) * 3; + + AssertScoreAndRuns( + L"sp anta", + L"Split Pane, split: horizontal, profile: SSH: Antares", + term1Score + term2Score, + { { 0, 1 }, { 45, 48 } }); + } + + void FzfTests::MultipleTerms_AllCharsMatch() + { + auto term1Score = ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + (BonusFirstCharMultiplier * BonusConsecutive * 2); + auto term2Score = term1Score; + + AssertScoreAndRuns( + L"foo bar", + L"foo bar", + term1Score + term2Score, + { { 0, 2 }, { 4, 6 } }); + } + + void FzfTests::MultipleTerms_NotAllTermsMatch() + { + AssertScoreAndRuns( + L"sp anta zz", + L"Split Pane, split: horizontal, profile: SSH: Antares", + 0, + {}); + } + + void FzfTests::MatchesAreCaseInSensitive_BonusBoundary() + { + AssertScoreAndRuns( + L"fbb", + L"Foo Bar Baz", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusBoundary * 2 + 2 * ScoreGapStart + 4 * ScoreGapExtension, + { { 0, 0 }, { 4, 4 }, { 8, 8 } }); + } + + void FzfTests::TraceBackWillPickTheFirstMatchIfBothHaveTheSameScore() + { + AssertScoreAndRuns( + L"bar", + L"Foo Bar Bar", + (ScoreMatch + BonusBoundary * BonusFirstCharMultiplier) + + (ScoreMatch + BonusBoundary) + + (ScoreMatch + BonusBoundary), + //ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier * 2, + { { 4, 6 } }); + } + + void FzfTests::TraceBackWillPickTheMatchWithTheHighestScore() + { + AssertScoreAndRuns( + L"bar", + L"Foo aBar Bar", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier * 2, + { { 9, 11 } }); + } + + void FzfTests::TraceBackWillPickTheMatchWithTheHighestScore_Gaps() + { + AssertScoreAndRuns( + L"bar", + L"Boo Author Raz Bar", + ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive * BonusFirstCharMultiplier * 2, + { { 15, 17 } }); + } + + void FzfTests::TraceBackWillPickEarlierCharsWhenNoBonus() + { + AssertScoreAndRuns( + L"clts", + L"close all tabs after this", + ScoreMatch * 4 + BonusBoundary * BonusFirstCharMultiplier + BonusFirstCharMultiplier * BonusConsecutive + ScoreGapStart + ScoreGapExtension * 7 + BonusBoundary + ScoreGapStart + ScoreGapExtension, + { { 0, 1 }, { 10, 10 }, { 13, 13 } }); + } + + void FzfTests::ConsecutiveMatchWillScoreHigherThanMatchWithGapWhenBothDontHaveBonus() + { + auto consecutiveScore = ScoreMatch * 3 + BonusConsecutive * 2; + auto gapScore = (ScoreMatch * 3) + ScoreGapStart + ScoreGapStart; + + AssertScoreAndRuns( + L"oob", + L"aoobar", + consecutiveScore, + { { 1, 3 } }); + + AssertScoreAndRuns( + L"oob", + L"aoaoabound", + gapScore, + { { 1, 1 }, { 3, 3 }, { 5, 5 } }); + + VERIFY_IS_GREATER_THAN(consecutiveScore, gapScore); + } + + void FzfTests::ConsecutiveMatchWillScoreHigherThanMatchWithGapWhenBothHaveFirstCharBonus() + { + auto consecutiveScore = ScoreMatch * 3 + BonusFirstCharMultiplier * BonusBoundary + BonusFirstCharMultiplier * BonusConsecutive * 2; + auto gapScore = (ScoreMatch * 3) + (BonusBoundary * BonusFirstCharMultiplier) + ScoreGapStart + ScoreGapStart; + + AssertScoreAndRuns( + L"oob", + L"oobar", + consecutiveScore, + { { 0, 2 } }); + + AssertScoreAndRuns( + L"oob", + L"oaoabound", + gapScore, + { { 0, 0 }, { 2, 2 }, { 4, 4 } }); + + VERIFY_IS_GREATER_THAN(consecutiveScore, gapScore); + } + + void FzfTests::MatchWithGapCanAHaveHigherScoreThanConsecutiveWhenGapMatchHasBoundaryBonus() + { + auto consecutiveScore = ScoreMatch * 3 + BonusConsecutive * 2; + auto gapScore = (ScoreMatch * 3) + (BonusBoundary * BonusFirstCharMultiplier) + (BonusBoundary * 2) + ScoreGapStart + (ScoreGapExtension * 2) + ScoreGapStart + ScoreGapExtension; + + AssertScoreAndRuns( + L"oob", + L"foobar", + consecutiveScore, + { { 1, 3 } }); + + AssertScoreAndRuns( + L"oob", + L"out-of-bound", + gapScore, + { { 0, 0 }, { 4, 4 }, { 7, 7 } }); + + VERIFY_IS_GREATER_THAN(gapScore, consecutiveScore); + } + + void FzfTests::MatchWithGapCanHaveHigherScoreThanConsecutiveWhenGapHasFirstCharBonus() + { + auto consecutiveScore = ScoreMatch * 2 + BonusConsecutive; + auto gapScore = ScoreMatch * 2 + BonusBoundary * BonusFirstCharMultiplier + ScoreGapStart; + + AssertScoreAndRuns( + L"ob", + L"aobar", + consecutiveScore, + { { 1, 2 } }); + + AssertScoreAndRuns( + L"ob", + L"oabar", + gapScore, + { { 0, 0 }, { 2, 2 } }); + + VERIFY_IS_GREATER_THAN(gapScore, consecutiveScore); + } + + void FzfTests::MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs11_2CharPattern() + { + auto consecutiveScore = ScoreMatch * 2 + BonusConsecutive; + auto gapScore = ScoreMatch * 2 + BonusBoundary * BonusFirstCharMultiplier + ScoreGapStart + ScoreGapExtension * 10; + + AssertScoreAndRuns( + L"ob", + L"aobar", + consecutiveScore, + { { 1, 2 } }); + + AssertScoreAndRuns( + L"ob", + L"oaaaaaaaaaaabar", + gapScore, + { { 0, 0 }, { 12, 12 } }); + + VERIFY_IS_GREATER_THAN(consecutiveScore, gapScore); + } + + void FzfTests::MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs11_3CharPattern_1ConsecutiveChar() + { + auto consecutiveScore = ScoreMatch * 3 + BonusConsecutive * 2; + auto gapScore = ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + BonusConsecutive + ScoreGapStart + ScoreGapExtension * 10; + + AssertScoreAndRuns( + L"oba", + L"aobar", + consecutiveScore, + { { 1, 3 } }); + + AssertScoreAndRuns( + L"oba", + L"oaaaaaaaaaaabar", + gapScore, + { { 0, 0 }, { 12, 13 } }); + + VERIFY_IS_GREATER_THAN(consecutiveScore, gapScore); + } + + void FzfTests::MatchWithGapThatMatchesOnTheFirstCharWillNoLongerHigherScoreThanConsecutiveCharsWhenTheGapIs5_NoConsecutiveChars_3CharPattern() + { + auto allConsecutiveScore = ScoreMatch * 3 + BonusConsecutive * 2; + auto allBoundaryWithGapScore = ScoreMatch * 3 + BonusBoundary * BonusFirstCharMultiplier + ScoreGapStart + ScoreGapExtension + ScoreGapExtension + ScoreGapStart + ScoreGapExtension; + + AssertScoreAndRuns( + L"oba", + L"aobar", + allConsecutiveScore, + { { 1, 3 } }); + + AssertScoreAndRuns( + L"oba", + L"oaaabzzar", + allBoundaryWithGapScore, + { { 0, 0 }, { 4, 4 }, { 7, 7 } }); + + VERIFY_IS_GREATER_THAN(allConsecutiveScore, allBoundaryWithGapScore); + } + + void FzfTests::MatchWithGapThatMatchesOnTheFirstCharWillNoLongerScoreHigherThanConsecutiveCharsWhenTheGapIs3_NoConsecutiveChar_4CharPattern() + { + auto consecutiveScore = ScoreMatch * 4 + BonusConsecutive * 3; + auto gapScore = ScoreMatch * 4 + BonusBoundary * BonusFirstCharMultiplier + ScoreGapStart * 3; + + AssertScoreAndRuns( + L"obar", + L"aobar", + consecutiveScore, + { { 1, 4 } }); + + AssertScoreAndRuns( + L"obar", + L"oabzazr", + gapScore, + { { 0, 0 }, { 2, 2 }, { 4, 4 }, { 6, 6 } }); + + VERIFY_IS_GREATER_THAN(consecutiveScore, gapScore); + } +} diff --git a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj index 705d5e3592..608bce64b6 100644 --- a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj +++ b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj @@ -24,6 +24,7 @@ + Create diff --git a/src/inc/til.h b/src/inc/til.h index c79b0debdc..531e571f75 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -57,7 +57,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" template as_view_t safe_slice_abs(const T& view, size_t beg, size_t end) { - const auto len = view.size(); + const size_t len = view.size(); end = std::min(end, len); beg = std::min(beg, end); return { view.data() + beg, end - beg }; @@ -66,7 +66,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" template as_view_t safe_slice_len(const T& view, size_t start, size_t count) { - const auto len = view.size(); + const size_t len = view.size(); start = std::min(start, len); count = std::min(count, len - start); return { view.data() + start, count }; diff --git a/src/inc/til/type_traits.h b/src/inc/til/type_traits.h index 1f426155eb..f38380ada9 100644 --- a/src/inc/til/type_traits.h +++ b/src/inc/til/type_traits.h @@ -3,6 +3,11 @@ #pragma once +namespace winrt +{ + struct hstring; +} + namespace til { namespace details @@ -50,6 +55,12 @@ namespace til { using type = std::basic_string_view; }; + + template<> + struct as_view + { + using type = std::wstring_view; + }; } template From 155d8a9ab264742d4bbaf243b922835417fe9901 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 3 Jun 2025 17:38:15 +0200 Subject: [PATCH 07/74] Tweak spacing on about dialog (#18993) Some minor styling tweaks to the about page. - Tweaking the spacing of the version-checking / update available text. - Wrapping the hyperlinkbuttons in a StackPanel so it's easier to set a negative margin so they are visually aligned with the version / title text. Closes #18994 --- src/cascadia/TerminalApp/AboutDialog.xaml | 31 +++++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/cascadia/TerminalApp/AboutDialog.xaml b/src/cascadia/TerminalApp/AboutDialog.xaml index b9aebf9a9b..ff075efb9a 100644 --- a/src/cascadia/TerminalApp/AboutDialog.xaml +++ b/src/cascadia/TerminalApp/AboutDialog.xaml @@ -28,18 +28,18 @@ - - + - @@ -51,15 +51,18 @@ - - - - - + + + + + + + From 4abc041eb78902708e1844902e40a88d0bc3d50a Mon Sep 17 00:00:00 2001 From: James Holderness Date: Tue, 10 Jun 2025 21:09:51 +0100 Subject: [PATCH 08/74] Add support for OSC 52 clipboard copy in conhost (#18949) This adds support for copying to the clipboard in conhost using the OSC 52 escape sequence, extending the original implementation which was for Windows Terminal only. The Windows Terminal implementation was added in PR #5823. Because the clipboard can't be accessed from a background thread, this works by saving the content in a global variable, and then posting a custom message to the main GUI thread, which takes care of the actual copy operation. Validation: I've manually confirmed that tmux copy mode is now able to copy to the system clipboard. Closes #18943 --- src/host/consoleInformation.cpp | 27 +++++++++++++++++++ src/host/outputStream.cpp | 4 +-- src/host/server.h | 3 +++ src/interactivity/win32/Clipboard.cpp | 16 +++++++++++ .../win32/CustomWindowMessages.h | 2 ++ src/interactivity/win32/clipboard.hpp | 1 + src/interactivity/win32/windowproc.cpp | 9 +++++++ 7 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index d4e5926f6c..7146bee438 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -13,6 +13,7 @@ #include "srvinit.h" #include "../interactivity/inc/ServiceLocator.hpp" +#include "../interactivity/win32/CustomWindowMessages.h" #include "../types/inc/convert.hpp" using Microsoft::Console::Interactivity::ServiceLocator; @@ -179,6 +180,32 @@ void CONSOLE_INFORMATION::SetBracketedPasteMode(const bool enabled) noexcept _bracketedPasteMode = enabled; } +void CONSOLE_INFORMATION::CopyTextToClipboard(const std::wstring_view text) +{ + const auto window = ServiceLocator::LocateConsoleWindow(); + if (window) + { + // The clipboard can only be updated from the main GUI thread, so we + // need to post a message to trigger the actual copy operation. But if + // the pending clipboard content is already set, a message would have + // already been posted, so there's no need to post another one. + const auto clipboardMessageSent = _pendingClipboardText.has_value(); + _pendingClipboardText = text; + if (!clipboardMessageSent) + { + PostMessageW(window->GetWindowHandle(), CM_UPDATE_CLIPBOARD, 0, 0); + } + } +} + +std::optional CONSOLE_INFORMATION::UsePendingClipboardText() +{ + // Once the pending text has been used, we clear the variable to let the + // CopyTextToClipboard method know that the last CM_UPDATE_CLIPBOARD message + // has been processed, and future updates will require another message. + return std::exchange(_pendingClipboardText, {}); +} + // Method Description: // - Return the active screen buffer of the console. // Arguments: diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 3e8219a9bb..9107413e7e 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -280,9 +280,9 @@ unsigned int ConhostInternalGetSet::GetInputCodePage() const // - content - the text to be copied. // Return Value: // - -void ConhostInternalGetSet::CopyToClipboard(const wil::zwstring_view /*content*/) +void ConhostInternalGetSet::CopyToClipboard(const wil::zwstring_view content) { - // TODO + ServiceLocator::LocateGlobals().getConsoleInformation().CopyTextToClipboard(content); } // Routine Description: diff --git a/src/host/server.h b/src/host/server.h index d62484b3eb..d318808a8c 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -126,6 +126,8 @@ public: bool GetBracketedPasteMode() const noexcept; void SetBracketedPasteMode(const bool enabled) noexcept; + void CopyTextToClipboard(const std::wstring_view text); + std::optional UsePendingClipboardText(); void SetTitle(const std::wstring_view newTitle); void SetTitlePrefix(const std::wstring_view newTitlePrefix); @@ -160,6 +162,7 @@ private: SCREEN_INFORMATION* pCurrentScreenBuffer = nullptr; COOKED_READ_DATA* _cookedReadData = nullptr; // non-ownership pointer bool _bracketedPasteMode = false; + std::optional _pendingClipboardText; Microsoft::Console::VirtualTerminal::VtIo _vtIo; Microsoft::Console::CursorBlinker _blinker; diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index 8c42e91af9..b022947bbd 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -24,6 +24,22 @@ using namespace Microsoft::Console::Types; #pragma region Public Methods +void Clipboard::CopyText(const std::wstring& text) +{ + const auto clipboard = _openClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle()); + if (!clipboard) + { + LOG_LAST_ERROR(); + return; + } + + EmptyClipboard(); + // As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats + // CF_UNICODETEXT: [...] A null character signals the end of the data. + // --> We add +1 to the length. This works because .c_str() is null-terminated. + _copyToClipboard(CF_UNICODETEXT, text.c_str(), (text.size() + 1) * sizeof(wchar_t)); +} + // Arguments: // - fAlsoCopyFormatting - Place colored HTML & RTF text onto the clipboard as well as the usual plain text. // Return Value: diff --git a/src/interactivity/win32/CustomWindowMessages.h b/src/interactivity/win32/CustomWindowMessages.h index bf36660249..bcc43fdb7a 100644 --- a/src/interactivity/win32/CustomWindowMessages.h +++ b/src/interactivity/win32/CustomWindowMessages.h @@ -29,4 +29,6 @@ #define CM_SET_KEYBOARD_LAYOUT (WM_USER+19) #endif +#define CM_UPDATE_CLIPBOARD (WM_USER+20) + // clang-format on diff --git a/src/interactivity/win32/clipboard.hpp b/src/interactivity/win32/clipboard.hpp index 09990ab514..54851fa55b 100644 --- a/src/interactivity/win32/clipboard.hpp +++ b/src/interactivity/win32/clipboard.hpp @@ -29,6 +29,7 @@ namespace Microsoft::Console::Interactivity::Win32 public: static Clipboard& Instance(); + void CopyText(const std::wstring& text); void Copy(_In_ const bool fAlsoCopyFormatting = false); void Paste(); void PasteDrop(HDROP drop); diff --git a/src/interactivity/win32/windowproc.cpp b/src/interactivity/win32/windowproc.cpp index c3f1e68c29..3517ddd839 100644 --- a/src/interactivity/win32/windowproc.cpp +++ b/src/interactivity/win32/windowproc.cpp @@ -773,6 +773,15 @@ static constexpr TsfDataProvider s_tsfDataProvider; } #endif // DBG + case CM_UPDATE_CLIPBOARD: + { + if (const auto clipboardText = gci.UsePendingClipboardText()) + { + Clipboard::Instance().CopyText(clipboardText.value()); + } + break; + } + case EVENT_CONSOLE_CARET: case EVENT_CONSOLE_UPDATE_REGION: case EVENT_CONSOLE_UPDATE_SIMPLE: From 557a193cb7affac7b1564c5fcd6d3a2ddc7e5fd6 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Wed, 11 Jun 2025 01:04:56 +0100 Subject: [PATCH 09/74] Improve correctness of Sixel background fill (#18260) This is an attempt to improve the correctness of the background fill that the Sixel parser performs when an image is scrolled because it doesn't fit on the screen. This new behavior may not be exactly correct, but does at least match the VT330 and VT340 hardware more closely than it did before. The initial Sixel parser implementation was in PR #17421. When a Sixel image has the background select parameter set to 0 or 2, it fills the background up to the active raster attribute dimensions prior to writing out any actual pixel data. But this fill operation is clamped at the boundaries of the screen, so if the image doesn't fit, and needs to scroll, we have to perform an additional fill at that point to cover the background of the newly revealed rows (this is something we weren't doing before). This later fill uses the width of the most recent raster attributes command, which is not necessarily the same as the initial background fill, and fills the entire height of the new rows, regardless of the height specified in the raster attributes command. ## Validation Steps Performed Thanks to @al20878 and @hackerb9, we've been able to test on both a VT330 and a VT340, and I've confirmed that we're matching the behavior of those terminals as closely as possible. There are some edge cases where they don't agree with each other, so we can't always match both. I've also confirmed that the test case in issue #17946 now matches what the OP was expecting, and the test case in #17887 at least works more consistently now when scrolling (this is not what the OP was expecting though). Closes #17887 Closes #17946 --- src/terminal/adapter/SixelParser.cpp | 80 +++++++++++++++++++++++----- src/terminal/adapter/SixelParser.hpp | 4 ++ 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/terminal/adapter/SixelParser.cpp b/src/terminal/adapter/SixelParser.cpp index 60e7fecaee..15b9499d2a 100644 --- a/src/terminal/adapter/SixelParser.cpp +++ b/src/terminal/adapter/SixelParser.cpp @@ -237,6 +237,7 @@ void SixelParser::_executeNextLine() _imageCursor.y += _sixelHeight; _availablePixelHeight -= _sixelHeight; _resizeImageBuffer(_sixelHeight); + _fillImageBackgroundWhenScrolled(); } void SixelParser::_executeMoveToHome() @@ -341,6 +342,12 @@ void SixelParser::_initRasterAttributes(const VTInt macroParameter, const Dispat // By default, the filled area will cover the maximum extent allowed. _backgroundSize = { til::CoordTypeMax, til::CoordTypeMax }; + + // If the image ends up extending beyond the bottom of the page, we may need + // to perform additional background filling as the screen is scrolled, which + // requires us to track the area filled so far. This will be initialized, if + // necessary, in the _fillImageBackground method below. + _filledBackgroundHeight = std::nullopt; } void SixelParser::_updateRasterAttributes(const VTParameters& rasterAttributes) @@ -373,6 +380,15 @@ void SixelParser::_updateRasterAttributes(const VTParameters& rasterAttributes) // back to the dimensions from an earlier raster attributes command. _backgroundSize.width = width > 0 ? width : _backgroundSize.width; _backgroundSize.height = height > 0 ? height : _backgroundSize.height; + + // If the aspect ratio has changed, the image height may increase, and that + // could potentially trigger a scroll requiring the background to be filled. + _fillImageBackgroundWhenScrolled(); + + // And while not documented, we know from testing on a VT330 that the raster + // attributes command should also trigger a carriage return. This applies + // regardless of whether the requested aspect ratio is valid or not. + _executeCarriageReturn(); } void SixelParser::_scrollTextBuffer(Page& page, const int scrollAmount) @@ -656,24 +672,60 @@ void SixelParser::_fillImageBackground() { _backgroundFillRequired = false; - const auto backgroundHeight = std::min(_backgroundSize.height, _availablePixelHeight); - const auto backgroundWidth = std::min(_backgroundSize.width, _availablePixelWidth); - _resizeImageBuffer(backgroundHeight); - // When a background fill is requested, we prefill the buffer with the 0 // color index, up to the boundaries set by the raster attributes (or if // none were given, up to the page boundaries). The actual image output // isn't limited by the background dimensions though. - static constexpr auto backgroundPixel = IndexedPixel{}; - const auto backgroundOffset = _imageCursor.y * _imageMaxWidth; - auto dst = std::next(_imageBuffer.begin(), backgroundOffset); - for (auto i = 0; i < backgroundHeight; i++) - { - std::fill_n(dst, backgroundWidth, backgroundPixel); - std::advance(dst, _imageMaxWidth); - } + const auto backgroundHeight = std::min(_backgroundSize.height, _availablePixelHeight); + _resizeImageBuffer(backgroundHeight); + _fillImageBackground(backgroundHeight); + // When the image extends beyond the page boundaries, and the screen is + // scrolled, we also need to fill the newly exposed lines, so we keep a + // record of the area filled so far. Initially this is considered to be + // the available height, even if it wasn't all filled to start with. + _filledBackgroundHeight = _imageCursor.y + _availablePixelHeight; + _fillImageBackgroundWhenScrolled(); + } +} - _imageWidth = std::max(_imageWidth, backgroundWidth); +void SixelParser::_fillImageBackground(const int backgroundHeight) +{ + static constexpr auto backgroundPixel = IndexedPixel{}; + const auto backgroundWidth = std::min(_backgroundSize.width, _availablePixelWidth); + const auto backgroundOffset = _imageCursor.y * _imageMaxWidth; + auto dst = std::next(_imageBuffer.begin(), backgroundOffset); + for (auto i = 0; i < backgroundHeight; i++) + { + std::fill_n(dst, backgroundWidth, backgroundPixel); + std::advance(dst, _imageMaxWidth); + } + _imageWidth = std::max(_imageWidth, backgroundWidth); +} + +void SixelParser::_fillImageBackgroundWhenScrolled() +{ + // If _filledBackgroundHeight is set, that means a background fill has been + // requested, and we need to extend that area whenever the image is about to + // overrun it. The newly filled area is a multiple of the cell height (this + // is to match the behavior of the original hardware terminals). + const auto imageHeight = _imageCursor.y + _sixelHeight; + if (_filledBackgroundHeight && imageHeight > _filledBackgroundHeight) [[unlikely]] + { + _filledBackgroundHeight = (imageHeight + _cellSize.height - 1) / _cellSize.height * _cellSize.height; + const auto additionalFillHeight = *_filledBackgroundHeight - _imageCursor.y; + _resizeImageBuffer(additionalFillHeight); + _fillImageBackground(additionalFillHeight); + } +} + +void SixelParser::_decreaseFilledBackgroundHeight(const int decreasedHeight) noexcept +{ + // Sometimes the top of the image buffer may be clipped (e.g. when the image + // scrolls off the top of a margin area). When that occurs, our record of + // the filled height will need to be decreased to account for the new start. + if (_filledBackgroundHeight) [[unlikely]] + { + _filledBackgroundHeight = *_filledBackgroundHeight - decreasedHeight; } } @@ -717,11 +769,13 @@ void SixelParser::_eraseImageBufferRows(const int rowCount, const til::CoordType const auto bufferOffsetEnd = bufferOffset + pixelCount * _imageMaxWidth; if (static_cast(bufferOffsetEnd) >= _imageBuffer.size()) [[unlikely]] { + _decreaseFilledBackgroundHeight(_imageCursor.y); _imageBuffer.clear(); _imageCursor.y = 0; } else { + _decreaseFilledBackgroundHeight(pixelCount); _imageBuffer.erase(_imageBuffer.begin() + bufferOffset, _imageBuffer.begin() + bufferOffsetEnd); _imageCursor.y -= pixelCount; } diff --git a/src/terminal/adapter/SixelParser.hpp b/src/terminal/adapter/SixelParser.hpp index ea97783fec..62a7f7eae7 100644 --- a/src/terminal/adapter/SixelParser.hpp +++ b/src/terminal/adapter/SixelParser.hpp @@ -89,6 +89,7 @@ namespace Microsoft::Console::VirtualTerminal til::CoordType _pendingTextScrollCount = 0; til::size _backgroundSize; bool _backgroundFillRequired = false; + std::optional _filledBackgroundHeight; void _initColorMap(const VTParameter backgroundColor); void _defineColor(const VTParameters& colorParameters); @@ -109,6 +110,9 @@ namespace Microsoft::Console::VirtualTerminal void _initImageBuffer(); void _resizeImageBuffer(const til::CoordType requiredHeight); void _fillImageBackground(); + void _fillImageBackground(const int backgroundHeight); + void _fillImageBackgroundWhenScrolled(); + void _decreaseFilledBackgroundHeight(const int decreasedHeight) noexcept; void _writeToImageBuffer(const int sixelValue, const int repeatCount); void _eraseImageBufferRows(const int rowCount, const til::CoordType startRow = 0) noexcept; void _maybeFlushImageBuffer(const bool endOfSequence = false); From ad149228744ca1b2ad1ee50bfbe6eb474b7141a4 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Thu, 12 Jun 2025 02:56:57 +0500 Subject: [PATCH 10/74] build: update WinGet publish script to use env var instead of --token (#19021) With the latest winget-create release, the preferred method for providing the GitHub token in CI/CD environment is via the environment variable `WINGET_CREATE_GITHUB_TOKEN`. Removed use of `--token` and switched to environment variable. See https://aka.ms/winget-create-token for details. --- .github/workflows/winget.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index 63464ea4fe..5dda1dc89c 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -6,6 +6,9 @@ on: env: REGEX: 'Microsoft\.WindowsTerminal(?:Preview)?_([\d.]+)_8wekyb3d8bbwe\.msixbundle$' + # winget-create will read the following environment variable to access the GitHub token needed for submitting a PR + # See https://aka.ms/winget-create-token + WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_TOKEN }} jobs: publish: @@ -21,4 +24,4 @@ jobs: $wingetPackage = "Microsoft.WindowsTerminal${{ github.event.release.prerelease && '.Preview' || '' }}" & curl.exe -JLO https://aka.ms/wingetcreate/latest - & .\wingetcreate.exe update $wingetPackage -s -v $version -u $wingetRelevantAsset.browser_download_url -t "${{ secrets.WINGET_TOKEN }}" + & .\wingetcreate.exe update $wingetPackage -s -v $version -u $wingetRelevantAsset.browser_download_url From 7a9fb769558693d982660a88640bea7ba1bf7eb2 Mon Sep 17 00:00:00 2001 From: James Pack Date: Thu, 12 Jun 2025 20:34:51 -0400 Subject: [PATCH 11/74] Ensure items is not null before checking its size so we dont crash (#19026) Prevents a crash when no storage items are returned from a dropped path. Terminal no longer crashes when a relative path is dragged and dropped into the tool. Closes #19015 --- src/cascadia/TerminalControl/TermControl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 7118c7a868..b38c651e3d 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -3180,7 +3180,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } CATCH_LOG(); - if (items.Size() > 0) + if (items && items.Size() > 0) { std::vector fullPaths; From 685499df3a5bc1e7f1d9e373062c66cb2d694259 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 13 Jun 2025 15:38:40 -0700 Subject: [PATCH 12/74] Automatically enable AdjustIndistinguishableColors if High Contrast mode enabled (#17346) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If High Contrast mode is enabled in the OS settings, we now automatically enable `adjustIndistinguishableColors`. To accomplish this, a new `Automatic` value is added to `adjustIndistinguishableColors`. When it's chosen, color nudging doesn't occur in regular contrast sessions, but we interpret the value as `Indexed` respectively. The new default value is `AutomaticIndexed`. Meaning that regular contrast sessions will see no difference in behavior. However, if they switch to high contrast mode, Windows Terminal will interpret the value as `Indexed` at runtime. This was chosen because `Always` is more performance intensive.    ## References and Relevant Issues #12999 ## Validation Steps Performed ✅ Toggling High Contrast mode immediately triggers an updated terminal instance with `adjustIndistinguishableColors` --- src/cascadia/TerminalControl/ControlCore.cpp | 5 +++++ src/cascadia/TerminalControl/ControlCore.h | 1 + src/cascadia/TerminalControl/ControlCore.idl | 1 + src/cascadia/TerminalControl/TermControl.cpp | 16 ++++++++++++++++ src/cascadia/TerminalControl/TermControl.h | 4 +--- src/cascadia/TerminalCore/ICoreAppearance.idl | 3 ++- src/cascadia/TerminalCore/Terminal.cpp | 15 ++++++++++++++- src/cascadia/TerminalCore/Terminal.hpp | 2 ++ .../Resources/en-US/Resources.resw | 4 ++++ .../TerminalSettingsModel/MTSMSettings.h | 2 +- .../TerminalSettingsSerializationHelpers.h | 3 ++- src/cascadia/inc/ControlProperties.h | 2 +- 12 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index a96adb7a1d..61d699a6cc 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -955,6 +955,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } + void ControlCore::SetHighContrastMode(const bool enabled) + { + _terminal->SetHighContrastMode(enabled); + } + Control::IControlSettings ControlCore::Settings() { return *_settings; diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 8e99e26155..2453670d3f 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -94,6 +94,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void UpdateSettings(const Control::IControlSettings& settings, const IControlAppearance& newAppearance); void ApplyAppearance(const bool focused); + void SetHighContrastMode(const bool enabled); Control::IControlSettings Settings(); Control::IControlAppearance FocusedAppearance() const; Control::IControlAppearance UnfocusedAppearance() const; diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index 528bc11458..bc704a1342 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -102,6 +102,7 @@ namespace Microsoft.Terminal.Control IControlAppearance FocusedAppearance { get; }; IControlAppearance UnfocusedAppearance { get; }; Boolean HasUnfocusedAppearance(); + void SetHighContrastMode(Boolean enabled); UInt64 SwapChainHandle { get; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index b38c651e3d..67809d4cea 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -257,6 +257,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation return get_self(_termControl->_core); } + static Windows::UI::ViewManagement::AccessibilitySettings& _GetAccessibilitySettings() + { + static Windows::UI::ViewManagement::AccessibilitySettings accessibilitySettings; + return accessibilitySettings; + } + TermControl::TermControl(IControlSettings settings, Control::IControlAppearance unfocusedAppearance, TerminalConnection::ITerminalConnection connection) : @@ -276,6 +282,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation _core = _interactivity.Core(); + // If high contrast mode was changed, update the appearance appropriately. + _core.SetHighContrastMode(_GetAccessibilitySettings().HighContrast()); + _revokers.HighContrastChanged = _GetAccessibilitySettings().HighContrastChanged(winrt::auto_revoke, [weakThis{ get_weak() }](const Windows::UI::ViewManagement::AccessibilitySettings& a11ySettings, auto&&) { + if (auto termControl = weakThis.get()) + { + termControl->_core.SetHighContrastMode(a11ySettings.HighContrast()); + termControl->_core.ApplyAppearance(termControl->_focused); + } + }); + // This event is specifically triggered by the renderer thread, a BG thread. Use a weak ref here. _revokers.RendererEnteredErrorState = _core.RendererEnteredErrorState(winrt::auto_revoke, { get_weak(), &TermControl::_RendererEnteredErrorState }); diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 2e8fadd850..7fe3db1be0 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -362,7 +362,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation }; bool _InitializeTerminal(const InitializeReason reason); safe_void_coroutine _restoreInBackground(); - void _SetFontSize(int fontSize); void _TappedHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& e); void _KeyDownHandler(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); @@ -394,8 +393,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _SwapChainSizeChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::SizeChangedEventArgs& e); void _SwapChainScaleChanged(const Windows::UI::Xaml::Controls::SwapChainPanel& sender, const Windows::Foundation::IInspectable& args); - void _TerminalTabColorChanged(const std::optional color); - void _ScrollPositionChanged(const IInspectable& sender, const Control::ScrollPositionChangedArgs& args); bool _CapturePointer(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e); @@ -480,6 +477,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // These are set up in _InitializeTerminal Control::ControlCore::RendererWarning_revoker RendererWarning; Control::ControlCore::SwapChainChanged_revoker SwapChainChanged; + Windows::UI::ViewManagement::AccessibilitySettings::HighContrastChanged_revoker HighContrastChanged; Control::ControlInteractivity::OpenHyperlink_revoker interactivityOpenHyperlink; Control::ControlInteractivity::ScrollPositionChanged_revoker interactivityScrollPositionChanged; diff --git a/src/cascadia/TerminalCore/ICoreAppearance.idl b/src/cascadia/TerminalCore/ICoreAppearance.idl index fa0364a322..d86fdd4262 100644 --- a/src/cascadia/TerminalCore/ICoreAppearance.idl +++ b/src/cascadia/TerminalCore/ICoreAppearance.idl @@ -23,7 +23,8 @@ namespace Microsoft.Terminal.Core { Never, Indexed, - Always + Always, + Automatic }; // TerminalCore declares its own Color struct to avoid depending diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 68d7178187..039b251144 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -143,7 +143,15 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) renderSettings.SetRenderMode(RenderSettings::Mode::IntenseIsBold, appearance.IntenseIsBold()); renderSettings.SetRenderMode(RenderSettings::Mode::IntenseIsBright, appearance.IntenseIsBright()); - switch (appearance.AdjustIndistinguishableColors()) + // If AIC is set to Automatic, + // update the value based on if high contrast mode is enabled. + AdjustTextMode deducedAIC = appearance.AdjustIndistinguishableColors(); + if (deducedAIC == AdjustTextMode::Automatic) + { + deducedAIC = _highContrastMode ? AdjustTextMode::Indexed : AdjustTextMode::Never; + } + + switch (deducedAIC) { case AdjustTextMode::Always: renderSettings.SetRenderMode(RenderSettings::Mode::IndexedDistinguishableColors, false); @@ -213,6 +221,11 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) _NotifyScrollEvent(); } +void Terminal::SetHighContrastMode(bool hc) noexcept +{ + _highContrastMode = hc; +} + void Terminal::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) { auto& engine = reinterpret_cast(_stateMachine->Engine()); diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 1459c39fc3..afbf873da9 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -92,6 +92,7 @@ public: void UpdateSettings(winrt::Microsoft::Terminal::Core::ICoreSettings settings); void UpdateAppearance(const winrt::Microsoft::Terminal::Core::ICoreAppearance& appearance); + void SetHighContrastMode(bool hc) noexcept; void SetFontInfo(const FontInfo& fontInfo); void SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle); void SetVtChecksumReportSupport(const bool enabled); @@ -382,6 +383,7 @@ private: std::wstring _answerbackMessage; std::wstring _workingDirectory; + bool _highContrastMode = false; // This default fake font value is only used to check if the font is a raster font. // Otherwise, the font is changed to a real value with the renderer via TriggerFontChange. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 7949d017ca..7ed6a84252 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -946,6 +946,10 @@ Always An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Automatic + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Bar ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index eb195f2695..01bd5ce5c8 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -136,7 +136,7 @@ Author(s): X(ConvergedAlignment, BackgroundImageAlignment, "backgroundImageAlignment", ConvergedAlignment::Horizontal_Center | ConvergedAlignment::Vertical_Center) \ X(hstring, BackgroundImagePath, "backgroundImage") \ X(Model::IntenseStyle, IntenseTextStyle, "intenseTextStyle", Model::IntenseStyle::Bright) \ - X(Core::AdjustTextMode, AdjustIndistinguishableColors, "adjustIndistinguishableColors", Core::AdjustTextMode::Never) \ + X(Core::AdjustTextMode, AdjustIndistinguishableColors, "adjustIndistinguishableColors", Core::AdjustTextMode::Automatic) \ X(bool, UseAcrylic, "useAcrylic", false) // Intentionally omitted Appearance settings: diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index 448eae5e43..32a24b11f7 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -35,10 +35,11 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Core::CursorStyle) // - Helper for converting a user-specified adjustTextMode value to its corresponding enum JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Core::AdjustTextMode) { - JSON_MAPPINGS(3) = { + JSON_MAPPINGS(4) = { pair_type{ "never", ValueType::Never }, pair_type{ "indexed", ValueType::Indexed }, pair_type{ "always", ValueType::Always }, + pair_type{ "automatic", ValueType::Automatic }, }; // Override mapping parser to add boolean parsing diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index 42c3b1b9d3..107773da06 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -14,7 +14,7 @@ X(til::color, SelectionBackground, DEFAULT_FOREGROUND) \ X(bool, IntenseIsBold) \ X(bool, IntenseIsBright, true) \ - X(winrt::Microsoft::Terminal::Core::AdjustTextMode, AdjustIndistinguishableColors, winrt::Microsoft::Terminal::Core::AdjustTextMode::Never) + X(winrt::Microsoft::Terminal::Core::AdjustTextMode, AdjustIndistinguishableColors, winrt::Microsoft::Terminal::Core::AdjustTextMode::Automatic) // --------------------------- Control Appearance --------------------------- // All of these settings are defined in IControlAppearance. From bb62ce93459feb9bbe0a90ddb6c444bc23817d20 Mon Sep 17 00:00:00 2001 From: Katherine Reynolds <7054971+reynoldskr@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:41:10 -0700 Subject: [PATCH 13/74] Improve VS profile generation (#19025) * Make PowerShell profile generation try to find `pwsh.exe` before falling back to legacy powershell * Make profiles generated on an `arm64` host work properly ## Validation Steps Performed * Local build ran * Verified the new `arm64` profile works * Verified `pwsh.exe` is used if present * Verified `powershell.exe` is used if `pwsh` is not present in path * Verified we don't attempt to create `arm64` host cmd/pwsh profiles if VS is not >= 17.4 --- .../VsDevCmdGenerator.cpp | 10 +++++- .../VsDevShellGenerator.cpp | 36 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp b/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp index 19d6f50cc8..c4baf36a39 100644 --- a/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/VsDevCmdGenerator.cpp @@ -45,7 +45,15 @@ std::wstring VsDevCmdGenerator::GetProfileCommandLine(const VsSetupConfiguration // The "-startdir" parameter will prevent "vsdevcmd" from automatically // setting the shell path so the path in the profile will be used instead. #if defined(_M_ARM64) - commandLine.append(LR"(" -startdir=none -arch=arm64 -host_arch=x64)"); + commandLine.append(LR"(" -startdir=none -arch=arm64 -host_arch=)"); + if (instance.VersionInRange(L"[17.4,")) + { + commandLine.append(LR"(arm64)"); + } + else + { + commandLine.append(LR"(x64)"); + } #elif defined(_M_AMD64) commandLine.append(LR"(" -startdir=none -arch=x64 -host_arch=x64)"); #else diff --git a/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp b/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp index 22c17cd98a..ce14532107 100644 --- a/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/VsDevShellGenerator.cpp @@ -39,17 +39,43 @@ std::wstring VsDevShellGenerator::GetProfileName(const VsSetupConfiguration::VsS std::wstring VsDevShellGenerator::GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance& instance) const { - // The triple-quotes are a PowerShell path escape sequence that can safely be stored in a JSON object. - // The "SkipAutomaticLocation" parameter will prevent "Enter-VsDevShell" from automatically setting the shell path - // so the path in the profile will be used instead. + // Build this in stages, so reserve space now std::wstring commandLine; commandLine.reserve(256); - commandLine.append(LR"(powershell.exe -NoExit -Command "&{Import-Module """)"); + + // Try to detect if `pwsh.exe` is available in the PATH, if so we want to use that + // Allow some extra space in case user put it somewhere odd + // We do need to allocate space for the full path even if we don't want to paste the whole thing in + wchar_t pwshPath[MAX_PATH] = { 0 }; + const auto pwshExeName = L"pwsh.exe"; + if (SearchPathW(nullptr, pwshExeName, nullptr, MAX_PATH, pwshPath, nullptr)) + { + commandLine.append(pwshExeName); + } + else + { + commandLine.append(L"powershell.exe"); + } + + // The triple-quotes are a PowerShell path escape sequence that can safely be stored in a JSON object. + // The "SkipAutomaticLocation" parameter will prevent "Enter-VsDevShell" from automatically setting the shell path + // so the path in the profile will be used instead + commandLine.append(LR"( -NoExit -Command "&{Import-Module """)"); commandLine.append(GetDevShellModulePath(instance)); commandLine.append(LR"("""; Enter-VsDevShell )"); commandLine.append(instance.GetInstanceId()); #if defined(_M_ARM64) - commandLine.append(LR"( -SkipAutomaticLocation -DevCmdArguments """-arch=arm64 -host_arch=x64"""}")"); + // This part stays constant no matter what + commandLine.append(LR"( -SkipAutomaticLocation -DevCmdArguments """-arch=arm64 -host_arch=)"); + if (instance.VersionInRange(L"[17.4,")) + { + commandLine.append(LR"("arm64 """}")"); + } + // If an old version of VS is installed without ARM64 host support + else + { + commandLine.append(LR"("x64 """}")"); + } #elif defined(_M_AMD64) commandLine.append(LR"( -SkipAutomaticLocation -DevCmdArguments """-arch=x64 -host_arch=x64"""}")"); #else From bd7e3179ff1c41e9147d99253a9ab150cf9e942a Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 13 Jun 2025 15:43:13 -0700 Subject: [PATCH 14/74] Manually focus panes after swapping them (#19024) --- src/cascadia/TerminalApp/Pane.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index dc235b0602..3255c5fba3 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -697,12 +697,24 @@ bool Pane::SwapPanes(std::shared_ptr first, std::shared_ptr second) // Refocus the last pane if there was a pane focused if (const auto focus = first->GetActivePane()) { - focus->_Focus(); + // GH#18184: manually focus the pane and content. + // _Focus() results in no-op because the pane was _lastActive + focus->GotFocus.raise(focus, FocusState::Programmatic); + if (const auto& lastContent{ focus->GetLastFocusedContent() }) + { + lastContent.Focus(FocusState::Programmatic); + } } if (const auto focus = second->GetActivePane()) { - focus->_Focus(); + // GH#18184: manually focus the pane and content. + // _Focus() results in no-op because the pane was _lastActive + focus->GotFocus.raise(focus, FocusState::Programmatic); + if (const auto& lastContent{ focus->GetLastFocusedContent() }) + { + lastContent.Focus(FocusState::Programmatic); + } } return true; From 098da6ce1c8fa439cc814931e64c2b443e16783b Mon Sep 17 00:00:00 2001 From: James Pack Date: Mon, 16 Jun 2025 09:37:45 -0400 Subject: [PATCH 15/74] Replace newline literal in AtlasEngine Readme with
tag (#19041) --- src/renderer/atlas/README.md | 42 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/renderer/atlas/README.md b/src/renderer/atlas/README.md index c72e77f51e..a1619e9b4b 100644 --- a/src/renderer/atlas/README.md +++ b/src/renderer/atlas/README.md @@ -4,18 +4,18 @@ ```mermaid graph TD - RenderThread["RenderThread (base/thread.cpp)\ncalls Renderer::PaintFrame() x times per sec"] - Renderer["Renderer (base/renderer.cpp)\nbreaks the text buffer down into GDI-oriented graphics\nprimitives (#quot;change brush to color X#quot;, #quot;draw string Y#quot;, ...)"] - RenderEngineBase[/"RenderEngineBase\n(base/RenderEngineBase.cpp)\nabstracts 24 LOC 👻"\] + RenderThread["RenderThread (base/thread.cpp)
calls Renderer::PaintFrame() x times per sec"] + Renderer["Renderer (base/renderer.cpp)
breaks the text buffer down into GDI-oriented graphics
primitives (#quot;change brush to color X#quot;, #quot;draw string Y#quot;, ...)
"] + RenderEngineBase[/"RenderEngineBase
(base/RenderEngineBase.cpp)
abstracts 24 LOC 👻"\] GdiEngine["GdiEngine (gdi/...)"] subgraph AtlasEngine["AtlasEngine (atlas/...)"] - AtlasEngine.cpp["AtlasEngine.cpp\nImplements IRenderEngine text rendering API\nbreaks GDI graphics primitives down into DWRITE_GLYPH_RUNs"] - AtlasEngine.api.cpp["AtlasEngine.api.cpp\nImplements the parts run inside the console\nlock (many IRenderEngine setters)"] - AtlasEngine.r.cpp["AtlasEngine.r.cpp\nImplements the parts run\noutside of the console lock"] - Backend.cpp["Backend.cpp\nImplements common functionality/helpers"] - BackendD2D.cpp["BackendD2D.cpp\nPure Direct2D text renderer (for low latency\nremote desktop and older/no GPUs)"] - BackendD3D.cpp["BackendD3D.cpp\nCustom, performant text renderer\nwith our own glyph cache"] + AtlasEngine.cpp["AtlasEngine.cpp
Implements IRenderEngine text rendering API
breaks GDI graphics primitives down into DWRITE_GLYPH_RUNs
"] + AtlasEngine.api.cpp["AtlasEngine.api.cpp
Implements the parts run inside the console
lock (many IRenderEngine setters)"] + AtlasEngine.r.cpp["AtlasEngine.r.cpp
Implements the parts run
outside of the console lock"] + Backend.cpp["Backend.cpp
Implements common functionality/helpers"] + BackendD2D.cpp["BackendD2D.cpp
Pure Direct2D text renderer (for low latency
remote desktop and older/no GPUs)
"] + BackendD3D.cpp["BackendD3D.cpp
Custom, performant text renderer
with our own glyph cache
"] end RenderThread --> Renderer @@ -63,8 +63,8 @@ graph TD ```mermaid graph TD - Render --> _drawCursorPart1["_drawCursorPart1\nruns before _drawText\ndraws cursors that are behind the text"] - Render --> _drawCursorPart2["_drawCursorPart2\nruns after _drawText\ndraws inverted cursors"] + Render --> _drawCursorPart1["_drawCursorPart1
runs before _drawText
draws cursors that are behind the text"] + Render --> _drawCursorPart2["_drawCursorPart2
runs after _drawText
draws inverted cursors"] _drawCursorPart1 -.->|_cursorRects| _drawCursorPart2 ``` @@ -81,26 +81,26 @@ graph TD foreachFont --> foreachGlyph(("for each glyph")) foreachGlyph --> foreachGlyph - foreachGlyph --> _glyphAtlasMap[("font/glyph-pair lookup in\nglyph cache hashmap")] + foreachGlyph --> _glyphAtlasMap[("font/glyph-pair lookup in
glyph cache hashmap")] _glyphAtlasMap --> drawGlyph - drawGlyph --> _appendQuad["_appendQuad\nstages the glyph for later drawing"] + drawGlyph --> _appendQuad["_appendQuad
stages the glyph for later drawing"] _glyphAtlasMap --> _appendQuad subgraph drawGlyph["if glyph is missing"] - _drawGlyph["_drawGlyph\n(defers to _drawSoftFontGlyph for soft fonts)"] + _drawGlyph["_drawGlyph
(defers to _drawSoftFontGlyph for soft fonts)"] _drawGlyph -.->|if glpyh cache is full| _drawGlyphPrepareRetry - _drawGlyphPrepareRetry --> _flushQuads["_flushQuads\ndraws the current state\ninto the render target"] - _flushQuads --> _recreateInstanceBuffers["_recreateInstanceBuffers\nallocates a GPU buffer\nfor our glyph instances"] - _drawGlyphPrepareRetry --> _resetGlyphAtlas["_resetGlyphAtlas\nclears the glyph texture"] - _resetGlyphAtlas --> _resizeGlyphAtlas["_resizeGlyphAtlas\nresizes the glyph texture if it's still small"] + _drawGlyphPrepareRetry --> _flushQuads["_flushQuads
draws the current state
into the render target
"] + _flushQuads --> _recreateInstanceBuffers["_recreateInstanceBuffers
allocates a GPU buffer
for our glyph instances
"] + _drawGlyphPrepareRetry --> _resetGlyphAtlas["_resetGlyphAtlas
clears the glyph texture"] + _resetGlyphAtlas --> _resizeGlyphAtlas["_resizeGlyphAtlas
resizes the glyph texture if it's still small"] - _drawGlyph -.->|if it's a DECDHL glyph| _splitDoubleHeightGlyph["_splitDoubleHeightGlyph\nDECDHL glyphs are split up into their\ntop/bottom halves to emulate clip rects"] + _drawGlyph -.->|if it's a DECDHL glyph| _splitDoubleHeightGlyph["_splitDoubleHeightGlyph
DECDHL glyphs are split up into their
top/bottom halves to emulate clip rects
"] end - foreachGlyph -.-> _drawTextOverlapSplit["_drawTextOverlapSplit\nsplits overly wide glyphs up into smaller chunks to support\nforeground color changes within the ligature"] + foreachGlyph -.-> _drawTextOverlapSplit["_drawTextOverlapSplit
splits overly wide glyphs up into smaller chunks to support
foreground color changes within the ligature
"] - foreachRow -.->|if gridlines exist| _drawGridlineRow["_drawGridlineRow\ndraws underlines, etc."] + foreachRow -.->|if gridlines exist| _drawGridlineRow["_drawGridlineRow
draws underlines, etc."] ``` ### `_drawSelection` From f28bb42979756318073a9d42b0209e6f9ebd77c7 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 16 Jun 2025 18:01:35 -0500 Subject: [PATCH 16/74] Gently rework icon and background image validation (#19044) Right now, image validation accepts web-sourced icons (boo) and rejects images whose paths begin with `\\?\`. In addition, it will warn the user for things out of their control like images set by fragments. This pull request adds a filesystem path validator (which accepts images with fully-qualified paths and UNC paths), makes the URI validator reject any web-origin URIs (only `file` and `ms-*` are allowable), and suppresses warnings for any images that were not _directly_ set by the user. Since we want to avoid using fragment images that fail validation, we no longer `Clear` each image property but rather set it to the blank or fallback value. This does **not** actually add support for images at absolute paths beginning with `\\?\`. Such images are still rejected by `Image` and the other XAML fixtures we use for images. It's better than a warning, though. Closes #18703 Closes #14143 Refs #18710 Refs #5204 Related to #18922 (http-origin icons will be blank everywhere and not just the jump list ;)) --- .../CascadiaSettings.cpp | 101 ++++++++++++------ 1 file changed, 70 insertions(+), 31 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index a252695108..9dc2c156f2 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -488,6 +488,39 @@ void CascadiaSettings::_validateAllSchemesExist() } } +static bool _validateSingleMediaResource(std::wstring_view resource) +{ + // URI + try + { + winrt::Windows::Foundation::Uri resourceUri{ resource }; + if (!resourceUri) + { + return false; + } + + const auto scheme{ resourceUri.SchemeName() }; + // Only file: URIs and ms-* URIs are permissible. http, https, ftp, gopher, etc. are not. + return til::equals_insensitive_ascii(scheme, L"file") || til::starts_with_insensitive_ascii(scheme, L"ms-"); + } + catch (...) + { + // fall through + } + + // Not a URI? Try a path. + try + { + std::filesystem::path resourcePath{ resource }; + return std::filesystem::exists(resourcePath); + } + catch (...) + { + // fall through + } + return false; +} + // Method Description: // - Ensures that all specified images resources (icons and background images) are valid URIs. // This does not verify that the icon or background image files are encoded as an image. @@ -501,24 +534,26 @@ void CascadiaSettings::_validateAllSchemesExist() // we find any invalid icon images. void CascadiaSettings::_validateMediaResources() { - auto invalidBackground{ false }; - auto invalidIcon{ false }; + auto warnInvalidBackground{ false }; + auto warnInvalidIcon{ false }; for (auto profile : _allProfiles) { if (const auto path = profile.DefaultAppearance().ExpandedBackgroundImagePath(); !path.empty()) { - // Attempt to convert the path to a URI, the ctor will throw if it's invalid/unparseable. - // This covers file paths on the machine, app data, URLs, and other resource paths. - try + if (!_validateSingleMediaResource(path)) { - winrt::Windows::Foundation::Uri imagePath{ path }; - } - catch (...) - { - // reset background image path - profile.DefaultAppearance().ClearBackgroundImagePath(); - invalidBackground = true; + if (profile.DefaultAppearance().HasBackgroundImagePath()) + { + // Only warn and delete if the user set this at the top level (do not warn for fragments, just clear it) + warnInvalidBackground = true; + profile.DefaultAppearance().ClearBackgroundImagePath(); + } + else + { + // reset background image path (set it to blank as an override for any fragment value) + profile.DefaultAppearance().BackgroundImagePath({}); + } } } @@ -526,17 +561,18 @@ void CascadiaSettings::_validateMediaResources() { if (const auto path = profile.UnfocusedAppearance().ExpandedBackgroundImagePath(); !path.empty()) { - // Attempt to convert the path to a URI, the ctor will throw if it's invalid/unparseable. - // This covers file paths on the machine, app data, URLs, and other resource paths. - try + if (!_validateSingleMediaResource(path)) { - winrt::Windows::Foundation::Uri imagePath{ path }; - } - catch (...) - { - // reset background image path - profile.UnfocusedAppearance().ClearBackgroundImagePath(); - invalidBackground = true; + if (profile.UnfocusedAppearance().HasBackgroundImagePath()) + { + warnInvalidBackground = true; + profile.UnfocusedAppearance().ClearBackgroundImagePath(); + } + else + { + // reset background image path (set it to blank as an override for any fragment value) + profile.UnfocusedAppearance().BackgroundImagePath({}); + } } } } @@ -552,24 +588,27 @@ void CascadiaSettings::_validateMediaResources() if (const auto icon = profile.Icon(); icon.size() > 2 && icon != HideIconValue) { const auto iconPath{ wil::ExpandEnvironmentStringsW(icon.c_str()) }; - try + if (!_validateSingleMediaResource(iconPath)) { - winrt::Windows::Foundation::Uri imagePath{ iconPath }; - } - catch (...) - { - profile.ClearIcon(); - invalidIcon = true; + if (profile.HasIcon()) + { + warnInvalidIcon = true; + profile.ClearIcon(); + } + else + { + profile.Icon({}); + } } } } - if (invalidBackground) + if (warnInvalidBackground) { _warnings.Append(SettingsLoadWarnings::InvalidBackgroundImage); } - if (invalidIcon) + if (warnInvalidIcon) { _warnings.Append(SettingsLoadWarnings::InvalidIcon); } From b47fdfc7e63fd7c2f8c060a1abc930b3fc40fc19 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 17 Jun 2025 01:52:26 +0200 Subject: [PATCH 17/74] Fix support for the Tencent QQPinyin IME (#19046) --- .github/actions/spelling/allow/allow.txt | 5 ++-- src/tsf/Implementation.cpp | 31 ++++++++++++++++-------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index f9a16af64d..b347ec85b9 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -24,8 +24,8 @@ dze dzhe Emacspeak Fitt -FTCS flac +FTCS gantt gfm ghe @@ -61,8 +61,8 @@ Powerline ptys pwn pwshw -QOL qof +QOL qps quickfix rclt @@ -82,6 +82,7 @@ stakeholders subpage sustainability sxn +Tencent TLDR tonos toolset diff --git a/src/tsf/Implementation.cpp b/src/tsf/Implementation.cpp index f2cd958933..6f2e249755 100644 --- a/src/tsf/Implementation.cpp +++ b/src/tsf/Implementation.cpp @@ -711,13 +711,28 @@ TextAttribute Implementation::_textAttributeFromAtom(TfGuidAtom atom) const TF_DISPLAYATTRIBUTE da; THROW_IF_FAILED(dai->GetAttributeInfo(&da)); - if (da.crText.type != TF_CT_NONE) + // The Tencent QQPinyin IME creates TF_CT_COLORREF attributes with a color of 0x000000 (black). + // We respect their wish, which results in the preview text being invisible. + // (Note that sending this COLORREF is incorrect, and not a bug in our handling.) + // + // After some discussion, we realized that an IME which sets only one color but not + // the others is likely not properly tested anyway, so we reject those cases. + // After all, what behavior do we expect, if the IME sends e.g. foreground=blue, + // without knowing whether our terminal theme already uses a blue background? + if (da.crText.type == da.crBk.type && da.crText.type == da.crLine.type) { - attr.SetForeground(_colorFromDisplayAttribute(da.crText)); - } - if (da.crBk.type != TF_CT_NONE) - { - attr.SetBackground(_colorFromDisplayAttribute(da.crBk)); + if (da.crText.type != TF_CT_NONE) + { + attr.SetForeground(_colorFromDisplayAttribute(da.crText)); + } + if (da.crBk.type != TF_CT_NONE) + { + attr.SetBackground(_colorFromDisplayAttribute(da.crBk)); + } + if (da.crLine.type != TF_CT_NONE) + { + attr.SetUnderlineColor(_colorFromDisplayAttribute(da.crLine)); + } } if (da.lsStyle >= TF_LS_NONE && da.lsStyle <= TF_LS_SQUIGGLE) { @@ -737,10 +752,6 @@ TextAttribute Implementation::_textAttributeFromAtom(TfGuidAtom atom) const { attr.SetUnderlineStyle(UnderlineStyle::DoublyUnderlined); } - if (da.crLine.type != TF_CT_NONE) - { - attr.SetUnderlineColor(_colorFromDisplayAttribute(da.crLine)); - } return attr; } From 8a018334890b05080888f7bdb91a561661faf5d3 Mon Sep 17 00:00:00 2001 From: Windows Console Service Bot <14666831+consvc@users.noreply.github.com> Date: Mon, 16 Jun 2025 22:42:04 -0500 Subject: [PATCH 18/74] Localization Updates - main - 06/14/2025 03:04:55 (#19035) --- .../TerminalSettingsEditor/Resources/de-DE/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/es-ES/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/fr-FR/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/it-IT/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/ja-JP/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/ko-KR/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/pt-BR/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/qps-ploc/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/qps-ploca/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/qps-plocm/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/ru-RU/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/zh-CN/Resources.resw | 4 ++++ .../TerminalSettingsEditor/Resources/zh-TW/Resources.resw | 4 ++++ 13 files changed, 52 insertions(+) diff --git a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw index 968d594762..cf7198582d 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw @@ -946,6 +946,10 @@ Immer An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility.
+ + Automatisch + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Balken ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw index f3204f013f..3e5d705e1a 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw @@ -946,6 +946,10 @@ Siempre An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Automático + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Barra ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw index ac90f141ab..884781e9c9 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw @@ -946,6 +946,10 @@ Toujours An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Automatique + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Barre ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw index b6aebaa918..b4ce453594 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw @@ -946,6 +946,10 @@ Sempre An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Automatico + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Bar ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw index 10514c94e6..80dd6d9cfe 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw @@ -946,6 +946,10 @@ 常時 An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + 自動 + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + バー ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw index 711a533182..6c668fd91a 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw @@ -946,6 +946,10 @@ 항상 An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + 자동 + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + 막대( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw index faf76cce95..3e0b684fbf 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw @@ -946,6 +946,10 @@ Sempre An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Automático + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Barra ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw index 800e7b85cd..c9e208ee3f 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw @@ -946,6 +946,10 @@ Åļωάỳš ! An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Дüтôмαťί¢ !!! + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Ъâг ( ┃ ) !!! {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw index 800e7b85cd..c9e208ee3f 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw @@ -946,6 +946,10 @@ Åļωάỳš ! An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Дüтôмαťί¢ !!! + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Ъâг ( ┃ ) !!! {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw index 800e7b85cd..c9e208ee3f 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw @@ -946,6 +946,10 @@ Åļωάỳš ! An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Дüтôмαťί¢ !!! + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Ъâг ( ┃ ) !!! {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw index 28113389e5..c70359f395 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw @@ -946,6 +946,10 @@ Всегда An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + Автоматически + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + Вертикальная линия (┃) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw index 7f46d38030..a93980ce3b 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw @@ -946,6 +946,10 @@ 始终 An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + 自动 + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + 条形 ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw index ed1ee7b18e..2ada7577e3 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw @@ -946,6 +946,10 @@ 一律 An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility. + + 自動 + An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table if and only if high contrast mode is enabled. + 橫條圖 ( ┃ ) {Locked="┃"} An option to choose from for the "cursor shape" setting. When selected, the cursor will look like a vertical bar. The character in the parentheses is used to show what it looks like. From 4cf492e36bbb4f26769b71c586d402957310d764 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 18 Jun 2025 18:04:45 -0500 Subject: [PATCH 19/74] build: adjust for changes in the Az.Accounts module (#19020) --- .../job-publish-symbols-using-symbolrequestprod-api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml b/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml index a5ed41f737..1b34002c50 100644 --- a/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml +++ b/build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml @@ -61,7 +61,7 @@ jobs: pwsh: true ScriptType: InlineScript Inline: |- - $AzToken = (Get-AzAccessToken -ResourceUrl api://30471ccf-0966-45b9-a979-065dbedb24c1).Token + $AzToken = (Get-AzAccessToken -AsSecureString -ResourceUrl api://30471ccf-0966-45b9-a979-065dbedb24c1).Token | ConvertFrom-SecureString -AsPlainText Write-Host "##vso[task.setvariable variable=SymbolAccessToken;issecret=true]$AzToken" From 00ee88400aab0cd94409204ce0c6d20854ba7eff Mon Sep 17 00:00:00 2001 From: James Holderness Date: Thu, 19 Jun 2025 02:13:00 +0100 Subject: [PATCH 20/74] Indicate support for OSC 52 in the DA1 report (#19034) ## Summary of the Pull Request Some applications that make use of the `OSC 52` clipboard sequence will only do so if they can be certain that the terminal actually has that functionality. Indicating our support for `OSC 52` in the `DA1` report will give them an easy way to detect that. ## References and Relevant Issues `OSC 52` support was added to Windows Terminal in issue #5823, and to ConHost in issue #18949. ## Detailed Description of the Pull Request / Additional comments Support for writing to the clipboard is indicated in the primary device attributes report by the extension parameter `52`. This is obviously not a standard DEC extension, but it's one that's been agreed upon by a number of modern terminals. The extension is only reported when writing to the clipboard is actually permitted (Windows Terminal has an option to disable that). ## Validation Steps Performed I've updated the Device Attributes unit test to check that we're reporting extension `52` when clipboard access is enabled, and not reporting it when disabled. ## PR Checklist - [x] Closes #19017 - [x] Tests added/passed --- src/cascadia/TerminalCore/Terminal.cpp | 9 +++++--- src/cascadia/TerminalCore/Terminal.hpp | 2 +- src/terminal/adapter/ITermDispatch.hpp | 9 +++++++- src/terminal/adapter/adaptDispatch.cpp | 22 +++++++++++++------ src/terminal/adapter/adaptDispatch.hpp | 4 ++-- src/terminal/adapter/termDispatch.hpp | 2 +- .../adapter/ut_adapter/adapterTest.cpp | 13 +++++++---- 7 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 039b251144..005916822d 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -94,7 +94,7 @@ void Terminal::UpdateSettings(ICoreSettings settings) if (_stateMachine) { - SetVtChecksumReportSupport(settings.AllowVtChecksumReport()); + SetOptionalFeatures(settings); } _getTerminalInput().ForceDisableWin32InputMode(settings.ForceVTInput()); @@ -232,10 +232,13 @@ void Terminal::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) engine.Dispatch().SetCursorStyle(cursorStyle); } -void Terminal::SetVtChecksumReportSupport(const bool enabled) +void Terminal::SetOptionalFeatures(winrt::Microsoft::Terminal::Core::ICoreSettings settings) { auto& engine = reinterpret_cast(_stateMachine->Engine()); - engine.Dispatch().SetVtChecksumReportSupport(enabled); + auto features = til::enumset{}; + features.set(ITermDispatch::OptionalFeature::ChecksumReport, settings.AllowVtChecksumReport()); + features.set(ITermDispatch::OptionalFeature::ClipboardWrite, settings.AllowVtClipboardWrite()); + engine.Dispatch().SetOptionalFeatures(features); } bool Terminal::IsXtermBracketedPasteModeEnabled() const noexcept diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index afbf873da9..e38841e3bc 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -95,7 +95,7 @@ public: void SetHighContrastMode(bool hc) noexcept; void SetFontInfo(const FontInfo& fontInfo); void SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle); - void SetVtChecksumReportSupport(const bool enabled); + void SetOptionalFeatures(winrt::Microsoft::Terminal::Core::ICoreSettings settings); bool IsXtermBracketedPasteModeEnabled() const noexcept; std::wstring_view GetWorkingDirectory() noexcept; diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index da24f71b6f..5ebb2ff31c 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -25,6 +25,12 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch public: using StringHandler = std::function; + enum class OptionalFeature + { + ChecksumReport, + ClipboardWrite, + }; + #pragma warning(push) #pragma warning(disable : 26432) // suppress rule of 5 violation on interface because tampering with this is fraught with peril virtual ~ITermDispatch() = 0; @@ -133,7 +139,6 @@ public: virtual void ScreenAlignmentPattern() = 0; // DECALN virtual void SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) = 0; // DECSCUSR - virtual void SetVtChecksumReportSupport(const bool enabled) = 0; virtual void SetClipboard(wil::zwstring_view content) = 0; // OSCSetClipboard @@ -185,6 +190,8 @@ public: virtual StringHandler RestorePresentationState(const DispatchTypes::PresentationReportFormat format) = 0; // DECRSPS virtual void PlaySounds(const VTParameters parameters) = 0; // DECPS + + virtual void SetOptionalFeatures(const til::enumset features) = 0; }; inline Microsoft::Console::VirtualTerminal::ITermDispatch::~ITermDispatch() = default; #pragma warning(pop) diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index bc03ae8331..cb8e4feb5a 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1297,11 +1297,6 @@ void AdaptDispatch::SelectAttributeChangeExtent(const DispatchTypes::ChangeExten } } -void AdaptDispatch::SetVtChecksumReportSupport(const bool enabled) noexcept -{ - _vtChecksumReportEnabled = enabled; -} - // Routine Description: // - DECRQCRA - Computes and reports a checksum of the specified area of // the buffer memory. @@ -1318,7 +1313,7 @@ void AdaptDispatch::RequestChecksumRectangularArea(const VTInt id, const VTInt p // If this feature is not enabled, we'll just report a zero checksum. if constexpr (Feature_VtChecksumReport::IsEnabled()) { - if (_vtChecksumReportEnabled) + if (_optionalFeatures.test(OptionalFeature::ChecksumReport)) { // If the page number is 0, then we're meant to return a checksum of all // of the pages, but we have no need for that, so we'll just return 0. @@ -1483,8 +1478,16 @@ void AdaptDispatch::DeviceAttributes() // 28 = Rectangular area operations // 32 = Text macros // 42 = ISO Latin-2 character set + // 52 = Clipboard access - _ReturnCsiResponse(L"?61;4;6;7;14;21;22;23;24;28;32;42c"); + if (_optionalFeatures.test(OptionalFeature::ClipboardWrite)) + { + _ReturnCsiResponse(L"?61;4;6;7;14;21;22;23;24;28;32;42;52c"); + } + else + { + _ReturnCsiResponse(L"?61;4;6;7;14;21;22;23;24;28;32;42c"); + } } // Routine Description: @@ -4790,3 +4793,8 @@ void AdaptDispatch::PlaySounds(const VTParameters parameters) _api.PlayMidiNote(noteNumber, noteNumber == 71 ? 0 : velocity, duration); }); } + +void AdaptDispatch::SetOptionalFeatures(const til::enumset features) noexcept +{ + _optionalFeatures = features; +} diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 313611a732..af726c0e01 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -188,7 +188,7 @@ namespace Microsoft::Console::VirtualTerminal void PlaySounds(const VTParameters parameters) override; // DECPS - void SetVtChecksumReportSupport(const bool enabled) noexcept override; + void SetOptionalFeatures(const til::enumset features) noexcept override; private: enum class Mode @@ -313,7 +313,7 @@ namespace Microsoft::Console::VirtualTerminal std::unique_ptr _fontBuffer; std::shared_ptr _macroBuffer; std::optional _initialCodePage; - bool _vtChecksumReportEnabled = false; + til::enumset _optionalFeatures = { OptionalFeature::ClipboardWrite }; // We have two instances of the saved cursor state, because we need // one for the main buffer (at index 0), and another for the alt buffer diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index 0f3908331b..99c9033fee 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -178,7 +178,7 @@ public: void PlaySounds(const VTParameters /*parameters*/) override{}; // DECPS - void SetVtChecksumReportSupport(const bool /*enabled*/) override{}; + void SetOptionalFeatures(const til::enumset /*features*/) override{}; }; #pragma warning(default : 26440) // Restore "can be declared noexcept" warning diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index b288561359..a6eecb6b37 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -415,7 +415,6 @@ public: auto& renderer = _testGetSet->_renderer; auto& renderSettings = renderer._renderSettings; auto adapter = std::make_unique(*_testGetSet, &renderer, renderSettings, _terminalInput); - adapter->SetVtChecksumReportSupport(true); fSuccess = adapter.get() != nullptr; if (fSuccess) @@ -1737,11 +1736,15 @@ public: Log::Comment(L"Test 1: Verify normal response."); _testGetSet->PrepData(); _pDispatch->DeviceAttributes(); + _testGetSet->ValidateInputEvent(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42;52c"); - auto pwszExpectedResponse = L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c"; - _testGetSet->ValidateInputEvent(pwszExpectedResponse); + Log::Comment(L"Test 2: Verify response with clipboard disabled."); + _testGetSet->PrepData(); + _pDispatch->SetOptionalFeatures({}); + _pDispatch->DeviceAttributes(); + _testGetSet->ValidateInputEvent(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c"); - Log::Comment(L"Test 2: Verify failure when ReturnResponse doesn't work."); + Log::Comment(L"Test 3: Verify failure when ReturnResponse doesn't work."); _testGetSet->PrepData(); _testGetSet->_returnResponseResult = FALSE; @@ -2187,6 +2190,8 @@ public: using namespace std::string_view_literals; + _pDispatch->SetOptionalFeatures(ITermDispatch::OptionalFeature::ChecksumReport); + Log::Comment(L"Test 1: ASCII characters"); outputText(L"A"sv); verifyChecksumReport(L"FF4F"); From 1980e725ab085acdeda96b7b0fa2526996279456 Mon Sep 17 00:00:00 2001 From: Chawye Hsu Date: Tue, 24 Jun 2025 07:55:46 +0800 Subject: [PATCH 21/74] schema: add Microsoft.WSL profile source to schema, width its type (#19047) WSL now generates profiles with the source named `Microsoft.WSL`, this PR adds the value to the profile schema. Refs #18231 --- doc/cascadia/profiles.schema.json | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index d60db96aac..a5011bb980 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -32,13 +32,21 @@ ] }, "DynamicProfileSource": { - "enum": [ - "Windows.Terminal.Wsl", - "Windows.Terminal.Azure", - "Windows.Terminal.PowershellCore", - "Windows.Terminal.VisualStudio" - ], - "type": "string" + "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "Microsoft.WSL", + "Windows.Terminal.Wsl", + "Windows.Terminal.Azure", + "Windows.Terminal.PowershellCore", + "Windows.Terminal.VisualStudio" + ] + } + ] }, "BellStyle": { "oneOf": [ From 3680e13bc0bbd0a68b69858623f738f1d8a6ead4 Mon Sep 17 00:00:00 2001 From: "S. M. Mohiuddin Khan Shiam" <147746955+mohiuddin-khan-shiam@users.noreply.github.com> Date: Wed, 25 Jun 2025 01:19:22 +0600 Subject: [PATCH 22/74] hygiene: fix mutable default argument in `vttests/common.py::sgr_n` (#19028) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sgr_n previously used a mutable list (`seq=[]`) as its default parameter. In Python, default arguments are instantiated once at function‐definition time and then shared across all calls. If any caller mutated that list (e.g., `seq.append(...)`), later invocations would inherit the mutated state, producing unpredictable or corrupted VT-test sequences. --- src/tools/vttests/common.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/tools/vttests/common.py b/src/tools/vttests/common.py index 2ecd854145..b08d23618b 100644 --- a/src/tools/vttests/common.py +++ b/src/tools/vttests/common.py @@ -59,7 +59,18 @@ def clear_all(): def sgr(code=0): csi('{}m'.format(code)) -def sgr_n(seq=[]): +def sgr_n(seq=None): + """Apply multiple SGR (Select Graphic Rendition) parameters. + + Parameters + ---------- + seq : Iterable of int | None + Sequence of numeric SGR codes to emit. If ``None`` (default) an empty + sequence is assumed. A mutable default argument must be avoided to + prevent unwanted state sharing between calls. + """ + if seq is None: + seq = [] csi('{}m'.format(';'.join(str(code) for code in seq))) def tbc(): From 218c9fbe3ecd79cc15d9cab267753b1b68ead250 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 24 Jun 2025 13:06:03 -0700 Subject: [PATCH 23/74] Display a warning if SUI is unable to write to the settings file (#19027) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds logic to display a warning popup if the settings.json is marked as read-only and we try to write to the settings.json file. Previously, this scenario would crash, which definitely isn't right. However, a simple fix of "not-crashing" wouldn't feel right either. This leverages the existing infrastructure to display a warning dialog when we failed to write to the settings file. The main annoyance here is that that popup dialog is located in `TerminalWindow` and is normally triggered from a failed `SettingsLoadEventArgs`. To get around this, `CascadiaSettings::WriteSettingsToDisk()` now returns a boolean to signal if the write was successful; whereas if it fails, a warning is added to the settings object. If we fail to write to disk, the function will return false and we'll raise an event with the settings' warnings to `TerminalPage` which passes it along to `TerminalWindow`. Additionally, this uses `IVectorView` as opposed to `IVector` throughout the relevant code. It's more correct as the list of warnings shouldn't be mutable and the warnings from the `CascadiaSettings` object are retrieved in that format. ## Validation Steps Performed - ✅ Using SUI, save settings when the settings.json is set to read-only Closes #18913 --- src/cascadia/TerminalApp/AppLogic.cpp | 6 ++--- .../TerminalApp/SettingsLoadEventArgs.h | 4 ++-- src/cascadia/TerminalApp/TerminalPage.cpp | 7 ++++++ src/cascadia/TerminalApp/TerminalPage.h | 1 + src/cascadia/TerminalApp/TerminalPage.idl | 1 + src/cascadia/TerminalApp/TerminalWindow.cpp | 7 +++--- src/cascadia/TerminalApp/TerminalWindow.h | 2 +- src/cascadia/TerminalApp/TerminalWindow.idl | 2 +- .../TerminalSettingsEditor/MainPage.cpp | 5 +++- .../TerminalSettingsEditor/MainPage.h | 1 + .../TerminalSettingsEditor/MainPage.idl | 1 + .../TerminalSettingsModel/CascadiaSettings.h | 2 +- .../CascadiaSettings.idl | 2 +- .../CascadiaSettingsSerialization.cpp | 24 ++++++++++--------- src/inc/til/io.h | 6 ++++- 15 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index b7e0eae91a..6a0d456142 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -392,7 +392,7 @@ namespace winrt::TerminalApp::implementation auto ev = winrt::make_self(true, static_cast(_settingsLoadedResult), _settingsLoadExceptionText, - warnings, + warnings.GetView(), _settings); SettingsChanged.raise(*this, *ev); return; @@ -424,7 +424,7 @@ namespace winrt::TerminalApp::implementation auto ev = winrt::make_self(!initialLoad, _settingsLoadedResult, _settingsLoadExceptionText, - warnings, + warnings.GetView(), _settings); SettingsChanged.raise(*this, *ev); } @@ -491,7 +491,7 @@ namespace winrt::TerminalApp::implementation auto ev = winrt::make_self(false, _settingsLoadedResult, _settingsLoadExceptionText, - warnings, + warnings.GetView(), _settings); auto window = winrt::make_self(*ev, _contentManager); diff --git a/src/cascadia/TerminalApp/SettingsLoadEventArgs.h b/src/cascadia/TerminalApp/SettingsLoadEventArgs.h index e094810784..f9436f95ae 100644 --- a/src/cascadia/TerminalApp/SettingsLoadEventArgs.h +++ b/src/cascadia/TerminalApp/SettingsLoadEventArgs.h @@ -12,14 +12,14 @@ namespace winrt::TerminalApp::implementation WINRT_PROPERTY(bool, Reload, false); WINRT_PROPERTY(uint64_t, Result, S_OK); WINRT_PROPERTY(winrt::hstring, ExceptionText, L""); - WINRT_PROPERTY(winrt::Windows::Foundation::Collections::IVector, Warnings, nullptr); + WINRT_PROPERTY(winrt::Windows::Foundation::Collections::IVectorView, Warnings, nullptr); WINRT_PROPERTY(Microsoft::Terminal::Settings::Model::CascadiaSettings, NewSettings, nullptr); public: SettingsLoadEventArgs(bool reload, uint64_t result, winrt::hstring exceptionText, - winrt::Windows::Foundation::Collections::IVector warnings, + winrt::Windows::Foundation::Collections::IVectorView warnings, Microsoft::Terminal::Settings::Model::CascadiaSettings newSettings) : _Reload{ reload }, _Result{ result }, diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 702b4a36ee..810ce5272b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -4165,6 +4165,13 @@ namespace winrt::TerminalApp::implementation } }); + sui.ShowLoadWarningsDialog([weakThis{ get_weak() }](auto&& /*s*/, const Windows::Foundation::Collections::IVectorView& warnings) { + if (auto page{ weakThis.get() }) + { + page->ShowLoadWarningsDialog.raise(*page, warnings); + } + }); + return *settingsContent; } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index b369bd920e..1fd773fb3b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -187,6 +187,7 @@ namespace winrt::TerminalApp::implementation til::typed_event OpenSystemMenu; til::typed_event QuitRequested; til::typed_event ShowWindowChanged; + til::typed_event> ShowLoadWarningsDialog; til::typed_event RequestMoveContent; til::typed_event RequestReceiveContent; diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 2788cedd10..f1bd4fbc66 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -97,6 +97,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler OpenSystemMenu; event Windows.Foundation.TypedEventHandler ShowWindowChanged; + event Windows.Foundation.TypedEventHandler > ShowLoadWarningsDialog; event Windows.Foundation.TypedEventHandler RequestMoveContent; event Windows.Foundation.TypedEventHandler RequestReceiveContent; diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 65518b5fdc..88a839949c 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -220,6 +220,7 @@ namespace winrt::TerminalApp::implementation _root->Initialized({ get_weak(), &TerminalWindow::_pageInitialized }); _root->WindowSizeChanged({ get_weak(), &TerminalWindow::_WindowSizeChanged }); _root->RenameWindowRequested({ get_weak(), &TerminalWindow::_RenameWindowRequested }); + _root->ShowLoadWarningsDialog({ get_weak(), &TerminalWindow::_ShowLoadWarningsDialog }); _root->Create(); AppLogic::Current()->SettingsChanged({ get_weak(), &TerminalWindow::UpdateSettingsHandler }); @@ -478,7 +479,7 @@ namespace winrt::TerminalApp::implementation // validating the settings. // - Only one dialog can be visible at a time. If another dialog is visible // when this is called, nothing happens. See ShowDialog for details - void TerminalWindow::_ShowLoadWarningsDialog(const Windows::Foundation::Collections::IVector& warnings) + void TerminalWindow::_ShowLoadWarningsDialog(const IInspectable&, const Windows::Foundation::Collections::IVectorView& warnings) { auto title = RS_(L"SettingsValidateErrorTitle"); auto buttonText = RS_(L"Ok"); @@ -539,7 +540,7 @@ namespace winrt::TerminalApp::implementation } else if (settingsLoadedResult == S_FALSE) { - _ShowLoadWarningsDialog(_initialLoadResult.Warnings()); + _ShowLoadWarningsDialog(nullptr, _initialLoadResult.Warnings()); } } @@ -825,7 +826,7 @@ namespace winrt::TerminalApp::implementation } else if (args.Result() == S_FALSE) { - _ShowLoadWarningsDialog(args.Warnings()); + _ShowLoadWarningsDialog(nullptr, args.Warnings()); } else if (args.Result() == S_OK) { diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index fd27617846..a7db7a8034 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -189,7 +189,7 @@ namespace winrt::TerminalApp::implementation const winrt::hstring& contentKey, HRESULT settingsLoadedResult, const winrt::hstring& exceptionText); - void _ShowLoadWarningsDialog(const Windows::Foundation::Collections::IVector& warnings); + void _ShowLoadWarningsDialog(const IInspectable& sender, const Windows::Foundation::Collections::IVectorView& warnings); bool _IsKeyboardServiceEnabled(); diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index baf58317df..35ccfe9340 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -30,7 +30,7 @@ namespace TerminalApp { Boolean Reload { get; }; UInt64 Result { get; }; - IVector Warnings { get; }; + IVectorView Warnings { get; }; String ExceptionText { get; }; Microsoft.Terminal.Settings.Model.CascadiaSettings NewSettings { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 098827d9a9..b5fdc867b6 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -680,7 +680,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void MainPage::SaveButton_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*args*/) { _settingsClone.LogSettingChanges(false); - _settingsClone.WriteSettingsToDisk(); + if (!_settingsClone.WriteSettingsToDisk()) + { + ShowLoadWarningsDialog.raise(*this, _settingsClone.Warnings()); + } } void MainPage::ResetButton_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*args*/) diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index aebeb516e3..ff87f4910b 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -46,6 +46,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Editor::ExtensionsViewModel ExtensionsVM() const noexcept { return _extensionsVM; } til::typed_event OpenJson; + til::typed_event> ShowLoadWarningsDialog; private: Windows::Foundation::Collections::IObservableVector _breadcrumbs; diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.idl b/src/cascadia/TerminalSettingsEditor/MainPage.idl index 390ae5cb80..20f3bab869 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.idl +++ b/src/cascadia/TerminalSettingsEditor/MainPage.idl @@ -39,6 +39,7 @@ namespace Microsoft.Terminal.Settings.Editor void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings); event Windows.Foundation.TypedEventHandler OpenJson; + event Windows.Foundation.TypedEventHandler > ShowLoadWarningsDialog; // Due to the aforementioned bug, we can't use IInitializeWithWindow _here_ either. // Let's just smuggle the HWND in as a UInt64 :| diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index a4fd16bd43..defd04af69 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -167,7 +167,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::Windows::Foundation::Collections::IVectorView Extensions(); void ResetApplicationState() const; void ResetToDefaultSettings(); - void WriteSettingsToDisk(); + bool WriteSettingsToDisk(); Json::Value ToJson() const; Model::Profile ProfileDefaults() const; Model::Profile CreateNewProfile(); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index 95165bcc80..d1f4304ba9 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -31,7 +31,7 @@ namespace Microsoft.Terminal.Settings.Model CascadiaSettings Copy(); void ResetApplicationState(); void ResetToDefaultSettings(); - void WriteSettingsToDisk(); + Boolean WriteSettingsToDisk(); void LogSettingChanges(Boolean isJsonLoad); String Hash { get; }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 6e03785dec..bbbb9172c7 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -1226,15 +1226,7 @@ try // settings string back to the file. if (mustWriteToDisk) { - try - { - settings->WriteSettingsToDisk(); - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - settings->_warnings.Append(SettingsLoadWarnings::FailedToWriteToSettings); - } + settings->WriteSettingsToDisk(); } else { @@ -1539,7 +1531,7 @@ void CascadiaSettings::ResetToDefaultSettings() // - // Return Value: // - -void CascadiaSettings::WriteSettingsToDisk() +bool CascadiaSettings::WriteSettingsToDisk() { // write current settings to current settings file Json::StreamWriterBuilder wbuilder; @@ -1547,7 +1539,17 @@ void CascadiaSettings::WriteSettingsToDisk() wbuilder.settings_["indentation"] = " "; wbuilder.settings_["precision"] = 6; // prevent values like 1.1000000000000001 - _writeSettingsToDisk(Json::writeString(wbuilder, ToJson())); + try + { + _writeSettingsToDisk(Json::writeString(wbuilder, ToJson())); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + _warnings.Append(SettingsLoadWarnings::FailedToWriteToSettings); + return false; + } + return true; } void CascadiaSettings::_writeSettingsToDisk(std::string_view contents) diff --git a/src/inc/til/io.h b/src/inc/til/io.h index 6f917f0d0b..9e28b99630 100644 --- a/src/inc/til/io.h +++ b/src/inc/til/io.h @@ -256,7 +256,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" // renaming one is (supposed to be) atomic. // Wait... "supposed to be"!? Well it's technically not always atomic, // but it's pretty darn close to it, so... better than nothing. - std::filesystem::rename(tmpPath, resolvedPath); + std::filesystem::rename(tmpPath, resolvedPath, ec); + if (ec) + { + THROW_WIN32_MSG(ec.value(), "failed to write to file"); + } } } // io } // til From 9c452cd985bb0c602bb17b1465cd6b66a1ae272e Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:54:04 -0400 Subject: [PATCH 24/74] Upgrade to check-spelling v0.0.25 (#18940) - Various spelling fixes - Refresh metadata (including dictionaries) - Upgrade to v0.0.25 ## Validation Steps Performed - check-spelling has been automatically testing this repository for a while now on a daily basis to ensure that it works fairly reliably: https://github.com/check-spelling-sandbox/autotest-check-spelling/actions/workflows/microsoft-terminal-spelling2.yml Specific in-code fixes: - winget - whereas - tl;dr - set up - otherwise, - more, - macbook - its - invalid - in order to - if - if the - for this tab,... - fall back - course, - cch - aspect - archaeologists - an - all at once - a - `...` - ; otherwise, Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- .github/actions/spelling/allow/allow.txt | 19 -- .github/actions/spelling/allow/apis.txt | 72 ------ .github/actions/spelling/allow/chinese.txt | 5 +- .github/actions/spelling/allow/colors.txt | 9 - .github/actions/spelling/allow/fonts.txt | 2 +- .github/actions/spelling/allow/japanese.txt | 3 - .github/actions/spelling/allow/math.txt | 9 - .github/actions/spelling/allow/microsoft.txt | 29 +-- .github/actions/spelling/allow/names.txt | 34 +-- .github/actions/spelling/candidate.patterns | 89 +++++-- .github/actions/spelling/excludes.txt | 4 +- .github/actions/spelling/expect/alphabet.txt | 41 +-- .github/actions/spelling/expect/expect.txt | 184 ++------------ .../actions/spelling/line_forbidden.patterns | 232 ++++++++++++++++- .../actions/spelling/patterns/patterns.txt | 239 ++++++++++-------- .github/actions/spelling/reject.txt | 12 +- .github/workflows/spelling2.yml | 39 ++- .github/workflows/winget.yml | 2 +- build/Helix/HelixTestHelpers.cs | 92 +++---- .../job-pgo-build-nuget-and-publish.yml | 2 +- doc/Niksa.md | 40 +-- doc/WindowsTestPasses.md | 6 +- doc/creating_a_new_project.md | 6 +- .../#1564 - Settings UI/cascading-settings.md | 2 +- doc/specs/#1595 - Suggestions UI/Snippets.md | 2 +- .../#1790 - Font features and axes-spec.md | 6 +- ... - Theme-controlled color scheme switch.md | 2 +- ...2 - Windows Terminal Session Management.md | 2 +- .../#5000 - Process Model 2.0.md | 4 +- doc/specs/#605 - Search/spec.md | 2 +- .../#7335 - Console Allocation Policy.md | 2 +- .../#754 - Cascading Default Settings.md | 2 +- .../#885 - Terminal Settings Model.md | 6 +- .../#2634 - Broadcast Input.md | 6 +- doc/terminal-a11y-2023.md | 12 +- policies/en-US/WindowsTerminal.adml | 2 +- .../GUIConsole/GUIConsole.WPF/MainWindow.xaml | 2 +- src/buffer/out/textBuffer.cpp | 2 +- src/buffer/out/ut_textbuffer/ReflowTests.cpp | 2 +- .../ShellExtension/OpenTerminalHere.cpp | 2 +- .../TerminalApp/AppActionHandlers.cpp | 2 +- src/cascadia/TerminalApp/AppLogic.cpp | 2 +- src/cascadia/TerminalApp/ColorHelper.cpp | 2 +- src/cascadia/TerminalApp/CommandPalette.cpp | 2 +- src/cascadia/TerminalApp/Pane.cpp | 24 +- .../TerminalApp/ShortcutActionDispatch.cpp | 2 +- .../TerminalApp/SuggestionsControl.cpp | 2 +- src/cascadia/TerminalApp/TerminalPage.cpp | 4 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 2 +- src/cascadia/TerminalControl/ControlCore.cpp | 4 +- .../TerminalControl/ControlInteractivity.h | 2 +- .../HwndTerminalAutomationPeer.cpp | 2 +- src/cascadia/TerminalControl/TermControl.cpp | 6 +- src/cascadia/TerminalControl/TermControl.h | 2 +- .../TermControlAutomationPeer.cpp | 4 +- src/cascadia/TerminalCore/Terminal.cpp | 8 +- src/cascadia/TerminalCore/TerminalApi.cpp | 2 +- .../TerminalCore/TerminalSelection.cpp | 2 +- .../TerminalSettingsEditor/MainPage.cpp | 2 +- .../TerminalSettingsModel/ActionMap.cpp | 4 +- .../CascadiaSettingsSerialization.cpp | 2 +- .../TerminalSettingsModel/Command.cpp | 4 +- .../TerminalSettingsModel/JsonUtils.h | 2 +- .../TerminalSettingsModel/Profile.cpp | 2 +- .../TerminalSettings.cpp | 2 +- src/cascadia/TerminalSettingsModel/Theme.cpp | 2 +- src/cascadia/WindowsTerminal/AppHost.cpp | 2 +- .../WindowsTerminal/NonClientIslandWindow.cpp | 2 +- .../TerminalControl.xaml.cs | 2 +- src/host/ConsoleArguments.cpp | 10 +- src/host/VtIo.cpp | 6 +- src/host/exe/exemain.cpp | 2 +- src/host/ft_host/API_AliasTestsHelpers.hpp | 2 +- src/host/outputStream.cpp | 2 +- src/host/readDataCooked.cpp | 2 +- src/host/readDataDirect.cpp | 2 +- src/host/readDataRaw.cpp | 2 +- src/host/screenInfo.cpp | 4 +- src/host/selection.cpp | 2 +- src/host/ut_host/ConsoleArgumentsTests.cpp | 6 +- src/host/ut_host/ScreenBufferTests.cpp | 2 +- src/host/utils.cpp | 2 +- src/host/writeData.cpp | 2 +- src/inc/til/io.h | 2 +- src/inc/til/small_vector.h | 2 +- .../base/InteractivityFactory.cpp | 2 +- src/interactivity/win32/WindowMetrics.cpp | 2 +- src/propsheet/preview.cpp | 2 +- src/propslib/DelegationConfig.cpp | 2 +- src/renderer/atlas/AtlasEngine.cpp | 2 +- src/renderer/atlas/BackendD3D.cpp | 2 +- src/renderer/atlas/common.h | 2 +- src/renderer/base/renderer.cpp | 2 +- src/renderer/gdi/state.cpp | 2 +- src/server/IoDispatchers.cpp | 2 +- src/terminal/adapter/FontBuffer.cpp | 6 +- src/terminal/adapter/MacroBuffer.hpp | 2 +- src/terminal/adapter/adaptDispatch.cpp | 16 +- src/terminal/adapter/ut_adapter/inputTest.cpp | 2 +- src/terminal/input/mouseInput.cpp | 2 +- src/terminal/input/terminalInput.cpp | 2 +- src/terminal/parser/stateMachine.cpp | 2 +- src/tools/U8U16Test/U8U16Test.cpp | 2 +- src/types/UiaTextRangeBase.cpp | 6 +- src/winconpty/winconpty.cpp | 2 +- tools/GenerateAppxFromManifest.ps1 | 4 +- tools/README.md | 2 +- 107 files changed, 702 insertions(+), 744 deletions(-) diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index b347ec85b9..a99422e8b0 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -11,17 +11,11 @@ colorbrewer commandlines consvc copyable -CText -dalet dcs deselection -dialytika diffing Dimidium -dje downsides -dze -dzhe Emacspeak Fitt flac @@ -29,16 +23,13 @@ FTCS gantt gfm ghe -gje godbolt hstrings hyperlinking hyperlinks Kbds -kje libfuzzer liga -lje Llast Lmid locl @@ -50,10 +41,8 @@ minimalistic mkmk mnt mru -nje notwrapped NTMTo -ogonek overlined perlw postmodern @@ -75,18 +64,13 @@ rlig rubyw runtimes servicebus -shcha -similaritytolerance slnt stakeholders subpage sustainability sxn Tencent -TLDR -tonos toolset -tshe UEFI uiatextrange und @@ -94,8 +78,5 @@ vsdevcmd westus workarounds WSLs -wtconfig XBox YBox -yeru -zhe diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 6670909587..336e62ff2a 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -2,11 +2,8 @@ aalt abvm ACCEPTFILES ACCESSDENIED -acl -aclapi alignas alignof -allocconsolewithoptions APPLYTOSUBMENUS appxrecipe bitfield @@ -15,29 +12,20 @@ BUILDBRANCH BUILDMSG BUILDNUMBER BYCOMMAND -BYPOSITION charconv -CLASSNOTAVAILABLE CLOSEAPP cmdletbinding -COLORPROPERTY colspan COMDLG commandlinetoargv -commoncontrols -comparand COPYFROMRESOURCE cstdint CXICON CYICON -Dacl dataobject -dcomp debugbreak delayimp -DERR dlldata -DNE dnom DONTADDTORECENT DWMSBT @@ -64,81 +52,42 @@ GETHIGHCONTRAST GETMOUSEHOVERTIME GETTEXTLENGTH HARDBREAKS -Hashtable HIGHCONTRASTON HIGHCONTRASTW HIGHQUALITYSCALE hinternet -HINTERNET hotkeys href hrgn HTCLOSE hwinsta -HWINSTA -IActivation -IApp IAppearance -IAsync -IBind -IBox -IClass -IComparable -IComparer ICONINFO -IConnection -ICustom -IDialog IDirect -Idn -IExplorer -IFACEMETHOD -IFile -IGraphics -IImage IInheritable -IMap imm -IObject iosfwd -IPackage isa -ISetup isspace -IStorage istream -IStringable -ITab -ITaskbar -itow -IUri -IVirtual KEYSELECT LCID LINEBREAK -llabs -llu -localtime lround Lsa lsass LSHIFT LTGRAY MAINWINDOW -MAXIMIZEBOX medi -memchr memicmp MENUCOMMAND MENUDATA MENUINFO MENUITEMINFOW MINIMIZEBOX -mmeapi MOUSELEAVE mov -mptt -msappx MULTIPLEUSE NCHITTEST NCLBUTTONDBLCLK @@ -148,7 +97,6 @@ NCPOINTERUPDATE NCRBUTTONDBLCLK NIF NIN -NOAGGREGATION NOASYNC NOBREAKS NOCHANGEDIR @@ -161,8 +109,6 @@ NOTIFYICONDATA ntprivapi NTSYSCALLAPI numr -oaidl -ocidl ODR offsetof ofstream @@ -172,22 +118,17 @@ OSVERSIONINFOEXW otms OUTLINETEXTMETRICW overridable -PACL PAGESCROLL PALLOC PATINVERT -PEXPLICIT PICKFOLDERS PINPUT pmr -ptstr QUERYENDSESSION rcx REGCLS RETURNCMD rfind -RLO -rnrn ROOTOWNER roundf RSHIFT @@ -206,22 +147,17 @@ SHOWTIP SINGLEUSE SIZENS smoothstep -snprintf SOFTBREAK spsc -sregex SRWLOC srwlock SRWLOCK STDCPP STDMETHOD -strchr strcpy streambuf strtoul Stubless -Subheader -Subpage syscall syscolors SYSTEMBACKDROP @@ -238,23 +174,17 @@ tokeninfo tolower toupper TRACKMOUSEEVENT -TTask -TVal UChar UFIELD ULARGE UOI UPDATEINIFILE urlmon -userenv USEROBJECTFLAGS Vcpp Viewbox virtualalloc -vsnwprintf wcsnlen -wcsstr -wcstoui WDJ winhttp wininet @@ -264,10 +194,8 @@ winstamin wmemcmp wpc WSF -wsregex WWH wwinmain -xchg XDocument XElement xfacet diff --git a/.github/actions/spelling/allow/chinese.txt b/.github/actions/spelling/allow/chinese.txt index 6b7ca2a917..02ed0e4bae 100644 --- a/.github/actions/spelling/allow/chinese.txt +++ b/.github/actions/spelling/allow/chinese.txt @@ -1,5 +1,4 @@ CHINESEBIG choseong -Jongseong -Jungseong -ssangtikeut +Choseong +CHOSEONG diff --git a/.github/actions/spelling/allow/colors.txt b/.github/actions/spelling/allow/colors.txt index b56723b368..2b7192cf5b 100644 --- a/.github/actions/spelling/allow/colors.txt +++ b/.github/actions/spelling/allow/colors.txt @@ -1,4 +1,3 @@ -alice aliceblue antiquewhite blanchedalmond @@ -39,7 +38,6 @@ gainsboro ghostwhite greenyellow hotpink -indian indianred lavenderblush lawngreen @@ -74,7 +72,6 @@ mediumvioletred midnightblue mintcream mistyrose -navajo navajowhite navyblue oldlace @@ -88,7 +85,6 @@ papayawhip peachpuff peru powderblue -rebecca rebeccapurple rosybrown royalblue @@ -109,9 +105,4 @@ webgrey webmaroon webpurple whitesmoke -xaroon -xray -xreen -xrey -xurple yellowgreen diff --git a/.github/actions/spelling/allow/fonts.txt b/.github/actions/spelling/allow/fonts.txt index c9931b2fc0..a0bacd0600 100644 --- a/.github/actions/spelling/allow/fonts.txt +++ b/.github/actions/spelling/allow/fonts.txt @@ -1,8 +1,8 @@ Consolas emoji emojis +Emojis Extralight -Gabriola Iosevka MDL Monofur diff --git a/.github/actions/spelling/allow/japanese.txt b/.github/actions/spelling/allow/japanese.txt index 261f45dc2a..6d941c08a5 100644 --- a/.github/actions/spelling/allow/japanese.txt +++ b/.github/actions/spelling/allow/japanese.txt @@ -1,4 +1 @@ -arigatoo -doomo -Kaomojis TATEGAKI diff --git a/.github/actions/spelling/allow/math.txt b/.github/actions/spelling/allow/math.txt index bf8960f008..8c6041d465 100644 --- a/.github/actions/spelling/allow/math.txt +++ b/.github/actions/spelling/allow/math.txt @@ -1,11 +1,2 @@ -atan -CPrime -HBar -HPrime isnan -LPrime -LStep -powf -RSub -sqrtf ULP diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt index 940b0fc29d..3f639c4746 100644 --- a/.github/actions/spelling/allow/microsoft.txt +++ b/.github/actions/spelling/allow/microsoft.txt @@ -4,12 +4,11 @@ advapi akv AKV altform -altforms +Altforms appendwttlogging appinstaller appx appxbundle -appxerror appxmanifest ATL autoexec @@ -25,66 +24,43 @@ CPRs cryptbase cscript DACL -DACLs defaultlib diffs disposables -dotnetfeed -DTDs -DWINRT enablewttlogging HOMESHARE Intelli issecret -IVisual libucrt libucrtd -LKG LOCKFILE Lxss makepri -mfcribbon microsoft -microsoftonline MSAA msixbundle MSVC MSVCP mtu muxc -netcore -Onefuzz -osgvsowi -PFILETIME pgc pgo pgosweep -powerrename powershell priconfig PRIINFO propkey pscustomobject QWORD -rdpclip regedit resfiles -robocopy SACLs -sdkddkver segoe -Shobjidl sid -Skype SRW sxs symbolrequestprod Sysinternals -sysnative -systemroot -taskkill -tasklist -tdbuildteamid ucrt ucrtd unvirtualized @@ -92,12 +68,9 @@ USERDNSDOMAIN VCRT vcruntime Virtualization -visualstudio vscode VSTHRD WINBASEAPI -winsdkver -wlk wscript wslpath wtl diff --git a/.github/actions/spelling/allow/names.txt b/.github/actions/spelling/allow/names.txt index 11a1f95486..a97ec94ac9 100644 --- a/.github/actions/spelling/allow/names.txt +++ b/.github/actions/spelling/allow/names.txt @@ -1,4 +1,3 @@ -Anup arkthur austdi Ballmer @@ -6,13 +5,11 @@ bhoj Bhojwani Bluloco carlos -craigloewen dhowett Diviness dsafa duhowett DXP -ekg eryksun ethanschoonover Firefox @@ -25,70 +22,42 @@ Hernan Howett Illhardt Imms -iquilezles italo jantari jerrysh Kaiyu -kimwalisch -KMehrain -Kodelife -KODELIFE -Kourosh -kowalczyk leonardder -leonmsft -Lepilleur lhecker -lukesampson -Macbook -Manandhar masserano -mbadolato -Mehrain menger -mgravell -michaelniksa -michkap migrie mikegr mikemaccana -miloush miniksa nguyen niksa nvaccess nvda -oising -oldnewthing -opengl osgwiki Ottosson pabhojwa -panos +Panos paulcam -pauldotknopf PGP Pham Rincewind -rprichard Schoonover shadertoy Shomnipotence simioni -Somuah sonph sonpham stakx -talo thereses Thysell -Walisch WDX Wellons Westerman -Wirt -Wojciech zadjii Zamor zamora @@ -96,4 +65,3 @@ Zamora zljubisic Zoey zorio -Zverovich diff --git a/.github/actions/spelling/candidate.patterns b/.github/actions/spelling/candidate.patterns index 5d93035f91..d405383514 100644 --- a/.github/actions/spelling/candidate.patterns +++ b/.github/actions/spelling/candidate.patterns @@ -1,3 +1,6 @@ +# Repeated letters +\b([a-z])\g{-1}{2,}\b + # marker to ignore all code on line ^.*/\* #no-spell-check-line \*/.*$ # marker to ignore all code on line @@ -7,6 +10,9 @@ # cspell inline ^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b +# copyright +Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+ + # patch hunk comments ^@@ -\d+(?:,\d+|) \+\d+(?:,\d+|) @@ .* # git index header @@ -15,6 +21,9 @@ index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40} # file permissions ['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s] +# css fonts +\bfont(?:-family|):[^;}]+ + # css url wrappings \burl\([^)]+\) @@ -32,9 +41,6 @@ index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40} # https/http/file urls (?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/*%?=~_|!:,.;]+[-A-Za-z0-9+&@#/*%=~_|] -# https/http/file urls -(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|] - # mailto urls mailto:[-a-zA-Z=;:/?%&0-9+@._]{3,} @@ -69,6 +75,8 @@ magnet:[?=:\w]+ # Amazon \bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|) +# AWS ARN +arn:aws:[-/:\w]+ # AWS S3 \b\w*\.s3[^.]*\.amazonaws\.com/[-\w/&#%_?:=]* # AWS execute-api @@ -95,6 +103,8 @@ vpc-\w+ \bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]* # Google APIs \bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+ +# Google Artifact Registry +\.pkg\.dev(?:/[-\w]+)+(?::[-\w]+|) # Google Storage \b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|) # Google Calendar @@ -130,6 +140,8 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+. \bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+ # Google Colab Research Drive \bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]* +# Google Cloud regions +(?:us|(?:north|south)america|europe|asia|australia|me|africa)-(?:north|south|east|west|central){1,2}\d+ # GitHub SHAs (api) \bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b @@ -168,6 +180,12 @@ GHSA(?:-[0-9a-z]{4}){3} # GitLab commits \bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b +# #includes +^\s*#include\s*(?:<.*?>|".*?") + +# #pragma lib +^\s*#pragma comment\(lib, ".*?"\) + # binance accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* @@ -220,7 +238,7 @@ accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* \bmedium\.com/@?[^/\s"]+/[-\w]+ # microsoft -\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]* +\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]* # powerbi \bapp\.powerbi\.com/reportEmbed/[^"' ]* # vs devops @@ -394,7 +412,7 @@ ipfs://[0-9a-zA-Z]{3,} \bgetopts\s+(?:"[^"]+"|'[^']+') # ANSI color codes -(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m +(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m # URL escaped characters %[0-9A-F][A-F](?=[A-Za-z]) @@ -431,10 +449,14 @@ sha\d+:[0-9a-f]*?[a-f]{3,}[0-9a-f]* # pki (base64) LS0tLS1CRUdJT.* +# C# includes +^\s*using [^;]+; + # uuid: \b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b # hex digits including css/html color classes: -(?:[\\0][xX]|\\u|[uU]\+|#x?|%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b +(?:[\\0][xX]|\\u|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b + # integrity integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1} @@ -452,7 +474,10 @@ integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1} Name\[[^\]]+\]=.* # IServiceProvider / isAThing -(?:\b|_)(?:(?:ns|)I|isA)(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b)) +(?:(?:\b|_|(?<=[a-z]))I|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b)) + +# python +\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,}) # crypt (['"])\$2[ayb]\$.{56}\g{-1} @@ -466,17 +491,14 @@ Name\[[^\]]+\]=.* # machine learning (?) \b(?i)ml(?=[a-z]{2,}) -# python -\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,}) - # scrypt / argon \$(?:scrypt|argon\d+[di]*)\$\S+ # go.sum \bh1:\S+ -# scala imports -^import (?:[\w.]|\{\w*?(?:,\s*(?:\w*|\*))+\})+ +# imports +^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+ # scala modules ("[^"]+"\s*%%?\s*){2,3}"[^"]+" @@ -485,7 +507,7 @@ Name\[[^\]]+\]=.* image: [-\w./:@]+ # Docker images -^\s*FROM\s+\S+:\S+(?:\s+AS\s+\S+|) +^\s*(?i)FROM\s+\S+:\S+(?:\s+AS\s+\S+|) # `docker images` REPOSITORY TAG IMAGE ID CREATED SIZE \s*\S+/\S+\s+\S+\s+[0-9a-f]{8,}\s+\d+\s+(?:hour|day|week)s ago\s+[\d.]+[KMGT]B @@ -501,6 +523,7 @@ content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1} # The `(?=.*?")` suffix should limit the false positives rate # printf #%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA])(?=[a-zA-Z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%)\b)(?=.*?['"]) + # Alternative printf # %s %(?:s(?=[a-z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%[^s])\b)(?=.*?['"]) @@ -524,7 +547,7 @@ content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1} # javascript replace regex \.replace\(/[^/\s"]{3,}/[gim]*\s*, # assign regex -= /[^*].*?(?:[a-z]{3,}|[A-Z]{3,}|[A-Z][a-z]{2,}).*/[gi]?(?=\W|$) += /[^*].*?(?:[a-z]{3,}|[A-Z]{3,}|[A-Z][a-z]{2,}).*/[gim]*(?=\W|$) # perl regex test [!=]~ (?:/.*/|m\{.*?\}|m<.*?>|m([|!/@#,;']).*?\g{-1}) @@ -538,7 +561,7 @@ perl(?:\s+-[a-zA-Z]\w*)+ (?:\d|\bh)to(?!ken)(?=[a-z])|to(?=[adhiklpun]\() # Go regular expressions -regexp?\.MustCompile\(`[^`]*`\) +regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\) # regex choice \(\?:[^)]+\|[^)]+\) @@ -586,7 +609,7 @@ urn:shemas-jetbrains-com # xcode # xcodeproject scenes -(?:Controller|destination|ID|id)="\w{3}-\w{2}-\w{3}" +(?:Controller|destination|(?:first|second)Item|ID|id)="\w{3}-\w{2}-\w{3}" # xcode api botches customObjectInstantitationMethod @@ -601,14 +624,17 @@ PrependWithABINamepsace \.fa-[-a-z0-9]+ # bearer auth -(['"])[Bb]ear[e][r] .*?\g{-1} +(['"])[Bb]ear[e][r] .{3,}?\g{-1} # bearer auth -\b[Bb]ear[e][r]:? [-a-zA-Z=;:/0-9+.]+ +\b[Bb]ear[e][r]:? [-a-zA-Z=;:/0-9+.]{3,} # basic auth (['"])[Bb]asic [-a-zA-Z=;:/0-9+]{3,}\g{-1} +# basic auth +: [Bb]asic [-a-zA-Z=;:/0-9+.]{3,} + # base64 encoded content #([`'"])[-a-zA-Z=;:/0-9+]{3,}=\g{-1} # base64 encoded content in xml/sgml @@ -620,6 +646,9 @@ PrependWithABINamepsace # base64 encoded pkcs #\bMII[-a-zA-Z=;:/0-9+]+ +# uuencoded +#[!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_]{40,} + # DNS rr data #(?:\d+\s+){3}(?:[-+/=.\w]{2,}\s*){1,2} @@ -630,7 +659,7 @@ PrependWithABINamepsace \bnumer\b(?=.*denom) # Time Zones -\b(?:Africa|Atlantic|America|Antarctica|Asia|Australia|Europe|Indian|Pacific)(?:/\w+)+ +\b(?:Africa|Atlantic|America|Antarctica|Arctic|Asia|Australia|Europe|Indian|Pacific)(?:/[-\w]+)+ # linux kernel info ^(?:bugs|flags|Features)\s+:.* @@ -676,11 +705,17 @@ TeX/AMS "varsIgnorePattern": ".+" # nolint -nolint:\w+ +nolint:\s*[\w,]+ # Windows short paths [/\\][^/\\]{5,6}~\d{1,2}(?=[/\\]) +# Windows Resources with accelerators +\b[A-Z]&[a-z]+\b(?!;) + +# signed off by +(?i)Signed-off-by: .* + # cygwin paths /cygdrive/[a-zA-Z]/(?:Program Files(?: \(.*?\)| ?)(?:/[-+.~\\/()\w ]+)*|[-+.~\\/()\w])+ @@ -715,29 +750,29 @@ W/"[^"]+" # Compiler flags (Unix, Java/Scala) # Use if you have things like `-Pdocker` and want to treat them as `docker` -#(?:^|[\t ,>"'`=(])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) +#(?:^|[\t ,>"'`=(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) # Compiler flags (Windows / PowerShell) # This is a subset of the more general compiler flags pattern. # It avoids matching `-Path` to prevent it from being treated as `ath` -#(?:^|[\t ,"'`=(])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})) +#(?:^|[\t ,"'`=(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})) # Compiler flags (linker) ,-B # libraries -#(?:\b|_)lib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z]) - -# WWNN/WWPN (NAA identifiers) -\b(?:0x)?10[0-9a-f]{14}\b|\b(?:0x|3)?[25][0-9a-f]{15}\b|\b(?:0x|3)?6[0-9a-f]{31}\b +#(?:\b|_)[Ll]ib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z]) # iSCSI iqn (approximate regex) \biqn\.[0-9]{4}-[0-9]{2}(?:[\.-][a-z][a-z0-9]*)*\b +# WWNN/WWPN (NAA identifiers) +\b(?:0x)?10[0-9a-f]{14}\b|\b(?:0x|3)?[25][0-9a-f]{15}\b|\b(?:0x|3)?6[0-9a-f]{31}\b + # curl arguments \b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)* # set arguments -\b(?:bash|sh|set)(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)* +\b(?:bash|sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)* # tar arguments \b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ # tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long... diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index c142f11548..23511535fb 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -97,7 +97,7 @@ Resources/(?!en) ^doc/reference/UTF8-torture-test\.txt$ ^doc/reference/windows-terminal-logo\.ans$ ^NOTICE.md -^oss/ +^oss/.*?/ ^samples/PixelShaders/Screenshots/ ^src/cascadia/TerminalSettingsEditor/SegoeFluentIconList.h$ ^src/interactivity/onecore/BgfxEngine\. @@ -121,8 +121,8 @@ Resources/(?!en) ^tools/ReleaseEngineering/ServicingPipeline\.ps1$ ^XamlStyler\.json$ ^\.github/actions/spelling/ +^\.github/workflows/spelling\d*\.yml$ ^\.vsconfig$ -^\Q.github/workflows/spelling.yml\E$ ^\Qbuild/config/release.gdnbaselines\E$ ^\Qdep/WinAppDriver/EULA.rtf\E$ ^\Qdoc/reference/windows-terminal-logo.ans\E$ diff --git a/.github/actions/spelling/expect/alphabet.txt b/.github/actions/spelling/expect/alphabet.txt index 1ff5611c38..73bbdffb3c 100644 --- a/.github/actions/spelling/expect/alphabet.txt +++ b/.github/actions/spelling/expect/alphabet.txt @@ -1,38 +1,19 @@ -AAAAAABBBBBBCCC -AAAAABBBBBBCCC -abcd -ABCDEFGHIJ -abcdefghijk -ABCDEFGHIJKLMNOPQRS -ABCDEFGHIJKLMNOPQRST -ABCDEFGHIJKLMNOPQRSTUVWXY ABCG ABE -abf -BBBBB -BBBBBCCC -BBBBCCCCC +AZZ +BBDM BBGGRR -EFG +CBN +cbt +Ccc +cch efg -EFGh efgh -KLMNOQQQQQQQQQQ -QQQQQQQQQQABCDEFGHIJ -QQQQQQQQQQABCDEFGHIJKLMNOPQRS -QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ -QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ -QQQQQQQQQQABCDEFGHIJPQRST -QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ +fdw +fesb +ffd +FFFD qwerty qwertyuiopasdfg -ZAAZZ -ZABBZ -ZBAZZ -ZBBBZ -ZBBZZ ZYXWVUT -ZZBBZ -ZZZBB -ZZZBZ -ZZZZZ +zzf diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 318903fe17..3507bf387b 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -2,7 +2,6 @@ aaaaabbb aabbcc ABANDONFONT abbcc -ABCF abgr ABORTIFHUNG ACCESSTOKEN @@ -26,13 +25,11 @@ AImpl AInplace ALIGNRIGHT allocing -allocs alpc ALTERNATENAME ALTF ALTNUMPAD ALWAYSTIP -aml ansicpg ANSISYS ANSISYSRC @@ -49,22 +46,17 @@ APIENTRY apiset APPBARDATA appcontainer -appium appletname -applicationmodel APPLMODAL Applocal appmodel +appshellintegration APPWINDOW APPXMANIFESTVERSION APrep -apsect APSTUDIO -archeologists -Argb ARRAYSIZE ARROWKEYS -asan ASBSET ASetting ASingle @@ -72,11 +64,11 @@ ASYNCDONTCARE ASYNCWINDOWPOS atch ATest +atg aumid Authenticode AUTOBUDDY AUTOCHECKBOX -autocrlf autohide AUTOHSCROLL automagically @@ -92,10 +84,8 @@ AZZ backgrounded Backgrounder backgrounding -backported backstory Bazz -bbb bbccb BBDM bbwe @@ -123,13 +113,13 @@ bitmasks BITOPERATION BKCOLOR BKGND +BKMK Bksp Blt blu BLUESCROLL bmi bodgy -BODGY BOLDFONT Borland boutput @@ -148,9 +138,7 @@ buflen buildsystems buildtransitive BValue -bytebuffer -cac -cacafire +Cacafire CALLCONV CANDRABINDU capslock @@ -161,12 +149,6 @@ catid cazamor CBash cbiex -CBN -cbt -Ccc -CCCBB -CCCDDD -cch CCHAR CCmd ccolor @@ -181,15 +163,14 @@ cfie cfiex cfte CFuzz +cgmanifest cgscrn chafa changelists CHARSETINFO -chh chshdng CHT CLASSSTRING -CLE cleartype CLICKACTIVE clickdown @@ -212,9 +193,7 @@ cmw CNL cnn Codeflow -codenav codepages -codepath coinit colorizing COLORONCOLOR @@ -226,13 +205,8 @@ colortbl colortest colortool COLORVALUE -combaseapi comctl -commandline -commctrl commdlg -COMMITID -componentization conapi conattrs conbufferout @@ -250,7 +224,6 @@ conintegrityuwp coninteractivitybase coninteractivityonecore coninteractivitywin -conio coniosrv CONKBD conlibk @@ -263,13 +236,13 @@ conpropsp conpty conptylib conserv +consoleaccessibility consoleapi CONSOLECONTROL CONSOLEENDTASK consolegit consolehost CONSOLEIME -consoleinternal CONSOLESETFOREGROUND consoletaeftemplates consoleuwp @@ -277,7 +250,6 @@ Consolewait CONSOLEWINDOWOWNER consrv constexprable -constness contentfiles conterm contsf @@ -311,7 +283,6 @@ csbi csbiex CSHORT Cspace -csrmsg CSRSS csrutil CSTYLE @@ -368,16 +339,14 @@ DBGFONTS DBGOUTPUT dbh dblclk +DBUILD Dcd DColor -dcommon +DCOMMON DComposition -DDDCCC -dde DDESHARE DDevice DEADCHAR -dealloc Debian debugtype DECAC @@ -482,18 +451,16 @@ DELAYLOAD DELETEONRELEASE depersist deprioritized -deserializers -desktopwindowxamlsource devicecode Dext DFactory DFF dialogbox +DINLINE directio DIRECTX DISABLEDELAYEDEXPANSION DISABLENOSCROLL -DISPATCHNOTIFY DISPLAYATTRIBUTE DISPLAYCHANGE distros @@ -530,9 +497,10 @@ dsm dsound DSSCL DSwap -DTest DTo DTTERM +DUNICODE +DUNIT dup'ed dvi dwl @@ -542,8 +510,6 @@ dwmapi DWORDs dwrite dxgi -dxgidwm -dxinterop dxsm dxttbmp Dyreen @@ -557,7 +523,6 @@ EDITKEYS EDITTEXT EDITUPDATE Efast -efghijklmn EHsc EINS ELEMENTNOTAVAILABLE @@ -566,7 +531,6 @@ enabledelayedexpansion ENDCAP endptr ENTIREBUFFER -entrypoints ENU ENUMLOGFONT ENUMLOGFONTEX @@ -581,7 +545,6 @@ esrp ESV ETW EUDC -EVENTID eventing evflags evt @@ -603,16 +566,6 @@ FACESIZE FAILIFTHERE fastlink fcharset -FDEA -fdw -FECF -FEEF -fesb -FFAF -ffd -FFDE -FFFD -FFFDb fgbg FGCOLOR FGHIJ @@ -630,7 +583,6 @@ FINDDLG FINDDOWN FINDREGEX FINDSTRINGEXACT -FINDUP FITZPATRICK FIXEDFILEINFO Flg @@ -665,7 +617,6 @@ Ftm Fullscreens Fullwidth FUNCTIONCALL -fuzzer fuzzmain fuzzmap fuzzwrapper @@ -729,6 +680,7 @@ GETWHEELSCROLLCHARS GETWHEELSCROLLLINES Gfun gfx +gfycat GGI GHgh GHIJK @@ -749,6 +701,7 @@ Greyscale gridline gset gsl +Guake guc GUIDATOM GValue @@ -778,7 +731,6 @@ hfind hfont hfontresource hglobal -hhh hhook hhx HIBYTE @@ -794,7 +746,6 @@ HKCU hkey hkl HKLM -hlocal hlsl HMB HMK @@ -813,7 +764,6 @@ HREDRAW hresult hscroll hstr -hstring HTBOTTOMLEFT HTBOTTOMRIGHT HTCAPTION @@ -842,14 +792,12 @@ idl idllib IDOK IDR -idth IDTo IDXGI IFACEMETHODIMP ification IGNORELANGUAGE iid -IInput IIo ILC ILCo @@ -863,20 +811,18 @@ INFOEX inheritcursor INITCOMMONCONTROLSEX INITDIALOG -initguid +INITGUID INITMENU inkscape INLINEPREFIX inproc Inputkeyinfo -inputpaneinterop Inputreadhandledata INPUTSCOPE INSERTMODE INTERACTIVITYBASE INTERCEPTCOPYPASTE INTERNALNAME -Interner intsafe INVALIDARG INVALIDATERECT @@ -888,8 +834,6 @@ itermcolors itf Ith IUI -IUnknown -ivalid IWIC IXP jconcpp @@ -913,7 +857,6 @@ keydowns KEYFIRST KEYLAST Keymapping -keyscan keystate keyups Kickstart @@ -923,10 +866,6 @@ kinda KIYEOK KLF KLMNO -KLMNOPQRST -KLMNOPQRSTQQQQQ -KLMNOPQRSTUVWXY -KLMNOPQRSTY KOK KPRIORITY KVM @@ -948,6 +887,8 @@ LCONTROL LCTRL lcx LEFTALIGN +libsancov +libtickit LIMITTEXT LINEDOWN LINESELECTION @@ -1052,6 +993,7 @@ MBUTTONDOWN MBUTTONUP mdmerge MDs +mdtauk MEASUREITEM megamix memallocator @@ -1061,7 +1003,6 @@ MENUCONTROL MENUDROPALIGNMENT MENUITEMINFO MENUSELECT -messageext metaproj Mgrs microsoftpublicsymbols @@ -1078,11 +1019,9 @@ minwindef MMBB mmcc MMCPL -mmsystem MNC MNOPQ MNOPQR -MNOPQRSTUVWXY MODALFRAME MODERNCORE MONITORINFO @@ -1094,7 +1033,6 @@ MOUSEHWHEEL MOVESTART msb msbuildcache -msctf msctls msdata MSDL @@ -1109,10 +1047,9 @@ MSGSELECTMODE msiexec MSIL msix -msrc +MSRC MSVCRTD MTSM -Munged murmurhash muxes myapplet @@ -1151,7 +1088,6 @@ Newtonsoft NEXTLINE nfe NLSMODE -nnn NOACTIVATE NOAPPLYNOW NOCLIP @@ -1204,40 +1140,31 @@ NPFS nrcs NSTATUS ntapi -ntcon -ntcsrdll ntdef NTDEV ntdll ntifs -ntlpcapi ntm -ntrtl ntstatus nttree -nturtl ntuser NTVDM -ntverp nugetversions NUKTA nullness nullonfailure nullopts -numlock NUMSCROLL NUnit nupkg NVIDIA NVT OACR -objbase ocolor oemcp OEMFONT OEMFORMAT OEMs -offboarded OLEAUT OLECHAR onebranch @@ -1287,7 +1214,6 @@ PALPC pankaj parentable PATCOPY -pathcch PATTERNID pbstr pcb @@ -1342,7 +1268,6 @@ phicon phwnd pidl PIDLIST -pids pii piml pimpl @@ -1367,11 +1292,9 @@ POINTERUPDATE POINTSLIST policheck POLYTEXTW -poppack POPUPATTR popups PORFLG -positionals POSTCHARBREAKS POSX POSXSCROLL @@ -1404,11 +1327,9 @@ PREVLINE prg pri prioritization -processenv processhost PROCESSINFOCLASS PRODEXT -Productize PROPERTYID PROPERTYKEY propertyval @@ -1420,16 +1341,13 @@ propsys PROPTITLE propvar propvariant -propvarutil psa PSCRED PSECURITY pseudoconsole -pseudoterminal psh pshn PSHNOTIFY -pshpack PSINGLE psl psldl @@ -1501,8 +1419,6 @@ REGISTERVDM regkey REGSTR RELBINPATH -remoting -renamer rendersize reparented reparenting @@ -1537,19 +1453,16 @@ RIGHTALIGN RIGHTBUTTON riid ris -roadmap robomac rodata rosetta RRF -rrr RRRGGGBB rsas rtcore RTEXT RTLREADING Rtn -ruleset runas RUNDLL runformat @@ -1567,7 +1480,6 @@ rvpa RWIN rxvt safemath -sancov sba SBCS SBCSDBCS @@ -1595,10 +1507,8 @@ SCROLLSCREENBUFFER scursor sddl SDKDDK -securityappcontainer segfault SELCHANGE -SELECTALL SELECTEDFONT SELECTSTRING Selfhosters @@ -1642,14 +1552,10 @@ SFUI sgr SHCo shcore -shellapi shellex -shellscalingapi SHFILEINFO SHGFI SHIFTJIS -shlguid -shlobj shlwapi SHORTPATH SHOWCURSOR @@ -1687,7 +1593,6 @@ snapcy snk SOLIDBOX Solutiondir -somefile sourced SRCAND SRCCODEPAGE @@ -1719,9 +1624,8 @@ STDEXT STDMETHODCALLTYPE STDMETHODIMP STGM -Stringable STRINGTABLE -strsafe +STRSAFE STUBHEAD STUVWX stylecop @@ -1747,32 +1651,27 @@ SYSLIB SYSLINK SYSMENU sysparams -sysparamsext SYSTEMHAND SYSTEMMENU SYSTEMTIME tabview -TAdd taef TARG targetentrypoint TARGETLIBS TARGETNAME targetver -TBase tbc tbi Tbl TBM -tchar +TCHAR TCHFORMAT TCI tcommands tdbuild Tdd -TDelegated TDP -tearoff Teb Techo tellp @@ -1782,7 +1681,6 @@ terminalinput terminalrenderdata TERMINALSCROLLING terminfo -TEs testcon testd testenvs @@ -1794,7 +1692,6 @@ TESTNULL testpass testpasses TEXCOORD -TExpected textattribute TEXTATTRIBUTEID textboxes @@ -1805,39 +1702,28 @@ TEXTMETRIC TEXTMETRICW textmode texttests -TFunction THUMBPOSITION THUMBTRACK -tickit -TIcon tilunittests titlebars TITLEISLINKNAME -TJson -TLambda TLDP TLEN TMAE TMPF -TMult tmultiple -TODOs tofrom -tokenhelpers toolbars TOOLINFO TOOLWINDOW TOPDOWNDIB -TOpt tosign -touchpad tracelogging traceviewpp trackbar trackpad transitioning Trd -TREX triaged triaging TRIMZEROHEADINGS @@ -1845,9 +1731,7 @@ trx tsa tsgr tsm -TStr TSTRFORMAT -TSub TTBITMAP TTFONT TTFONTLIST @@ -1856,7 +1740,6 @@ TTo tvpp tvtseq TYUI -UAC uap uapadmin UAX @@ -1892,7 +1775,6 @@ unk unknwn UNORM unparseable -Unregistering untextured UPDATEDISPLAY UPDOWN @@ -1910,7 +1792,6 @@ USEFILLATTRIBUTE USEGLYPHCHARS USEHICON USEPOSITION -USERDATA userdpiapi Userp userprivapi @@ -1923,7 +1804,6 @@ USRDLL utext utr UVWXY -UVWXYZ uwa uwp uwu @@ -1932,17 +1812,16 @@ Vanara vararg vclib vcxitems -vectorize VERCTRL VERTBAR VFT vga vgaoem viewkind -viewports VIRAMA Virt VIRTTERM +visualstudiosdk vkey VKKEYSCAN VMs @@ -2001,7 +1880,6 @@ wekyb wewoad wex wextest -wextestclass WFill wfopen WHelper @@ -2012,9 +1890,7 @@ Wiggum wil WImpl WINAPI -winbase winbasep -wincodec wincon winconp winconpty @@ -2029,10 +1905,8 @@ windll WINDOWALPHA windowdpiapi WINDOWEDGE -windowext WINDOWINFO windowio -windowmetrics WINDOWPLACEMENT windowpos WINDOWPOSCHANGED @@ -2040,20 +1914,15 @@ WINDOWPOSCHANGING windowproc windowrect windowsapp -windowsinternalstring WINDOWSIZE windowsshell windowsterminal -windowsx windowtheme winevent -wingdi winget wingetcreate WINIDE -winioctl winmd -winmeta winmgr winmm WINMSAPP @@ -2063,8 +1932,8 @@ WInplace winres winrt winternl +winui winuser -winuserp WINVER wistd wmain @@ -2101,10 +1970,9 @@ WRITECONSOLEINPUT WRITECONSOLEOUTPUT WRITECONSOLEOUTPUTSTRING wrkstr -wrl +WRL wrp WRunoff -wsl WSLENV wstr wstrings @@ -2117,7 +1985,7 @@ wtof WTs WTSOFTFONT wtw -wtypes +Wtypes WUX WVerify WWith @@ -2136,7 +2004,6 @@ XBUTTONDOWN XBUTTONUP XCast XCENTER -xchar xcopy XCount xdy @@ -2146,6 +2013,7 @@ XFG XFile XFORM XIn +xkcd XManifest XMath XNamespace @@ -2172,7 +2040,6 @@ YLimit YPan YSubstantial YVIRTUALSCREEN -Zab zabcd Zabcdefghijklmn Zabcdefghijklmnopqrstuvwxyz @@ -2181,4 +2048,3 @@ ZCtrl ZWJs ZYXWVU ZYXWVUTd -zzf diff --git a/.github/actions/spelling/line_forbidden.patterns b/.github/actions/spelling/line_forbidden.patterns index 5617a8af5f..ecfead2f0c 100644 --- a/.github/actions/spelling/line_forbidden.patterns +++ b/.github/actions/spelling/line_forbidden.patterns @@ -8,6 +8,24 @@ # you might not want to check in code where you skip all the other tests. #\bfit\( +# English does not use a hyphen between adverbs and nouns +# https://twitter.com/nyttypos/status/1894815686192685239 +(?:^|\s)[A-Z]?[a-z]+ly-(?=[a-z]{3,})(?:[.,?!]?\s|$) + +# Don't use `requires that` + `to be` +# https://twitter.com/nyttypos/status/1894816551435641027 +\brequires that \w+\b[^.]+to be\b + +# A fully parenthetical sentence’s period goes inside the parentheses, not outside. +# https://twitter.com/nyttypos/status/1898844061873639490 +\([A-Z][a-z]{2,}(?: [a-z]+){3,}\)\.\s + +# Complete sentences shouldn't be in the middle of another sentence as a parenthetical. +(? In formal writing and where contractions are frowned upon, use `cannot`. # > It is possible to write `can not`, but you generally find it only as part of some other construction, such as `not only . . . but also.` # - if you encounter such a case, add a pattern for that case to patterns.txt. -\b[Cc]an not\b +\b[Cc]an not\b(?! only\b) + +# Should be `chart` +(?i)\bhelm\b.*\bchard\b # Do not use `(click) here` links # For more information, see: @@ -56,9 +111,27 @@ # * https://heyoka.medium.com/dont-use-click-here-f32f445d1021 (?i)(?:>|\[)(?:(?:click |)here|link|(?:read |)more)(?:> /etc/apt/sources.list.d/something-distro.list +# ```` +\bapt-key add\b + +# Should be `nearby` +\bnear by\b + # Should probably be a person named `Nick` or the abbreviation `NIC` \bNic\b @@ -153,7 +260,7 @@ \bperform it's\b # Should be `opt-in` -#(? below for the` +(?i)\bfind below the\b + +# Should be `then any` unless there's a comparison before the `,` +, than any\b + # Should be `did not exist` \bwere not existent\b @@ -197,9 +357,18 @@ # Should be `nonexistent` \b[Nn]o[nt][- ]existent\b +# Should be `our` +\bspending out time\b + # Should be `@brief` / `@details` / `@param` / `@return` / `@retval` (?:^\s*|(?:\*|//|/*)\s+`)[\\@](?:breif|(?:detail|detials)|(?:params(?!\.)|prama?)|ret(?:uns?)|retvl)\b +# Should be `more than` or `more, then` +\bmore then\b + +# Should be `Pipeline`/`pipeline` +(?:(?<=\b|[A-Z])p|P)ipeLine(?:\b|(?=[A-Z])) + # Should be `preexisting` [Pp]re[- ]existing @@ -224,14 +393,27 @@ # Should be `reentrant` [Rr]e[- ]entrant +# Should be `room for` +\brooms for (?!lease|rent|sale) + +# Should be `socioeconomic` +# https://dictionary.cambridge.org/us/dictionary/english/socioeconomic +socio-economic + # Should be `strong suit` \b(?:my|his|her|their) strong suite\b +# Should probably be `temperatures` unless actually talking about thermal drafts (things birds may fly on) +\bthermals\b + +# Should be `there are` or `they are` (or `they're`) +(?i)\btheir are\b + # Should be `understand` \bunder stand\b -# Should be `URI` or `uri` unless it refers to a person named `Uri` -#(?|".*?") + +# hit-count: 1131 file-count: 326 +# C# includes +^\s*using [^;]+; + +# hit-count: 128 file-count: 47 # C network byte conversions (?:\d|\bh)to(?!ken)(?=[a-z])|to(?=[adhiklpun]\() -# hit-count: 59 file-count: 36 -# IServiceProvider / isAThing -(?:\b|_)(?:(?:ns|)I|isA)(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b)) +# hit-count: 53 file-count: 10 +# ANSI color codes +(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m -# hit-count: 9 file-count: 6 +# hit-count: 45 file-count: 29 +# version suffix v# +(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) + +# hit-count: 30 file-count: 19 +# uuid: +\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b + +# hit-count: 17 file-count: 7 +# File extensions +\*\.[+\w]+, + +# hit-count: 15 file-count: 9 # Markdown anchor links \(#\S*?[a-zA-Z]\S*?\) -# hit-count: 5 file-count: 5 -# libraries -(?:\b|_)lib(?:re(?=office)|)(?!era[lt]|ero|ert(?:ies|y)|rar(?:i(?:an|es)|y))(?=[a-z]) +# hit-count: 14 file-count: 8 +# hex runs +\b[0-9a-fA-F]{16,}\b + +# hit-count: 12 file-count: 8 +# hex digits including css/html color classes: +(?:[\\0][xX]|\\u|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b + +# hit-count: 12 file-count: 7 +# tar arguments +\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ + +# hit-count: 9 file-count: 6 +# Repeated letters +\b([a-z])\g{-1}{2,}\b + +# hit-count: 8 file-count: 2 +# regex choice +\(\?:[^)]+\|[^)]+\) + +# hit-count: 8 file-count: 1 +# latex (check-spelling >= 0.0.22) +\\\w{2,}\{ + +# hit-count: 7 file-count: 4 +# Python string prefix / binary prefix +# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings +(?)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|"'`=(])-(?:D(?=[A-Z])|W(?!ork)|X|f(?=[ms]))(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) - -# hit-count: 60 file-count: 35 -# version suffix v# -(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) - -# hit-count: 2 file-count: 2 -# This does not cover multiline strings, if your repository has them, -# you'll want to remove the `(?=.*?")` suffix. -# The `(?=.*?")` suffix should limit the false positives rate -# printf -%(?:s)(?!ize)(?=[a-z]{2,}) - -# hit-count: 16 file-count: 10 -# uuid: -\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b - -# hit-count: 13 file-count: 4 -# Non-English -[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,} - -# hit-count: 7 file-count: 5 -# hex digits including css/html color classes: -(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b - -# hit-count: 7 file-count: 1 -# regex choice -\(\?:[^)]+\|[^)]+\) - -# hit-count: 4 file-count: 4 -# tar arguments -\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ - -# hit-count: 4 file-count: 1 -# ANSI color codes -(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m - -# hit-count: 4 file-count: 1 -# Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally ) -# grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review - -# Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into: -## Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary). -## You could manually change `(?i)X...` to use `[Xx]...` -## or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path) -# Lorem -(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])* - -# hit-count: 3 file-count: 3 -# mailto urls -mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} - -# hit-count: 2 file-count: 1 -# Python string prefix / binary prefix -# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings -(?= 0.0.22) -\\\w{2,}\{ - -# hit-count: 1 file-count: 1 -# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long... -\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b - -# Questionably acceptable forms of `in to` -# Personally, I prefer `log into`, but people object -# https://www.tprteaching.com/log-into-log-in-to-login/ -\b(?:[Ll]og|[Ss]ign) in to\b - -# to opt in -\bto opt in\b # Questionably acceptable forms of `in to` # Personally, I prefer `log into`, but people object @@ -183,6 +210,10 @@ mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} # to opt in \bto opt in\b + +# pass(ed|ing) in +\bpass(?:ed|ing) in\b + # acceptable duplicates # ls directory listings [-bcdlpsw](?:[-r][-w][-SsTtx]){3}[\.+*]?\s+\d+\s+\S+\s+\S+\s+[.\d]+(?:[KMGT]|)\s+ diff --git a/.github/actions/spelling/reject.txt b/.github/actions/spelling/reject.txt index d98038f96f..a4022cd4b4 100644 --- a/.github/actions/spelling/reject.txt +++ b/.github/actions/spelling/reject.txt @@ -1,10 +1,19 @@ ^attache$ ^attacher$ ^attachers$ -^bellow$ +^bellow?$ benefitting occurences? ^dependan.* +^develope$ +^developement$ +^developpe +^Devers?$ +^devex +^devide +^Devinn?[ae] +^devisal +^devisor ^diables?$ ^oer$ Sorce @@ -12,4 +21,5 @@ Sorce ^Teh$ ^untill$ ^untilling$ +^venders?$ ^wether.* diff --git a/.github/workflows/spelling2.yml b/.github/workflows/spelling2.yml index 7675d5d004..f01ac1eea5 100644 --- a/.github/workflows/spelling2.yml +++ b/.github/workflows/spelling2.yml @@ -93,7 +93,7 @@ jobs: steps: - name: check-spelling id: spelling - uses: check-spelling/check-spelling@v0.0.24 + uses: check-spelling/check-spelling@v0.0.25 with: suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }} checkout: true @@ -114,34 +114,33 @@ jobs: cspell:software-terms/softwareTerms.txt cspell:cpp/stdlib-cpp.txt cspell:cpp/stdlib-c.txt - cspell:lorem-ipsum/dictionary.txt + cspell:python/python/python-lib.txt cspell:php/php.txt + cspell:node/node.txt + cspell:dart/dart.txt cspell:filetypes/filetypes.txt cspell:java/java.txt - cspell:node/node.txt - cspell:golang/go.txt - cspell:java/java-terms.txt - cspell:mnemonics/mnemonics.txt + cspell:css/css.txt + cspell:dotnet/dotnet.txt cspell:npm/npm.txt cspell:fullstack/fullstack.txt - cspell:python/python/python-lib.txt - cspell:dotnet/dotnet.txt - cspell:dart/dart.txt - cspell:aws/aws.txt - cspell:python/common/extra.txt - cspell:css/css.txt + cspell:java/java-terms.txt + cspell:r/r.txt + cspell:golang/go.txt cspell:cpp/stdlib-cmath.txt cspell:typescript/typescript.txt + cspell:html/html.txt cspell:cpp/compiler-msvc.txt cspell:django/django.txt - cspell:html/html.txt - cspell:cpp/lang-keywords.txt + cspell:aws/aws.txt + cspell:python/common/extra.txt cspell:cpp/ecosystem.txt - cspell:r/r.txt - cspell:cpp/compiler-clang-attributes.txt - cspell:powershell/powershell.txt + cspell:cpp/lang-keywords.txt cspell:csharp/csharp.txt + cspell:cpp/compiler-clang-attributes.txt cspell:python/python/python.txt + cspell:mnemonics/mnemonics.txt + cspell:powershell/powershell.txt comment-push: name: Report (Push) @@ -154,7 +153,7 @@ jobs: if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push' steps: - name: comment - uses: check-spelling/check-spelling@v0.0.24 + uses: check-spelling/check-spelling@v0.0.25 with: checkout: true spell_check_this: microsoft/terminal@main @@ -172,7 +171,7 @@ jobs: if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request') steps: - name: comment - uses: check-spelling/check-spelling@v0.0.24 + uses: check-spelling/check-spelling@v0.0.25 with: checkout: true spell_check_this: microsoft/terminal@main @@ -198,7 +197,7 @@ jobs: cancel-in-progress: false steps: - name: apply spelling updates - uses: check-spelling/check-spelling@v0.0.24 + uses: check-spelling/check-spelling@v0.0.25 with: experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} checkout: true diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index 5dda1dc89c..4adf3837b1 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -1,4 +1,4 @@ -name: Publish to Winget +name: Publish to WinGet on: release: diff --git a/build/Helix/HelixTestHelpers.cs b/build/Helix/HelixTestHelpers.cs index 67f13937c0..cb8591031e 100644 --- a/build/Helix/HelixTestHelpers.cs +++ b/build/Helix/HelixTestHelpers.cs @@ -28,7 +28,7 @@ namespace HelixTestHelpers public List Screenshots { get; private set; } public List RerunResults { get; private set; } } - + // // Azure DevOps doesn't currently provide a way to directly report sub-results for tests that failed at least once // that were run multiple times. To get around that limitation, we'll mark the test as "Skip" since @@ -49,46 +49,46 @@ namespace HelixTestHelpers // TODO (https://github.com/dotnet/arcade/issues/2773): Once we're able to directly report things in a // more granular fashion than just a binary pass/fail result, we should do that. // - [DataContract] + [DataContract] internal class JsonSerializableTestResults - { + { [DataMember] internal string blobPrefix; - + [DataMember] internal string blobSuffix; - + [DataMember] internal string[] errors; - + [DataMember] internal JsonSerializableTestResult[] results; } - - [DataContract] - internal class JsonSerializableTestResult + + [DataContract] + internal class JsonSerializableTestResult { [DataMember] internal string outcome; [DataMember] internal int duration; - + [DataMember(EmitDefaultValue = false)] internal string log; - + [DataMember(EmitDefaultValue = false)] internal string[] screenshots; - + [DataMember(EmitDefaultValue = false)] internal int errorIndex; } - + public class TestPass { public TimeSpan TestPassExecutionTime { get; set; } public List TestResults { get; set; } - + public static TestPass ParseTestWttFile(string fileName, bool cleanupFailuresAreRegressions, bool truncateTestNames) { using (var stream = File.OpenRead(fileName)) @@ -174,7 +174,7 @@ namespace HelixTestHelpers if (testsExecuting == 1) { string testName = element.Attribute("Title").Value; - + if (truncateTestNames) { const string xamlNativePrefix = "Windows::UI::Xaml::Tests::"; @@ -243,7 +243,7 @@ namespace HelixTestHelpers // The test cleanup errors will often come after the test claimed to have - // 'passed'. We treat them as errors as well. + // 'passed'. We treat them as errors as well. if (inTestCleanup) { currentResult.CleanupPassed = false; @@ -292,7 +292,7 @@ namespace HelixTestHelpers foreach(var screenshot in screenshots) { string fileNameSuffix = string.Empty; - + if (fileName.Contains("_rerun_multiple")) { fileNameSuffix = "_rerun_multiple"; @@ -301,7 +301,7 @@ namespace HelixTestHelpers { fileNameSuffix = "_rerun"; } - + currentResult.Screenshots.Add(screenshot.Replace(".jpg", fileNameSuffix + ".jpg")); } } @@ -313,7 +313,7 @@ namespace HelixTestHelpers testPassStopTime = Int64.Parse(doc.Root.Descendants("WexTraceInfo").Last().Attribute("TimeStamp").Value); var testPassTime = TimeSpan.FromSeconds((double)(testPassStopTime - testPassStartTime) / frequency); - + foreach (TestResult testResult in testResults) { if (testResult.Details != null) @@ -331,13 +331,13 @@ namespace HelixTestHelpers return testpass; } } - + public static TestPass ParseTestWttFileWithReruns(string fileName, string singleRerunFileName, string multipleRerunFileName, bool cleanupFailuresAreRegressions, bool truncateTestNames) { TestPass testPass = ParseTestWttFile(fileName, cleanupFailuresAreRegressions, truncateTestNames); TestPass singleRerunTestPass = File.Exists(singleRerunFileName) ? ParseTestWttFile(singleRerunFileName, cleanupFailuresAreRegressions, truncateTestNames) : null; TestPass multipleRerunTestPass = File.Exists(multipleRerunFileName) ? ParseTestWttFile(multipleRerunFileName, cleanupFailuresAreRegressions, truncateTestNames) : null; - + List rerunTestResults = new List(); if (singleRerunTestPass != null) @@ -377,9 +377,9 @@ namespace HelixTestHelpers public static void OutputFailedTestQuery(string wttInputPath) { var testPass = TestPass.ParseTestWttFile(wttInputPath, cleanupFailuresAreRegressions: true, truncateTestNames: false); - + List failedTestNames = new List(); - + foreach (var result in testPass.TestResults) { if (!result.Passed) @@ -387,23 +387,23 @@ namespace HelixTestHelpers failedTestNames.Add(result.Name); } } - + if (failedTestNames.Count > 0) { string failedTestSelectQuery = "(@Name='"; - + for (int i = 0; i < failedTestNames.Count; i++) { failedTestSelectQuery += failedTestNames[i]; - + if (i < failedTestNames.Count - 1) { failedTestSelectQuery += "' or @Name='"; } } - + failedTestSelectQuery += "')"; - + Console.WriteLine(failedTestSelectQuery); } else @@ -418,7 +418,7 @@ namespace HelixTestHelpers private string testNamePrefix; private string helixResultsContainerUri; private string helixResultsContainerRsas; - + public TestResultParser(string testNamePrefix, string helixResultsContainerUri, string helixResultsContainerRsas) { this.testNamePrefix = testNamePrefix; @@ -430,7 +430,7 @@ namespace HelixTestHelpers { Dictionary subResultsJsonByMethod = new Dictionary(); TestPass testPass = TestPass.ParseTestWttFileWithReruns(wttInputPath, wttSingleRerunInputPath, wttMultipleRerunInputPath, cleanupFailuresAreRegressions: true, truncateTestNames: false); - + foreach (var result in testPass.TestResults) { var methodName = result.Name.Substring(result.Name.LastIndexOf('.') + 1); @@ -488,7 +488,7 @@ namespace HelixTestHelpers int resultCount = results.Count; int passedCount = results.Where(r => r.Passed).Count(); - + // Since we re-run tests on failure, we'll mark every test that failed at least once as "skipped" rather than "failed". // If the test failed sufficiently often enough for it to count as a failed test (determined by a property on the // Azure DevOps job), we'll later mark it as failed during test results processing. @@ -504,15 +504,15 @@ namespace HelixTestHelpers assembly.SetAttributeValue("run-date", DateTime.Now.ToString("yyyy-MM-dd")); // This doesn't need to be completely accurate since it's not exposed anywhere. - // If we need accurate an start time we can probably calculate it from the te.wtl file, but for + // If we need an accurate start time we can probably calculate it from the te.wtl file, but for // now this is fine. assembly.SetAttributeValue("run-time", (DateTime.Now - testPass.TestPassExecutionTime).ToString("hh:mm:ss")); - + assembly.SetAttributeValue("total", resultCount); assembly.SetAttributeValue("passed", passedCount); assembly.SetAttributeValue("failed", failedCount); assembly.SetAttributeValue("skipped", skippedCount); - + assembly.SetAttributeValue("time", (int)testPass.TestPassExecutionTime.TotalSeconds); assembly.SetAttributeValue("errors", 0); root.Add(assembly); @@ -537,9 +537,9 @@ namespace HelixTestHelpers test.SetAttributeValue("method", methodName); test.SetAttributeValue("time", result.ExecutionTime.TotalSeconds); - + string resultString = string.Empty; - + if (result.Passed && !result.Skipped) { resultString = "Pass"; @@ -554,7 +554,7 @@ namespace HelixTestHelpers resultString = "Fail"; } - + if (!result.Passed) { if (result.Skipped) @@ -579,36 +579,36 @@ namespace HelixTestHelpers File.WriteAllText(xunitOutputPath, root.ToString()); } - + private JsonSerializableTestResult ConvertToSerializableResult(TestResult rerunResult, string[] uniqueErrors) { var serializableResult = new JsonSerializableTestResult(); - + serializableResult.outcome = rerunResult.Passed ? "Passed" : "Failed"; serializableResult.duration = (int)Math.Round(rerunResult.ExecutionTime.TotalMilliseconds); - + if (!rerunResult.Passed) { serializableResult.log = Path.GetFileName(rerunResult.SourceWttFile); - + if (rerunResult.Screenshots.Any()) { List screenshots = new List(); - + foreach (var screenshot in rerunResult.Screenshots) { screenshots.Add(Path.GetFileName(screenshot)); } - + serializableResult.screenshots = screenshots.ToArray(); } - + // To conserve space, we'll log the index of the error to index in a list of unique errors rather than // jotting down every single error in its entirety. We'll add one to the result so we can avoid // serializing this property when it has the default value of 0. serializableResult.errorIndex = Array.IndexOf(uniqueErrors, rerunResult.Details) + 1; } - + return serializableResult; } @@ -617,7 +617,7 @@ namespace HelixTestHelpers var filename = Path.GetFileName(filePath); return string.Format("{0}/{1}{2}", helixResultsContainerUri, filename, helixResultsContainerRsas); } - + private string GetTestNameSeparator(string testname) { var separatorString = "."; diff --git a/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml b/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml index e30e23b88c..513e5c5007 100644 --- a/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml +++ b/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml @@ -30,7 +30,7 @@ jobs: steps: - checkout: self clean: true - # It is important that this be 0, otherwise git will not fetch the branch ref names that the PGO rules require. + # It is important that this be 0; otherwise, git will not fetch the branch ref names that the PGO rules require. fetchDepth: 0 submodules: false persistCredentials: false diff --git a/doc/Niksa.md b/doc/Niksa.md index ec6af1f146..d9d82fe39d 100644 --- a/doc/Niksa.md +++ b/doc/Niksa.md @@ -15,7 +15,7 @@ This document serves as a storage point for those posts. ## Why do we avoid changing CMD.exe? `setlocal` doesn't behave the same way as an environment variable. It's a thing that would have to be put in at the top of the batch script that is `somefile.cmd` as one of its first commands to adjust the way that one specific batch file is processed by the `cmd.exe` engine. That's probably not suitable for your needs, but that's the way we have to go. -I don't think anyone is disagreeing with you, @mikemaccana, that this would be a five minute development change to read that environment variable and change the behavior of `cmd.exe`. It absolutely would be a tiny development time. +I don't think anyone is disagreeing with you, @mikemaccana, that this would be a five minute development change to read that environment variable and change the behavior of `cmd.exe`. It absolutely would be a tiny development time. It's just that from our experience, we know there's going to be a 3-24 month bug tail here where we get massive investigation callbacks by some billion dollar enterprise customer who for whatever reason was already using the environment variable we pick for another purpose. Their script that they give their rank-and-file folks will tell them to press Ctrl+C at some point in the batch script to do whatever happens, it will do something different, those people will notice the script doesn't match the computer anymore. They will then halt the production line and tell their supervisor. The supervisor tells some director. Their director comes screaming at their Microsoft enterprise support contract person that we've introduced a change to the OS that is costing them millions if not billions of dollars in shipments per month. Our directors at Microsoft then come bashing down our doors angry with us and make us fix it ASAP or revert it, we don't get to go home at 5pm to our families or friends because we're fixing it, we get stressed the heck out, we have to spin up servicing potentially for already shipped operating systems which is expensive and headache-causing...etc. @@ -27,7 +27,7 @@ I would highly recommend that Gulp convert to using PowerShell scripts and that Original Source: https://github.com/microsoft/terminal/issues/217#issuecomment-404240443 -_Addendum_: cmd.exe is the literal embodiment of [xkcd#1172]([url](https://xkcd.com/1172/)). Every change, no matter how small, will break _someone_. +_Addendum_: cmd.exe is the literal embodiment of [xkcd#1172]([url](https://xkcd.com/1172/)). Every change, no matter how small, will break _someone_. ## Why is typing-to-screen performance better than every other app? @@ -37,33 +37,33 @@ Also, I'm happy to discuss this with you until you're utterly sick of reading it If I had to take an educated guess as to what is making us faster than pretty much any other application on Windows at putting your text on the screen... I would say it is because that is literally our only job! Also probably because we are using darn near the oldest and lowest level APIs that Windows has to accomplish this work. -Pretty much everything else you've listed has some sort of layer or framework involved, or many, many layers and frameworks, when you start talking about Electron and JavaScript. We don't. +Pretty much everything else you've listed has some sort of layer or framework involved, or many, many layers and frameworks, when you start talking about Electron and JavaScript. We don't. -We have one bare, super un-special window with no additional controls attached to it. We get our keys fed into us from just barely above the kernel given that we're processing them from window messages and not from some sort of eventing framework common to pretty much any other more complicated UI framework than ours (WPF, WinForms, UWP, Electron). And we dump our text straight onto the window surface using GDI's [PolyTextOut](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-polytextoutw) with no frills. +We have one bare, super un-special window with no additional controls attached to it. We get our keys fed into us from just barely above the kernel given that we're processing them from window messages and not from some sort of eventing framework common to pretty much any other more complicated UI framework than ours (WPF, WinForms, UWP, Electron). And we dump our text straight onto the window surface using GDI's [PolyTextOut](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-polytextoutw) with no frills. Even `notepad.exe` has multiple controls on its window at the very least and is probably (I haven't looked) using some sort of library framework in the edit control to figure out its text layout (which probably is using another library framework for internationalization support...) -Of course this also means that we have trade offs. We don't support fully international text like pretty much every other application will. RTL? No go zone right now. Surrogate pairs and emoji? We're getting there but not there yet. Indic scripts? Nope. +Of course this also means that we have trade offs. We don't support fully international text like pretty much every other application will. RTL? No go zone right now. Surrogate pairs and emoji? We're getting there but not there yet. Indic scripts? Nope. Why are we like this? For one, `conhost.exe` is old as dirt. It has to use the bare metal bottom layer of everything because it was created before most of those other frameworks were created. And also it maintains as low/bottom level as possible because it is pretty much the first thing that one needs to bring up when bringing up a new operating system edition or device before you have all the nice things like frameworks or what those frameworks require to operate. Also it's written in C/C++ which is about as low and bare metal as we can get. -Will this UI enhancement come to other apps on Windows? Almost certainly not. They have too much going on which is both a good and a bad thing. I'm jealous of their ability to just call one method and layout text in an uncomplicated manner in any language without manually calculating pixels or caring about what styles apply to their font. But my manual pixel calculations, dirty region math, scroll region madness, and more makes it so we go faster than them. I'm also jealous that when someone says "hey can you add a status bar to the bottom of your window" that they can pretty much click and drag that into place with their UI Framework and it will just work where as for us, it's been a backlog item forever and gives me heartburn to think about implementing. +Will this UI enhancement come to other apps on Windows? Almost certainly not. They have too much going on which is both a good and a bad thing. I'm jealous of their ability to just call one method and layout text in an uncomplicated manner in any language without manually calculating pixels or caring about what styles apply to their font. But my manual pixel calculations, dirty region math, scroll region madness, and more makes it so we go faster than them. I'm also jealous that when someone says "hey can you add a status bar to the bottom of your window" that they can pretty much click and drag that into place with their UI Framework and it will just work whereas for us, it's been a backlog item forever and gives me heartburn to think about implementing. -Will we try to keep it from regressing? Yes! Right now it's sort of a manual process. We identify that something is getting slow and then we go haul out [WPR](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-recorder) and start taking traces. We stare down the hot paths and try to reason out what is going on and then improve them. For instance, in the last cycle or two, we focused on heap allocations as a major area where we could improve our end-to-end performance, changing a ton of our code to use stack-constructed iterator-like facades over the underlying request buffer instead of translating and allocating it into a new heap space for each level of processing. +Will we try to keep it from regressing? Yes! Right now it's sort of a manual process. We identify that something is getting slow and then we go haul out [WPR](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-recorder) and start taking traces. We stare down the hot paths and try to reason out what is going on and then improve them. For instance, in the last cycle or two, we focused on heap allocations as a major area where we could improve our end-to-end performance, changing a ton of our code to use stack-constructed iterator-like facades over the underlying request buffer instead of translating and allocating it into a new heap space for each level of processing. As an aside, @bitcrazed wants us to automate performance tests in some conhost specific way, but I haven't quite figured out a controlled environment to do this in yet. The Windows Engineering System runs performance tests each night that give us a coarse-grained way of knowing if we messed something up for the whole operating system, and they technically offer a fine-grained way for us to insert our own performance tests... but I just haven't got around to that yet. If you have an idea for a way for us to do this in an automated fashion, I'm all ears. -If there's anything else you'd like to know, let me know. I could go on all day. I deleted like 15 tangents from this reply before posting it.... +If there's anything else you'd like to know, let me know. I could go on all day. I deleted like 15 tangents from this reply before posting it.... Original Source: https://github.com/microsoft/terminal/issues/327#issuecomment-447391705 ## How are the Windows graphics/messaging stack assembled? -@stakx, I am referring to USER32 and GDI32. +@stakx, I am referring to USER32 and GDI32. I'll give you a cursory overview of what I know off the top of my head without spending hours confirming the details. As such, some of this is subject to handwaving and could be mildly incorrect but is probably in the right direction. Consider every statement to be my personal knowledge on how the world works and subject to opinion or error. -For the graphics part of the pipeline (GDI32), the user-mode portions of GDI are pretty far down. The app calls GDI32, some work is done in that DLL on the user-mode side, then a kernel call jumps over to the kernel and drawing occurs. +For the graphics part of the pipeline (GDI32), the user-mode portions of GDI are pretty far down. The app calls GDI32, some work is done in that DLL on the user-mode side, then a kernel call jumps over to the kernel and drawing occurs. The portion that you're thinking of regarding "silently converted to sit on top of other stuff" is probably that once we hit the kernel calls, a bunch of the kernel GDI stuff tends to be re-platformed on top of the same stuff as DirectX when it is actually handled by the NVIDIA/AMD/Intel/etc. graphics driver and the GPU at the bottom of the stack. I think this happened with the graphics driver re-architecture that came as a part of WDDM for Windows Vista. There's a document out there somewhere about what calls are still really fast in GDI and which are slower as a result of the re-platforming. Last time I found that document and checked, we were using the fast ones. @@ -71,11 +71,11 @@ On top of GDI, I believe there are things like Common Controls or comctl32.dll w As for DirectWrite and D2D and D3D and DXGI themselves, they're a separate set of commands and paths that are completely off to the side from GDI at all both in user and kernel mode. They're not really related other than that there's some interoperability provisions between the two. Most of our other UI frameworks tend to be built on top of the DirectX stack though. XAML is for sure. I think WPF is. Not sure about WinForms. And I believe the composition stack and the window manager are using DirectX as well. -As for the input/interaction part of the pipeline (USER32), I tend to find most other newer things (at least for desktop PCs) are built on top of what is already there. USER32's major concept is windows and window handles and everything is sent to a window handle. As long as you're on a desktop machine (or a laptop or whatever... I mean a classic-style Windows-powered machine), there's a window handle involved and messages floating around and that means we're talking USER32. +As for the input/interaction part of the pipeline (USER32), I tend to find most other newer things (at least for desktop PCs) are built on top of what is already there. USER32's major concept is windows and window handles and everything is sent to a window handle. As long as you're on a desktop machine (or a laptop or whatever... I mean a classic-style Windows-powered machine), there's a window handle involved and messages floating around and that means we're talking USER32. -The window message queue is just a straight up FIFO (more or less) of whatever input has occurred relevant to that window while it's in the foreground + whatever has been sent to the window by other components in the system. +The window message queue is just a straight up FIFO (more or less) of whatever input has occurred relevant to that window while it's in the foreground + whatever has been sent to the window by other components in the system. -The newer technologies and the frameworks like XAML and WPF and WinForms tend to receive the messages from the window message queue one way or another and process them and turn them into event callbacks to various objects that they've provisioned within their world. +The newer technologies and the frameworks like XAML and WPF and WinForms tend to receive the messages from the window message queue one way or another and process them and turn them into event callbacks to various objects that they've provisioned within their world. However, the newer technologies that also work on other non-desktop platforms like XAML tend to have the ability to process stuff off of a completely different non-USER32 stack as well. There's a separate parallel stack to USER32 with all of our new innovations and realizations on how input and interaction should occur that doesn't exactly deal with classic messaging queues and window handles the same way. This is the whole Core* family of things like CoreWindow and CoreMessaging. They also have a different concept of "what is a user" that isn't so centric around your butt in rolling chair in front of a screen with a keyboard and mouse on the desk. @@ -83,7 +83,7 @@ Now, if you're on XAML or one of the other Frameworks... all this intricacy is h The trick is that GDI32 and USER32 were designed for a limited world with a limited set of commands. Desktop PCs were the only thing that existed, single user at the keyboard and mouse, simple graphics output to a VGA monitor. So using them directly at the "low level" like conhost does is pretty easy. The new platforms could be used at the "low level" but they're orders of magnitude more complicated because they now account for everything that has happened with personal computing in 20+ years like different form factors, multiple active users, multiple graphics adapters, and on and on and on and on. So you tend to use a framework when using the new stuff so your head doesn't explode. They handle it for you, but they handle more than they ever did before so they're slower to some degree. -So are GDI32 and USER32 "lower" than the new stuff? Sort of. +So are GDI32 and USER32 "lower" than the new stuff? Sort of. Can you get that low with the newer stuff? Mostly yes, but you probably shouldn't and don't want to. Does new live on top of old or is old replatformed on the new? Sometimes and/or partially. Basically... it's like the answer to anything software... "it's an unmitigated disaster and if we all stepped back a moment, we should be astounded that it works at all." :P @@ -94,7 +94,7 @@ Original Source: https://github.com/microsoft/terminal/issues/327#issuecomment-4 ## Output Processing between "Far East" and "Western" -> +> > ``` > if (WI_IsFlagSet(CharType, C1_CNTRL)) > ``` @@ -120,7 +120,7 @@ Note in both of these, there is a little bit of indirection before `MultiByteToW When we took over the console codebase, this variation between "Western" and "Eastern" countries was especially painful because `conhost.exe` would choose which one it was in based on the `Codepage for Non-Unicode Applications` set in the Control Panel's Regional > Administrative panel and it could only be changed with a reboot. It wouldn't even change properly when you `chcp` to a different codepage. Heck, `chcp` would deny you from switching into many codepages. There was a block in place to prevent going to an "Eastern" codepage if you booted up in a "Western" codepage. There was also a block preventing you from going between "Eastern" codepages, if I recall correctly. In modernizing, I decided a few things: -1. What's good for the "Far East" should be good for the rest of the world. CJK languages that encompassed the "Far East" code have to be able to handle "Western" text as well even if the reverse wasn't true. +1. What's good for the "Far East" should be good for the rest of the world. CJK languages that encompassed the "Far East" code have to be able to handle "Western" text as well even if the reverse wasn't true. 2. We need to scrub all usages of "Far East" from the code. Someone already started that and replaced them with "East Asia" except then they left behind the shorthand of "FE" prefixing dozens of functions which made it hard to follow the code. It took us months to realize "FE" and "East Asia" were the same thing. 3. It's obnoxious that the way this was handled was to literally double-define every output function in the code base to have two definitions, compile them both into the conhost, then choose to run down the SB_ versions or the FE_ versions depending on the startup Non-Unicode codepage. It was a massive pile of complex pre-compilation `#ifdef` and `#else`s that would sometimes surround individual lines in the function bodies. Gross. 4. The fact that the FE_ versions of the functions were way slower than the SB_ ones was unacceptable even for the same output of Latin-character text. @@ -139,13 +139,13 @@ Original Source: https://github.com/microsoft/terminal/issues/166#issuecomment-5 ## Why do we not backport things? -Someone has to prove that this is costing millions to billions of dollars of lost productivity or revenue to outweigh the risks of shipping the fix to hundreds of millions of Windows machines and potentially breaking something. +Someone has to prove that this is costing millions to billions of dollars of lost productivity or revenue to outweigh the risks of shipping the fix to hundreds of millions of Windows machines and potentially breaking something. -Our team generally finds it pretty hard to prove that against the developer audience given that they're only a small portion of the total installed market of Windows machines. +Our team generally finds it pretty hard to prove that against the developer audience given that they're only a small portion of the total installed market of Windows machines. Our only backport successes really come from corporations with massive addressable market (like OEMs shipping PCs) who complain that this is fouling up their manufacturing line (or something of that ilk). Otherwise, our management typically says that the risks don't outweigh the benefits. -It's also costly in terms of time, effort, and testing for us to validate a modification to a released OS. We have a mindbogglingly massive amount of automated machinery dedicated to processing and validating the things that we check in while developing the current OS builds. But it's a special costly ask to spin up some to all of those activities to validate backported fixes. We do it all the time for Patch Tuesday, but in those patches, they only pass through the minimum number of fixes required to maximize the restoration of productivity/security/revenue/etc. because every additional fix adds additional complexity and additional risk. +It's also costly in terms of time, effort, and testing for us to validate a modification to a released OS. We have a mindbogglingly massive amount of automated machinery dedicated to processing and validating the things that we check in while developing the current OS builds. But it's a special costly ask to spin up some to all of those activities to validate backported fixes. We do it all the time for Patch Tuesday, but in those patches, they only pass through the minimum number of fixes required to maximize the restoration of productivity/security/revenue/etc. because every additional fix adds additional complexity and additional risk. So from our little team working hard to make developers happy, we virtually never make the cut for servicing. We're sorry, but we hope you can understand. It's just the reality of the situation to say "nope" when people ask for a backport. In our team's ideal world, you would all be running the latest console bits everywhere every time we make a change. But that's just not how it is today. @@ -189,7 +189,7 @@ _guest speaker @zadjii-msft_ I think there might be a bit of a misunderstanding here - there are two different kinds of applications we're talking about here: * shell applications, like `cmd.exe`, `powershell`, `zsh`, etc. These are text-only applications that emit streams of characters. They don't care at all about how they're eventually rendered to the user. These are also sometimes referred to as "commandline client" applications. -* terminal applications, like the Windows Terminal, gnome-terminal, xterm, iterm2, hyper. These are graphical applications that can be used to render the output of commandline clients. +* terminal applications, like the Windows Terminal, gnome-terminal, xterm, iterm2, hyper. These are graphical applications that can be used to render the output of commandline clients. On Windows, if you just run `cmd.exe` directly, the OS will create an instance of `conhost.exe` as the _terminal_ for `cmd.exe`. The same thing happens for `powershell.exe`, the system will create a new conhost window for any client that's not already connected to a terminal of some sort. This has lead to an enormous amount of confusion for people thinking that a conhost window is actually a "`cmd` window". `cmd` can't have a window, it's just a commandline application. Its window is always some other terminal. diff --git a/doc/WindowsTestPasses.md b/doc/WindowsTestPasses.md index f733f98af5..1689bbb443 100644 --- a/doc/WindowsTestPasses.md +++ b/doc/WindowsTestPasses.md @@ -31,12 +31,12 @@ Prerequisites: 1. Right click the machine name in the `Device Manager` list and choose `Launch T-Shell`. You can also use `Connect via Console` to get a "remote desktop"-like session to the KVM port on the VM. 1. In T-shell, use `testd Microsoft.Console.TestLab.Desktop.testlist` or a command of that format with a different TESTLIST or TESTMD name from our project (see the [UniversalTest.md] documentation). The `testd` utility will automatically resolve the build/branch/flavor information, dig through the build shares for the matching TESTLIST/TESTMD metadata, and attempt to deploy all relevant packages and dependencies on the device. When it's successful, it will move onto running all the tests and giving you the results. On conclusion, the test results should pop up in the web browser or the `Hubble - Log Viewer` tool provided by the Engineering Systems team. -If some of the above things do not work, go to [https://osgwiki.com] and type them into the Search bar. For instance, if T-Shell isn't found or working, you can find out where to get it or download it on `OSGWiki`. The same goes for the other commands besides `testd` to use in T-shell and more information on what `Hubble` or `Nebula` are. +If some of the above things do not work, go to [https://osgwiki.com] and type them into the Search bar. For instance, if T-Shell isn't found or working, you can find out where to get it or download it on `OSGWiki`. The same goes for the other commands besides `testd` to use in T-shell and more information on what `Hubble` or `Nebula` are. Presumably now you have a failure. Or a success. You can attempt to spelunk the logs in `Hubble` and you might come to a conclusion. Or you can move onto debugging directly. -Now that you've relied on `testd` to get everything deployed and orchestrated and run once on the device, you can use `execd` to run things again or to run a smaller subset of things on the remote device through `T-Shell`. +Now that you've relied on `testd` to get everything deployed and orchestrated and run once on the device, you can use `execd` to run things again or to run a smaller subset of things on the remote device through `T-Shell`. -By default, in the `Universal Test` world, everything will be deployed onto the remote machine at `C:\data\test\bin`. In T-Shell, use `cdd C:\data\test\bin` to change to that directory and then `execd te.exe Microsoft.Console.Host.FeatureTests.dll /name:*TestReadFileEcho*` to run just one specific test. Of course you should substitute the file name and test name parameters as makes sense. And of course you can find out more about `cdd` and `execd` on the `T-shell` page of `OSGWiki`. +By default, in the `Universal Test` world, everything will be deployed onto the remote machine at `C:\data\test\bin`. In T-Shell, use `cdd C:\data\test\bin` to change to that directory and then `execd te.exe Microsoft.Console.Host.FeatureTests.dll /name:*TestReadFileEcho*` to run just one specific test. Of course, you should substitute the file name and test name parameters as makes sense. And of course you can find out more about `cdd` and `execd` on the `T-shell` page of `OSGWiki`. Fortunately, running things through `T-shell` in this fashion is exactly the same way that the testlab orchestrates the tests. If you still don't get good data this way, you can use the `Connect via Console` mechanism way above to try to run things under `WinDBG` or the `Visual Studio Remote Debugger` manually on the machine to get them to repro or under the debugger more completely. diff --git a/doc/creating_a_new_project.md b/doc/creating_a_new_project.md index 0f7f3bd880..2e17b097c6 100644 --- a/doc/creating_a_new_project.md +++ b/doc/creating_a_new_project.md @@ -6,7 +6,7 @@ When creating a new DLL, it was really helpful to reference an existing DLL's `. - [ ] Make sure to `` our pre props at the _top_ of the vcxproj, and our post props at the _bottom_ of the vcxproj. ``` - + @@ -35,7 +35,7 @@ DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE - _Note_: If your new library eventually rolls up as a reference to our Centennial Packaging project `CascadiaPackage`, you don't have to worry about manually adding your definitions to the `AppXManifest.xml` because the Centennial Packaging project automatically enumerates the reference tree of WinMDs and stitches that information into the `AppXManifest.xml`. However, if your new project does _not_ ultimately roll up to a packaging project that will automatically put the references into `AppXManifest`, you will have to add them in manually. ### Troubleshooting -- If you hit an error that looks like this: +- If you hit an error that looks like this: ``` X found processing metadata file ..\blah1\Microsoft.UI.Xaml.winmd, type already exists in file ..\blah\NewDLLProject\Microsoft.UI.Xaml.winmd. ``` @@ -51,4 +51,4 @@ DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE - If you hit a `Class not Registered` error, this might be because a class isn't getting registered in the app manifest. You can go check `src/cascadia/CascadiaPackage/bin/x64/Debug/AppX/AppXManifest.xml` to see if there exist entries to the classes of your newly created DLL. If the references aren't there, double check that you've added `` blocks to both `WindowsTerminal.vcxproj` and `TerminalApp.vcxproj`. -- If you hit an extremely vague error along the lines of `Error in the DLL`, and right before that line you notice that your new DLL is loaded and unloaded right after each other, double check that your new DLL's definitions show up in the `AppXManifest.xml` file. If your new DLL is included as a reference to a project that rolls up to `CascadiaPackage`, double check that you've created a `.def` file for the project. Otherwise if your new project _does not_ roll up to a package that populates the `AppXManifest` references for you, you'll have to add those references yourself. +- If you hit an extremely vague error along the lines of `Error in the DLL`, and right before that line you notice that your new DLL is loaded and unloaded right after each other, double check that your new DLL's definitions show up in the `AppXManifest.xml` file. If your new DLL is included as a reference to a project that rolls up to `CascadiaPackage`, double check that you've created a `.def` file for the project. Otherwise, if your new project _does not_ roll up to a package that populates the `AppXManifest` references for you, you'll have to add those references yourself. diff --git a/doc/specs/#1564 - Settings UI/cascading-settings.md b/doc/specs/#1564 - Settings UI/cascading-settings.md index 78d8fd7bbf..0c358f945e 100644 --- a/doc/specs/#1564 - Settings UI/cascading-settings.md +++ b/doc/specs/#1564 - Settings UI/cascading-settings.md @@ -9,7 +9,7 @@ issue id: 1564 ## Abstract -Windows Terminal's settings model adheres to a cascading settings architecture. This allows a settings object to be defined incrementally across multiple layers of declarations. The value for any global setting like `copyOnSelect`, for example, is set to your settings.json value if one is defined, otherwise defaults.json, and otherwise a system set value. Profiles in particular are more complicated in that they must also take into account the values in `profiles.defaults` and dynamic profile generators. +Windows Terminal's settings model adheres to a cascading settings architecture. This allows a settings object to be defined incrementally across multiple layers of declarations. The value for any global setting like `copyOnSelect`, for example, is set to your settings.json value if one is defined; otherwise, defaults.json, and otherwise a system set value. Profiles in particular are more complicated in that they must also take into account the values in `profiles.defaults` and dynamic profile generators. This spec explores how to represent this feature in the Settings UI. diff --git a/doc/specs/#1595 - Suggestions UI/Snippets.md b/doc/specs/#1595 - Suggestions UI/Snippets.md index f80a9cf52f..39cd15de8a 100644 --- a/doc/specs/#1595 - Suggestions UI/Snippets.md +++ b/doc/specs/#1595 - Suggestions UI/Snippets.md @@ -529,7 +529,7 @@ their own workflows. * `--local`: Save to the `.wt.json` in the CWD, if there is one (or create one) * `--parent`: Save to the `.wt.json` in the first ancestor of the CWD, if - there is one. Otherwise create one here. + there is one. Otherwise, create one here. * `--settings`: Manually save to the settings file? * `--profile`: save to this profile???? Not sure if this is actually possible. Maybe with the `WT_SESSION_ID` env var to figure out which profile is in use diff --git a/doc/specs/#1790 - Font features and axes-spec.md b/doc/specs/#1790 - Font features and axes-spec.md index 26cc5fd40a..bc524a4099 100644 --- a/doc/specs/#1790 - Font features and axes-spec.md +++ b/doc/specs/#1790 - Font features and axes-spec.md @@ -23,7 +23,7 @@ In a similar vein, many fonts allow for setting variations on the font along cer ### Font features -It is already possible to pass in a list of [font feature structs](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ns-dwrite-dwrite_font_feature) to DWrite for it to handle. A font feature struct contains only 2 things: +It is already possible to pass in a list of [font feature structs](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ns-dwrite-dwrite_font_feature) to DWrite for it to handle. A font feature struct contains only 2 things: 1. A font feature tag 2. A parameter value @@ -78,7 +78,7 @@ Aside from additional parsing required for the settings file (which inherently o ### Compatibility -Older versions of Windows may not have the DWrite updates that allow for defining font features and axes of variation. We must make sure to fallback to the current implementation in these cases. +Older versions of Windows may not have the DWrite updates that allow for defining font features and axes of variation. We must make sure to fall back to the current implementation in these cases. ### Performance, Power, and Efficiency @@ -102,4 +102,4 @@ We will also need to consider how we want to represent this in the settings UI. [DWRITE_FONT_FEATURE structure](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ns-dwrite-dwrite_font_feature) -[DWRITE_FONT_AXIS_VALUE structure](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/ns-dwrite_3-dwrite_font_axis_value) \ No newline at end of file +[DWRITE_FONT_AXIS_VALUE structure](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/ns-dwrite_3-dwrite_font_axis_value) diff --git a/doc/specs/#4066 - Theme-controlled color scheme switch.md b/doc/specs/#4066 - Theme-controlled color scheme switch.md index 9cb41b3571..ed40c9f42d 100644 --- a/doc/specs/#4066 - Theme-controlled color scheme switch.md +++ b/doc/specs/#4066 - Theme-controlled color scheme switch.md @@ -17,7 +17,7 @@ I work remotely as a developer, so I have to spend a lot of hours in front of my Normally I like dark modes in all the programs and apps I use, but when there's too much sunlight, it becomes annoying, and sometimes even painful, to work in dark mode. So, I have all the programs and apps I use (at least, those that can) set to switch their color themes to what the system has. -The company I work for sent me a Macbook Pro, and my personal phone is an Android, both with automatic dark mode at sunset and light mode at sunrise, and in those devices it's been working relatively well. In Windows, as it is known, there's no such feature, so I manually change between dark and light mode when it's needed, and most of the programs and apps I use go along with this change. Windows Terminal, is not one of them. +The company I work for sent me a MacBook Pro, and my personal phone is an Android, both with automatic dark mode at sunset and light mode at sunrise, and in those devices it's been working relatively well. In Windows, as it is known, there's no such feature, so I manually change between dark and light mode when it's needed, and most of the programs and apps I use go along with this change. Windows Terminal, is not one of them. The theme changes just as expected, but in an app like this, this change only affects the top of the window, leaving almost all of the screen at the mercy of what the color scheme is, and it doesn't depend on the theme, which defeats any attempt to make a good use of the `system` theme feature. diff --git a/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md b/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md index 538da23725..625ef51e92 100644 --- a/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md +++ b/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md @@ -94,7 +94,7 @@ configurations: - `"useExisting"`: always glom to the most recent window, regardless of desktop. - `"useExistingOnSameDesktop"`: Only glom if there's an existing window on this - virtual desktop, otherwise create a new window. This will be the new default + virtual desktop; otherwise, create a new window. This will be the new default value. - `"useNew"`: Never glom, always create a new window. This is technically the current behavior of the Terminal. diff --git a/doc/specs/#5000 - Process Model 2.0/#5000 - Process Model 2.0.md b/doc/specs/#5000 - Process Model 2.0/#5000 - Process Model 2.0.md index 72a672cffa..620e5c30a6 100644 --- a/doc/specs/#5000 - Process Model 2.0/#5000 - Process Model 2.0.md +++ b/doc/specs/#5000 - Process Model 2.0/#5000 - Process Model 2.0.md @@ -362,7 +362,7 @@ Essentially, the probabilistic elective monarchy will work in the following way: register. 3. After registering as a server for `Monarch`s, attempt to create a `Monarch` using `winrt::create_instance`. -4. Using that `Monarch`, ask it for it's PID. +4. Using that `Monarch`, ask it for its PID. - If that PID is the same as the PID of the current process, then the window process knows that it is the monarch. - If that PID is some other process, then we know that we're not currently @@ -1102,7 +1102,7 @@ launch to use seems like an obvious next step. See also [#961]. - `true` or `"always"`: always glom to the most recent window, regardless of desktop - `"sameDesktop"`: Only glom if there's an existing window on this virtual - desktop, otherwise create a new window + desktop; otherwise, create a new window - `false` or `"never"`: Never glom, always create a new window. diff --git a/doc/specs/#605 - Search/spec.md b/doc/specs/#605 - Search/spec.md index 66a3b6dd54..e828cec0f8 100644 --- a/doc/specs/#605 - Search/spec.md +++ b/doc/specs/#605 - Search/spec.md @@ -13,7 +13,7 @@ This spec is for feature request #605 "Search". It goes over the details of a ne ## Inspiration -One of the superior features of iTerm2 is it's content search. The search comes in two variants: search from active tab and search from all tabs. In almost any editor, there is an roughly equivalent string search. We also want to realize search experience in Terminal. There will be two variants, search within one tab or from multiple tabs. We will start with one-tab search implementation. +One of the superior features of iTerm2 is it's content search. The search comes in two variants: search from active tab and search from all tabs. In almost any editor, there is a roughly equivalent string search. We also want to realize search experience in Terminal. There will be two variants, search within one tab or from multiple tabs. We will start with one-tab search implementation. ## Solution Design diff --git a/doc/specs/#7335 - Console Allocation Policy.md b/doc/specs/#7335 - Console Allocation Policy.md index c094a8edfc..f05df179d1 100644 --- a/doc/specs/#7335 - Console Allocation Policy.md +++ b/doc/specs/#7335 - Console Allocation Policy.md @@ -331,7 +331,7 @@ Are there other allocation policies we need to consider? - requires coordination between tooling teams both within and without Microsoft (regarding any tool that operates on or produces PE files) -- An exported symbol that shells can check for to determine whether to wait for the attached process to exit +- An exported symbol that shells can check for in order to determine whether to wait for the attached process to exit - relies on shells to update and check for this - cracking an executable to look for symbols is probably the last thing shells want to do - we could provide an API to determine whether to wait or return? diff --git a/doc/specs/#754 - Cascading Default Settings.md b/doc/specs/#754 - Cascading Default Settings.md index 73fd1928dc..5d43c2d0c0 100644 --- a/doc/specs/#754 - Cascading Default Settings.md +++ b/doc/specs/#754 - Cascading Default Settings.md @@ -360,7 +360,7 @@ GUID GetNamespaceGuid(IDynamicProfileGenerator& generator); GUID GetGuidForName(IDynamicProfileGenerator& generator, std::wstring& name); ``` -The generator does not _need_ to use `GetGuidForName` to generate guids for it's +The generator does not _need_ to use `GetGuidForName` to generate guids for its profiles. If the generator can determine another way to generate stable GUIDs for its profiles, it's free to use whatever method it wants. `GetGuidForName` is provided as a convenience. diff --git a/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md b/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md index 5426c9716d..6e27d19238 100644 --- a/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md +++ b/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md @@ -151,11 +151,11 @@ void CascadiaSettings::LayerJson(const Json::Value& json) // repeat the same for Profiles... } ``` -For `defaults.json`, `_globals` will now hold all of the values set in `defaults.json`. If any settings were omitted from the `defaults.json`, `_globals` will fallback to its parent (a `GlobalAppSettings` consisting purely of system-defined values). +For `defaults.json`, `_globals` will now hold all of the values set in `defaults.json`. If any settings were omitted from the `defaults.json`, `_globals` will fall back to its parent (a `GlobalAppSettings` consisting purely of system-defined values). -For `settings.json`, `_globals` will only hold the values set in `settings.json`. If any settings were omitted from `settings.json`, `_globals` will fallback to its parent (the `GlobalAppSettings` built from `defaults.json`). +For `settings.json`, `_globals` will only hold the values set in `settings.json`. If any settings were omitted from `settings.json`, `_globals` will fall back to its parent (the `GlobalAppSettings` built from `defaults.json`). -This process becomes a bit more complex for `Profile` because it can fallback in the following order: +This process becomes a bit more complex for `Profile` because it can fall back in the following order: 1. `settings.json` profile 2. `settings.json` `profiles.defaults` 3. (if a dynamic profile) the hard-coded value in the dynamic profile generator diff --git a/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md b/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md index c201c0bc33..6bf09a5786 100644 --- a/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md +++ b/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md @@ -101,8 +101,8 @@ The scopes would work as follows: this tab. - **TODO!: FOR DISCUSSION**: Should this disable the tab's "broadcastToAllPanes" setting? Or should it leave that alone? -* `"disableBroadcastInput"`: Set the global setting to false, the tab's setting - to false, and clear the set of panes being broadcasted to for this tab. +* `"disableBroadcastInput"`: For this tab, set the global setting to false, + the tab's setting to false, and clear the set of panes being broadcasted. - **TODO!** This could also just be `"action": "toggleBroadcastInput", "scope": "none"` @@ -161,7 +161,7 @@ As far as actions, we're looking at something like: from the broadcast set. Otherwise, add all the panes from this tab to the broadcast set. * **D** toggle sending input to the current pane - * If this pane is in the broadcast set, remove it. Otherwise add it. + * If this pane is in the broadcast set, remove it. Otherwise, add it. This seems to break down into the following actions: ```json diff --git a/doc/terminal-a11y-2023.md b/doc/terminal-a11y-2023.md index 3686120319..60be666fb9 100644 --- a/doc/terminal-a11y-2023.md +++ b/doc/terminal-a11y-2023.md @@ -8,12 +8,12 @@ Since accessibility is a very broad area, this document is intended to present r ### First-party terminals For many years, Console Host (Conhost) was the only first-party terminal on Windows. In 2019, Windows Terminal was released to the world as an open source first-party terminal. Windows Terminal was distributed through the Microsoft Store and received regular updates throughout the year, much more frequently than Conhost. In October 2022, Windows Terminal was enabled as the default terminal on Windows. -A significant amount of code is shared between Conhost and Windows Terminal to create the terminal area. To enable an accessible experience for this area, a shared UI Automation provider was introduced in 2019[^1], enabling accessibility tools to navigate and read contents from the terminal area. In 2020, Windows Terminal was updated to dispatch UIA events signaling when the cursor position, text output, or selection changed; this left the work of identifying what changed in the output to the attached screen reader application[^2]. In 2022, Windows Terminal was updated to dispatch UIA notifications with a payload of what text was written to the screen[^3]. +A significant amount of code is shared between Conhost and Windows Terminal to create the terminal area. To enable an accessible experience for this area, a shared UI Automation provider was introduced in 2019[^1], enabling accessibility tools to navigate and read contents from the terminal area. In 2020, Windows Terminal was updated to dispatch UIA events signaling when the cursor position, text output, or selection changed; this left the work of identifying what changed in the output to the attached screen reader application[^2]. In 2022, Windows Terminal was updated to dispatch UIA notifications with a payload of what text was written to the screen[^3]. ### Internal Partners There are many first-party command-line applications on Windows. The following are a few examples of those that are regularly updated: - [**GitHub CLI**](https://cli.github.com/): a tool that can be used to query and interact with GitHub repos (open source) -- [**Winget**](https://github.com/microsoft/winget-cli): a tool to install applications and other packages +- [**WinGet**](https://github.com/microsoft/winget-cli): a tool to install applications and other packages - [**PSReadLine**](https://github.com/PowerShell/PSReadLine): a PowerShell module that enhances the input line experience - [**Windows Subsystem for Linux (WSL)**](https://learn.microsoft.com/en-us/windows/wsl/): a tool to manage and run GNU/Linux environments without a traditional virtual machine - [**PowerShell**](https://github.com/PowerShell/PowerShell): a cross-platform command-line shell (open source) @@ -33,7 +33,7 @@ The following examples don't take over the entire viewport: - [**Oh My Posh**](https://ohmyposh.dev/): a tool to customize shell prompts - **git**: a tool for version control The following examples operate as command-line shells: -- [**Bash**](https://www.gnu.org/software/bash/) is the default shell for most Linux distributions +- [**Bash**](https://www.gnu.org/software/bash/) is the default shell for most Linux distributions - [**Fish shell**](https://fishshell.com/) provides a rich shell experience with features like autosuggestion support and VGA colors - [**Z shell**](https://zsh.sourceforge.io/) is an extended Bourne shell @@ -113,7 +113,7 @@ This issue is tracked by [megathread: Scrollbar Marks · Issue #11000](https://g ### Mark Mode support for degenerate range [PR #13053](https://github.com/microsoft/terminal/pull/13053) added support for mark mode in Windows Terminal. Mark mode allows users to create and modify selections by exclusively using the keyboard. However, screen reader users have reported it as a strange experience because it always has a cell of text selected; this results in the screen reader reading "x selected, y unselected" as opposed to the expected "x" when moving the cursor around. -Unfortunately, the changes required to fix this are very extensive because selections are stored as two inclusive terminal coordinates, which makes it impossible to represent an empty selection. +Unfortunately, the changes required to fix this are very extensive because selections are stored as two inclusive terminal coordinates, which makes it impossible to represent an empty selection. This is tracked by [A11y: windows terminal emits selection/deselection events in mark mode when navigating with arrow keys · Issue #13447](https://github.com/microsoft/terminal/issues/13447). @@ -158,7 +158,7 @@ In 2022, Windows Terminal added UI Automation notifications that contained a pay UIA notifications have provided many compatibility benefits since screen readers automatically read notifications they receive. Additionally, this has provided the possibility for major performance enhancements as screen readers may no longer be required to diff the text buffer and figure out what has changed. NVDA has prototyped listening to notifications and ignoring text changed events entirely[^7]. However, it reveals underlying challenges with this new model such as how to handle passwords. The proposals listed in this section are intended to have Windows Terminal achieve improved performance and accessibility quality. #### VT Screen Reader Control -Some command-line applications are simply too difficult to create a consistent accessible experience. Applications that draw decorative content, for example, may have that content read by a screen reader. +Some command-line applications are simply too difficult to create a consistent accessible experience. Applications that draw decorative content, for example, may have that content read by a screen reader. In 2019, Daniel Imms wrote a spec proposing a VT sequence that can partially control the attached screen reader[^8]. This VT sequence consists of three main formats: 1. Stop announcing incoming data to the screen reader. The screen reader will resume announcing incoming data if any key is pressed. @@ -214,4 +214,4 @@ Generally, the reasoning behind these priorities can be broken down as follows: [^5]: [Implement the Delta E algorithm to improve color perception by PankajBhojwani · Pull Request #11095](https://github.com/microsoft/terminal/pull/11095) [^6]: [Change AdjustIndistinguishableColors to an enum setting instead of a boolean setting by PankajBhojwani · Pull Request #13512](https://github.com/microsoft/terminal/pull/13512) [^7]: [Prototype for Windows Terminal: Use notifications instead of monitoring for new text by leonardder · Pull Request #14047 · nvaccess/nvda (github.com)](https://github.com/nvaccess/nvda/pull/14047) -[^8]: [Control Screen Reader from Applications (#18) · Issues · terminal-wg / specifications · GitLab](https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/18) \ No newline at end of file +[^8]: [Control Screen Reader from Applications (#18) · Issues · terminal-wg / specifications · GitLab](https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/18) diff --git a/policies/en-US/WindowsTerminal.adml b/policies/en-US/WindowsTerminal.adml index f2bcf71d3c..b0be1c3d57 100644 --- a/policies/en-US/WindowsTerminal.adml +++ b/policies/en-US/WindowsTerminal.adml @@ -22,7 +22,7 @@ Note: Existing profiles will disappear from Windows Terminal after adding their Default terminal application Select the default terminal application used in Windows. -If you select Windows Terminal Preview and it is not installed the system will fallback to the legacy Windows Console Host. (Please note that the settings interfaces showing "Let windows decide" in this case as configuration.) +If you select Windows Terminal Preview and it is not installed the system will fall back to the legacy Windows Console Host. (Please note that the settings interfaces showing "Let windows decide" in this case as configuration.) Automatic selection (Windows Terminal, if available) Windows Console Host (legacy) Windows Terminal diff --git a/samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml b/samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml index 2c67f1cb0e..36cc20b91f 100644 --- a/samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml +++ b/samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml @@ -32,7 +32,7 @@ BasedOn="{StaticResource TitleBarButtonStyle}" TargetType="{x:Type Button}"> - + diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 11f48ec0f6..6f0336e8bc 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -2969,7 +2969,7 @@ void TextBuffer::_SerializeRow(const ROW& row, const til::CoordType startX, cons // parameter and we'll calculate the position of the _end_ of those rows in // the new buffer. The rows's new value is placed back into this parameter. // Return Value: -// - S_OK if we successfully copied the contents to the new buffer, otherwise an appropriate HRESULT. +// - S_OK if we successfully copied the contents to the new buffer; otherwise, an appropriate HRESULT. void TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const Viewport* lastCharacterViewport, PositionInformation* positionInfo) { const auto& oldCursor = oldBuffer.GetCursor(); diff --git a/src/buffer/out/ut_textbuffer/ReflowTests.cpp b/src/buffer/out/ut_textbuffer/ReflowTests.cpp index e46e415e37..58e7766c89 100644 --- a/src/buffer/out/ut_textbuffer/ReflowTests.cpp +++ b/src/buffer/out/ut_textbuffer/ReflowTests.cpp @@ -573,7 +573,7 @@ namespace TestCase{ // This triggers the cursor being walked forward w/ newlines to maintain // distance from the last char in the buffer - L"SBCS, cursor at end of buffer, otherwise same as previous test", + L"SBCS, cursor at end of buffer; otherwise, same as previous test", { TestBuffer{ { 6, 5 }, diff --git a/src/cascadia/ShellExtension/OpenTerminalHere.cpp b/src/cascadia/ShellExtension/OpenTerminalHere.cpp index 88d4f43551..e3d0f813f6 100644 --- a/src/cascadia/ShellExtension/OpenTerminalHere.cpp +++ b/src/cascadia/ShellExtension/OpenTerminalHere.cpp @@ -21,7 +21,7 @@ static constexpr std::wstring_view VerbName{ L"WindowsTerminalOpenHere" }; // Arguments: // - psiItemArray: a IShellItemArray which contains the item that's selected. // Return Value: -// - S_OK if we successfully attempted to launch the Terminal, otherwise a +// - S_OK if we successfully attempted to launch the Terminal; otherwise, a // failure from an earlier HRESULT. HRESULT OpenTerminalHere::Invoke(IShellItemArray* psiItemArray, IBindCtx* /*pBindContext*/) diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 6964892963..50bd7cd122 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -517,7 +517,7 @@ namespace winrt::TerminalApp::implementation else { // Mark as handled only when the move succeeded (e.g. when there - // is a pane to move to), otherwise mark as unhandled so the + // is a pane to move to); otherwise, mark as unhandled so the // keychord can propagate to the terminal (GH#6129) const auto moveSucceeded = _MoveFocus(realArgs.FocusDirection()); args.Handled(moveSucceeded); diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 6a0d456142..53c3fde491 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -194,7 +194,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Attempt to load the settings. If we fail for any reason, returns an error. // Return Value: - // - S_OK if we successfully parsed the settings, otherwise an appropriate HRESULT. + // - S_OK if we successfully parsed the settings; otherwise, an appropriate HRESULT. [[nodiscard]] HRESULT AppLogic::_TryLoadSettings() noexcept { auto hr = E_FAIL; diff --git a/src/cascadia/TerminalApp/ColorHelper.cpp b/src/cascadia/TerminalApp/ColorHelper.cpp index c2dd4c2303..2235cc6b37 100644 --- a/src/cascadia/TerminalApp/ColorHelper.cpp +++ b/src/cascadia/TerminalApp/ColorHelper.cpp @@ -8,7 +8,7 @@ using namespace winrt::TerminalApp; // - color: this color is going to be examined whether it // is light or not // Return Value: -// - true of light, false if dark +// - true if light, false if dark bool ColorHelper::IsBrightColor(const winrt::Windows::UI::Color& color) { // https://www.w3.org/TR/AERT#color-contrast diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index e2e7526db8..13d058feab 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -338,7 +338,7 @@ namespace winrt::TerminalApp::implementation } else if (key == VirtualKey::Escape) { - // Dismiss the palette if the text is empty, otherwise clear the + // Dismiss the palette if the text is empty; otherwise, clear the // search string. if (_searchBox().Text().empty()) { diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 3255c5fba3..30ca4714eb 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -1052,7 +1052,7 @@ std::shared_ptr Pane::GetActivePane() // Arguments: // - // Return Value: -// - nullptr if this Pane is an unfocused parent, otherwise the TermControl of this Pane. +// - nullptr if this Pane is an unfocused parent; otherwise, the TermControl of this Pane. TermControl Pane::GetLastFocusedTerminalControl() { if (!_IsLeaf()) @@ -1105,7 +1105,7 @@ IPaneContent Pane::GetLastFocusedContent() // Arguments: // - // Return Value: -// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane. +// - nullptr if this Pane is a parent; otherwise, the TermControl of this Pane. TermControl Pane::GetTerminalControl() const { if (const auto& terminalPane{ _getTerminalContent() }) @@ -1597,7 +1597,7 @@ void Pane::_CloseChildRoutine(const bool closeFirst) return; } - // Setup the animation + // Set up the animation auto removedChild = closeFirst ? _firstChild : _secondChild; auto remainingChild = closeFirst ? _secondChild : _firstChild; @@ -1998,7 +1998,7 @@ void Pane::_SetupEntranceAnimation() if (splitWidth) { // If we're animating the first child, then stick to the top/left of - // the parent pane, otherwise use the bottom/right. This is always + // the parent pane; otherwise, use the bottom/right. This is always // the "outside" of the parent pane. childGrid.HorizontalAlignment(isFirstChild ? HorizontalAlignment::Left : HorizontalAlignment::Right); if (control) @@ -2023,7 +2023,7 @@ void Pane::_SetupEntranceAnimation() else { // If we're animating the first child, then stick to the top/left of - // the parent pane, otherwise use the bottom/right. This is always + // the parent pane; otherwise, use the bottom/right. This is always // the "outside" of the parent pane. childGrid.VerticalAlignment(isFirstChild ? VerticalAlignment::Top : VerticalAlignment::Bottom); if (control) @@ -2538,7 +2538,7 @@ std::pair Pane::_CalcChildrenSizes(const float fullSize) const // user doesn't get any pane shrank when they actually expand the window or parent pane. // That is also required by the layout algorithm. // Arguments: -// - widthOrHeight: if true, operates on width, otherwise on height. +// - widthOrHeight: if true, operates on width; otherwise, on height. // - fullSize: the amount of space in pixels that should be filled by our children and // their separator. Can be arbitrarily low. // Return Value: @@ -2600,7 +2600,7 @@ Pane::SnapChildrenSizeResult Pane::_CalcSnappedChildrenSizes(const bool widthOrH // align with their character grids as close as possible. Snaps to closes match // (either upward or downward). Also makes sure to fit in minimal sizes of the panes. // Arguments: -// - widthOrHeight: if true operates on width, otherwise on height +// - widthOrHeight: if true operates on width; otherwise, on height // - dimension: a dimension (width or height) to snap // Return Value: // - A value corresponding to the next closest snap size for this Pane, either upward or downward @@ -2615,7 +2615,7 @@ float Pane::CalcSnappedDimension(const bool widthOrHeight, const float dimension // align with their character grids as close as possible. Also makes sure to // fit in minimal sizes of the panes. // Arguments: -// - widthOrHeight: if true operates on width, otherwise on height +// - widthOrHeight: if true operates on width; otherwise, on height // - dimension: a dimension (width or height) to be snapped // Return Value: // - pair of floats, where first value is the size snapped downward (not greater than @@ -2702,11 +2702,11 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const // Method Description: // - Increases size of given LayoutSizeNode to match next possible 'snap'. In case of leaf -// pane this means the next cell of the terminal. Otherwise it means that one of its children +// pane this means the next cell of the terminal. Otherwise, it means that one of its children // advances (recursively). It expects the given node and its descendants to have either // already snapped or minimum size. // Arguments: -// - widthOrHeight: if true operates on width, otherwise on height. +// - widthOrHeight: if true operates on width; otherwise, on height. // - sizeNode: a layout size node that corresponds to this pane. // Return Value: // - @@ -2877,7 +2877,7 @@ Size Pane::_GetMinSize() const // - Builds a tree of LayoutSizeNode that matches the tree of panes. Each node // has minimum size that the corresponding pane can have. // Arguments: -// - widthOrHeight: if true operates on width, otherwise on height +// - widthOrHeight: if true operates on width; otherwise, on height // Return Value: // - Root node of built tree that matches this pane. Pane::LayoutSizeNode Pane::_CreateMinSizeTree(const bool widthOrHeight) const @@ -2897,7 +2897,7 @@ Pane::LayoutSizeNode Pane::_CreateMinSizeTree(const bool widthOrHeight) const // - Adjusts split position so that no child pane is smaller then its // minimum size // Arguments: -// - widthOrHeight: if true, operates on width, otherwise on height. +// - widthOrHeight: if true, operates on width; otherwise, on height. // - requestedValue: split position value to be clamped // - totalSize: size (width or height) of the parent pane // Return Value: diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp index 28cb64b5aa..fe03a94f78 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp @@ -27,7 +27,7 @@ namespace winrt::TerminalApp::implementation // Arguments: // - actionAndArgs: the ShortcutAction and associated args to raise an event for. // Return Value: - // - true if we handled the event was handled, else false. + // - true if the event was handled, else false. bool ShortcutActionDispatch::DoAction(const winrt::Windows::Foundation::IInspectable& sender, const ActionAndArgs& actionAndArgs) { diff --git a/src/cascadia/TerminalApp/SuggestionsControl.cpp b/src/cascadia/TerminalApp/SuggestionsControl.cpp index 3ebc7ca158..ed2bb3f686 100644 --- a/src/cascadia/TerminalApp/SuggestionsControl.cpp +++ b/src/cascadia/TerminalApp/SuggestionsControl.cpp @@ -421,7 +421,7 @@ namespace winrt::TerminalApp::implementation } else if (key == VirtualKey::Escape) { - // Dismiss the palette if the text is empty, otherwise clear the + // Dismiss the palette if the text is empty; otherwise, clear the // search string. if (_searchBox().Text().empty()) { diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 810ce5272b..f900e05cb7 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1289,7 +1289,7 @@ namespace winrt::TerminalApp::implementation // // We need to do this here, to ensure we tell the ConptyConnection // the correct starting path. If we're being invoked from another - // terminal instance (e.g. wt -w 0 -d .), then we have switched our + // terminal instance (e.g. `wt -w 0 -d .`), then we have switched our // CWD to the provided path. We should treat the StartingDirectory // as relative to the current CWD. // @@ -3970,7 +3970,7 @@ namespace winrt::TerminalApp::implementation // Arguments: // - args: the ExecuteCommandlineArgs to synthesize a list of startup actions for. // Return Value: - // - an empty list if we failed to parse, otherwise a list of actions to execute. + // - an empty list if we failed to parse; otherwise, a list of actions to execute. std::vector TerminalPage::ConvertExecuteCommandlineToActions(const ExecuteCommandlineArgs& args) { ::TerminalApp::AppCommandlineArgs appArgs; diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 88a839949c..c2afef3412 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -327,7 +327,7 @@ namespace winrt::TerminalApp::implementation // - Show a ContentDialog with buttons to take further action. Uses the // FrameworkElements provided as the title and content of this dialog, and // displays buttons (or a single button). Two buttons (primary and secondary) - // will be displayed if this is an warning dialog for closing the terminal, + // will be displayed if this is a warning dialog for closing the terminal, // this allows the users to abandon the closing action. Otherwise, a single // close button will be displayed. // - Only one dialog can be visible at a time. If another dialog is visible diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 61d699a6cc..b2ad7b7549 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -619,7 +619,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - vkey: The vkey of the key pressed. // - scanCode: The scan code of the key pressed. // - modifiers: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. - // - keyDown: If true, the key was pressed, otherwise the key was released. + // - keyDown: If true, the key was pressed; otherwise, the key was released. bool ControlCore::TrySendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates modifiers, @@ -1357,7 +1357,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation return false; } - // use action's copyFormatting if it's present, else fallback to globally + // use action's copyFormatting if it's present, else fall back to globally // set copyFormatting. const auto copyFormats = formats != nullptr ? formats.Value() : _settings->CopyFormatting(); diff --git a/src/cascadia/TerminalControl/ControlInteractivity.h b/src/cascadia/TerminalControl/ControlInteractivity.h index dcf2c811d0..02b198a77a 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.h +++ b/src/cascadia/TerminalControl/ControlInteractivity.h @@ -104,7 +104,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // // ControlCore::AttachUiaEngine receives a IRenderEngine as a raw pointer, which we own. // We must ensure that we first destroy the ControlCore before the UiaEngine instance - // in order to safely resolve this unsafe pointer dependency. Otherwise a deallocated + // in order to safely resolve this unsafe pointer dependency. Otherwise, a deallocated // IRenderEngine is accessed when ControlCore calls Renderer::TriggerTeardown. // (C++ class members are destroyed in reverse order.) std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine; diff --git a/src/cascadia/TerminalControl/HwndTerminalAutomationPeer.cpp b/src/cascadia/TerminalControl/HwndTerminalAutomationPeer.cpp index 6eae42a665..92724a2c67 100644 --- a/src/cascadia/TerminalControl/HwndTerminalAutomationPeer.cpp +++ b/src/cascadia/TerminalControl/HwndTerminalAutomationPeer.cpp @@ -45,7 +45,7 @@ static std::wstring Sanitize(std::wstring_view text) // Arguments: // - text: the string we're validating // Return Value: -// - true, if the text is readable. false, otherwise. +// - true, if the text is readable; otherwise, false. static constexpr bool IsReadable(std::wstring_view text) { for (const auto c : text) diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 67809d4cea..ea8446f04e 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -1033,7 +1033,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - Set up each layer's brush used to display the control's background. // - Respects the settings for acrylic, background image and opacity from // _settings. - // * If acrylic is not enabled, setup a solid color background, otherwise + // * If acrylic is not enabled, set up a solid color background; otherwise, // use bgcolor as acrylic's tint // - Avoids image flickering and acrylic brush redraw if settings are changed // but the appropriate brush is still in place. @@ -1898,7 +1898,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - vkey: The vkey of the key pressed. // - scanCode: The scan code of the key pressed. // - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. - // - keyDown: If true, the key was pressed, otherwise the key was released. + // - keyDown: If true, the key was pressed; otherwise, the key was released. bool TermControl::_TrySendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates modifiers, @@ -2982,7 +2982,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - Adjusts given dimension (width or height) so that it aligns to the character grid. // The snap is always downward. // Arguments: - // - widthOrHeight: if true operates on width, otherwise on height + // - widthOrHeight: if true operates on width; otherwise, on height // - dimension: a dimension (width or height) to be snapped // Return Value: // - A dimension that would be aligned to the character grid. diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 7fe3db1be0..2f9dad9784 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -242,7 +242,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // // ControlCore::AttachUiaEngine receives a IRenderEngine as a raw pointer, which we own. // We must ensure that we first destroy the ControlCore before the UiaEngine instance - // in order to safely resolve this unsafe pointer dependency. Otherwise a deallocated + // in order to safely resolve this unsafe pointer dependency. Otherwise, a deallocated // IRenderEngine is accessed when ControlCore calls Renderer::TriggerTeardown. // (C++ class members are destroyed in reverse order.) // Further, the TermControlAutomationPeer must be destructed after _uiaEngine! diff --git a/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp b/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp index 1adb9af5ce..90ad7b9876 100644 --- a/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp +++ b/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp @@ -52,7 +52,7 @@ static std::wstring Sanitize(std::wstring_view text) // Arguments: // - text: the string we're validating // Return Value: -// - true, if the text is readable. false, otherwise. +// - true, if the text is readable; otherwise, false. static constexpr bool IsReadable(std::wstring_view text) { for (const auto c : text) @@ -305,7 +305,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation hstring TermControlAutomationPeer::GetNameCore() const { - // fallback to title if profile name is empty + // fall back to title if profile name is empty if (auto control{ _termControl.get() }) { const auto profileName = control->GetProfileName(); diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 005916822d..b292fd813b 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -469,7 +469,7 @@ void Terminal::TrySnapOnInput() // Parameters: // - // Return value: -// - true, if we are tracking mouse input. False, otherwise +// - true, if we are tracking mouse input; otherwise, false. bool Terminal::IsTrackingMouseInput() const noexcept { return _getTerminalInput().IsTrackingMouseInput(); @@ -482,7 +482,7 @@ bool Terminal::IsTrackingMouseInput() const noexcept // Parameters: // - // Return value: -// - true, if we are tracking mouse input. False, otherwise +// - true, if we are tracking mouse input; otherwise, false. bool Terminal::ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const noexcept { @@ -599,7 +599,7 @@ std::optional Terminal::GetHyperlinkIntervalFromViewportPos // - vkey: The vkey of the last pressed key. // - scanCode: The scan code of the last pressed key. // - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. -// - keyDown: If true, the key was pressed, otherwise the key was released. +// - keyDown: If true, the key was pressed; otherwise, the key was released. // Return Value: // - true if we translated the key event, and it should not be processed any further. // - false if we did not translate the key, and it should be processed into a character. @@ -921,7 +921,7 @@ void Terminal::_StoreKeyEvent(const WORD vkey, const WORD scanCode) noexcept // Arguments: // - scanCode: The scan code. // Return Value: -// - The key code matching the given scan code. Otherwise 0. +// - The key code matching the given scan code. Otherwise, 0. WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept { const auto codes = _lastKeyEventCodes.value_or(KeyEventCodes{}); diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 7d8a706274..9822d68208 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -377,7 +377,7 @@ void Terminal::NotifyBufferRotation(const int delta) auto selection{ _selection.write() }; wil::hide_name _selection; // If the end of the selection will be out of range after the move, we just - // clear the selection. Otherwise we move both the start and end points up + // clear the selection. Otherwise, we move both the start and end points up // by the given delta and clamp to the first row. if (selection->end.y < delta) { diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index d588b67cb0..f0019d6614 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -438,7 +438,7 @@ void Terminal::ExpandSelectionToWord() // Arguments: // - dir: the direction we're scanning the buffer in to find the hyperlink of interest // Return Value: -// - true if we found a hyperlink to select (and selected it). False otherwise. +// - true if we found a hyperlink to select (and selected it); otherwise, false. void Terminal::SelectHyperlink(const SearchDirection dir) { if (_selectionMode != SelectionInteractionMode::Mark) diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index b5fdc867b6..6f9c460797 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -258,7 +258,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - // Couldn't find the selected item, fallback to first menu item + // Couldn't find the selected item, fall back to first menu item // This happens when the selected item was a profile which doesn't exist in the new configuration // We can use menuItemsSTL here because the only things they miss are profile entries. const auto& firstItem{ _menuItemSource.GetAt(0).as() }; diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 577903edc0..730e646310 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -137,7 +137,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Arguments: // - actionID: the internal ID associated with a Command // Return Value: - // - The command if it exists in this layer, otherwise nullptr + // - The command if it exists in this layer; otherwise, nullptr Model::Command ActionMap::_GetActionByID(const winrt::hstring& actionID) const { // Check current layer @@ -692,7 +692,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - oldKeys: the key binding that we are rebinding // - newKeys: the new key chord that is being used to replace oldKeys // Return Value: - // - true, if successful. False, otherwise. + // - true, if successful; otherwise, false. bool ActionMap::RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys) { const auto cmd{ GetActionByKeyChord(oldKeys) }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index bbbb9172c7..16f71dd130 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -1014,7 +1014,7 @@ void SettingsLoader::_appendProfile(winrt::com_ptr&& profile, const win } // If the given ParsedSettings instance contains a profile with the given profile's GUID, -// the profile is added as a parent. Otherwise a new child profile is created. +// the profile is added as a parent. Otherwise, a new child profile is created. void SettingsLoader::_addUserProfileParent(const winrt::com_ptr& profile) { if (const auto [it, inserted] = userSettings.profilesByGuid.emplace(profile->Guid(), nullptr); !inserted) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 7ec4d07374..d1ea74cfd5 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -165,7 +165,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Arguments: // - json: The Json::Value representing the command object we should get the name for. // Return Value: - // - the empty string if we couldn't find a name, otherwise the command's name. + // - the empty string if we couldn't find a name; otherwise, the command's name. static std::optional _nameFromJson(const Json::Value& json) { if (const auto name{ json[JsonKey(NameKey)] }) @@ -489,7 +489,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - expandable: the Command to potentially turn into more commands // - profiles: A list of all the profiles that this command should be expanded on. // Return Value: - // - and empty vector if the command wasn't expandable, otherwise a list of + // - and empty vector if the command wasn't expandable; otherwise, a list of // the newly-created commands. std::vector Command::_expandCommand(Command* const expandable, IVectorView profiles, diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index ab2b93ce96..ef3256f426 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -89,7 +89,7 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils { static constexpr std::optional EmptyV() { return std::nullopt; } static constexpr bool HasValue(const std::optional& o) { return o.has_value(); } - // We can return a reference here because the original value is stored inside an std::optional + // We can return a reference here because the original value is stored inside a std::optional static constexpr auto&& Value(const std::optional& o) { return *o; } }; diff --git a/src/cascadia/TerminalSettingsModel/Profile.cpp b/src/cascadia/TerminalSettingsModel/Profile.cpp index d1b2efb08b..1e5c61089a 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.cpp +++ b/src/cascadia/TerminalSettingsModel/Profile.cpp @@ -310,7 +310,7 @@ std::wstring Profile::EvaluateStartingDirectory(const std::wstring& directory) } // Function Description: -// - Generates a unique guid for a profile, given the name. For an given name, will always return the same GUID. +// - Generates a unique guid for a profile, given the name. For a given name, will always return the same GUID. // Arguments: // - name: The name to generate a unique GUID from // Return Value: diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index ff9860aef6..9d3540b145 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -305,7 +305,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _StartingDirectory = profile.EvaluatedStartingDirectory(); - // GH#2373: Use the tabTitle as the starting title if it exists, otherwise + // GH#2373: Use the tabTitle as the starting title if it exists; otherwise, // use the profile name _StartingTitle = !profile.TabTitle().empty() ? profile.TabTitle() : profile.Name(); diff --git a/src/cascadia/TerminalSettingsModel/Theme.cpp b/src/cascadia/TerminalSettingsModel/Theme.cpp index 90fd1d1a76..3b79b5297d 100644 --- a/src/cascadia/TerminalSettingsModel/Theme.cpp +++ b/src/cascadia/TerminalSettingsModel/Theme.cpp @@ -372,7 +372,7 @@ winrt::hstring Theme::ToString() // RequestedTheme, this saves some hassle. If there wasn't a `window` defined // for this theme, this'll quickly just return `system`, to use the OS theme. // Return Value: -// - the set applicationTheme for this Theme, otherwise the system theme. +// - the set applicationTheme for this Theme; otherwise, the system theme. winrt::WUX::ElementTheme Theme::RequestedTheme() const noexcept { return _Window ? _Window.RequestedTheme() : winrt::WUX::ElementTheme::Default; diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 6cdc7ff8e7..1e388f0f32 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -951,7 +951,7 @@ void AppHost::_updateTheme() _window->UseDarkTheme(_isActuallyDarkTheme(theme.RequestedTheme())); // Update the window frame. If `rainbowFrame:true` is enabled, then that - // will be used. Otherwise we'll try to use the `FrameBrush` set in the + // will be used. Otherwise, we'll try to use the `FrameBrush` set in the // terminal window, as that will have the right color for the ThemeColor for // this setting. If that value is null, then revert to the default frame // color. diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 414e4043f8..95d971ecea 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -692,7 +692,7 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept // If there's a taskbar on any side of the monitor, reduce our size // a little bit on that edge. // - // Note to future code archeologists: + // Note to future code archaeologists: // This doesn't seem to work for fullscreen on the primary display. // However, testing a bunch of other apps with fullscreen modes // and an auto-hiding taskbar has shown that _none_ of them diff --git a/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs b/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs index ab6a26d460..611cde52a6 100644 --- a/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs +++ b/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs @@ -103,7 +103,7 @@ namespace Microsoft.Terminal.Wpf } /// - /// Gets the selected text in the terminal, clearing the selection. Otherwise returns an empty string. + /// Gets the selected text in the terminal, clearing the selection. Otherwise, returns an empty string. /// /// Selected text, empty string if no content is selected. public string GetSelectedText() diff --git a/src/host/ConsoleArguments.cpp b/src/host/ConsoleArguments.cpp index d1ecdcb2b1..8241a62e0b 100644 --- a/src/host/ConsoleArguments.cpp +++ b/src/host/ConsoleArguments.cpp @@ -182,7 +182,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In // should be at (index+1). index will be decremented by one on success. // pSetting: receives the string at index+1 // Return Value: -// S_OK if we parsed the string successfully, otherwise E_INVALIDARG indicating +// S_OK if we parsed the string successfully; otherwise, E_INVALIDARG indicating // failure. [[nodiscard]] HRESULT ConsoleArguments::s_GetArgumentValue(_Inout_ std::vector& args, _Inout_ size_t& index, @@ -213,7 +213,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In // should be at (index+1). index will be decremented by one on success. // pSetting: receives the string at index+1 // Return Value: -// S_OK if we parsed the string successfully, otherwise E_INVALIDARG indicating +// S_OK if we parsed the string successfully; otherwise, E_INVALIDARG indicating // failure. [[nodiscard]] HRESULT ConsoleArguments::s_HandleFeatureValue(_Inout_ std::vector& args, _Inout_ size_t& index) { @@ -243,7 +243,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In // should be at (index+1). index will be decremented by one on success. // pSetting: receives the short at index+1 // Return Value: -// S_OK if we parsed the short successfully, otherwise E_INVALIDARG indicating +// S_OK if we parsed the short successfully; otherwise, E_INVALIDARG indicating // failure. This could be the case for non-numeric arguments, or for >SHORT_MAX args. [[nodiscard]] HRESULT ConsoleArguments::s_GetArgumentValue(_Inout_ std::vector& args, _Inout_ size_t& index, @@ -332,7 +332,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In // index: the index of the argument of which to start the commandline from. // skipFirst: if true, omit the arg at index (which should be "--") // Return Value: -// S_OK if we parsed the string successfully, otherwise E_INVALIDARG indicating +// S_OK if we parsed the string successfully; otherwise, E_INVALIDARG indicating // failure. [[nodiscard]] HRESULT ConsoleArguments::_GetClientCommandline(_Inout_ std::vector& args, const size_t index, const bool skipFirst) { @@ -369,7 +369,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In // Arguments: // // Return Value: -// S_OK if we parsed our _commandline successfully, otherwise E_INVALIDARG +// S_OK if we parsed our _commandline successfully; otherwise, E_INVALIDARG // indicating failure. [[nodiscard]] HRESULT ConsoleArguments::ParseCommandline() { diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index be1d8bd330..53911496a0 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -73,7 +73,7 @@ using namespace Microsoft::Console::Interactivity; // SignalHandle: an optional file handle that will be used to send signals into the console. // This represents the ability to send signals to a *nix tty/pty. // Return Value: -// S_OK if we initialized successfully, otherwise an appropriate HRESULT +// S_OK if we initialized successfully; otherwise, an appropriate HRESULT // indicating failure. [[nodiscard]] HRESULT VtIo::_Initialize(const HANDLE InHandle, const HANDLE OutHandle, @@ -136,7 +136,7 @@ bool VtIo::IsUsingVt() const // Arguments: // // Return Value: -// S_OK if we started successfully or had nothing to start, otherwise an +// S_OK if we started successfully or had nothing to start; otherwise, an // appropriate HRESULT indicating failure. [[nodiscard]] HRESULT VtIo::StartIfNeeded() { @@ -361,7 +361,7 @@ void VtIo::FormatAttributes(std::wstring& target, const TextAttribute& attribute wchar_t VtIo::SanitizeUCS2(wchar_t ch) { // If any of the values in the buffer are C0 or C1 controls, we need to - // convert them to printable codepoints, otherwise they'll end up being + // convert them to printable codepoints; otherwise, they'll end up being // evaluated as control characters by the receiving terminal. We use the // DOS 437 code page for the C0 controls and DEL, and just a `?` for the // C1 controls, since that's what you would most likely have seen in the diff --git a/src/host/exe/exemain.cpp b/src/host/exe/exemain.cpp index 2b95ba0916..054cdea490 100644 --- a/src/host/exe/exemain.cpp +++ b/src/host/exe/exemain.cpp @@ -175,7 +175,7 @@ static bool ShouldUseLegacyConhost(const ConsoleArguments& args) { // setup status error hr = HRESULT_FROM_WIN32(GetLastError()); - // fallback to V2 if conhostv1.dll cannot be loaded. + // fall back to V2 if conhostv1.dll cannot be loaded. useV2 = true; } diff --git a/src/host/ft_host/API_AliasTestsHelpers.hpp b/src/host/ft_host/API_AliasTestsHelpers.hpp index 9e2f751e7a..49dd8eb221 100644 --- a/src/host/ft_host/API_AliasTestsHelpers.hpp +++ b/src/host/ft_host/API_AliasTestsHelpers.hpp @@ -233,7 +233,7 @@ void TestGetConsoleAliasHelper(TCH* ptszSourceGiven, if (0 == dwExpectedLastError) { - // If it was successful, it should have been filled. Otherwise it will be zeroed as when it started. + // If it was successful, it should have been filled. Otherwise, it will be zeroed as when it started. StringCbCopyT(ptchExpectedTarget, cbTargetBuffer, ptszExpectedTargetGiven); } diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 9107413e7e..5fc568d456 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -84,7 +84,7 @@ void ConhostInternalGetSet::SetViewportPosition(const til::point position) THROW_IF_FAILED(info.SetViewportOrigin(true, position, true)); // SetViewportOrigin() only updates the virtual bottom (the bottom coordinate of the area // in the text buffer a VT client writes its output into) when it's moving downwards. - // But this function is meant to truly move the viewport no matter what. Otherwise `tput reset` breaks. + // But this function is meant to truly move the viewport no matter what. Otherwise, `tput reset` breaks. info.UpdateBottom(); } diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index ea086b1230..c7d82fae0b 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -184,7 +184,7 @@ COOKED_READ_DATA::COOKED_READ_DATA(_In_ InputBuffer* const pInputBuffer, // - It may be called more than once. // Arguments: // - TerminationReason - if this routine is called because a ctrl-c or ctrl-break was seen, this argument -// contains CtrlC or CtrlBreak. If the owning thread is exiting, it will have ThreadDying. Otherwise 0. +// contains CtrlC or CtrlBreak. If the owning thread is exiting, it will have ThreadDying. Otherwise, 0. // - fIsUnicode - Whether to convert the final data to A (using Console Input CP) at the end or treat everything as Unicode (UCS-2) // - pReplyStatus - The status code to return to the client application that originally called the API (before it was queued to wait) // - pNumBytes - The number of bytes of data that the server/driver will need to transmit back to the client process diff --git a/src/host/readDataDirect.cpp b/src/host/readDataDirect.cpp index 5009817763..ef36557093 100644 --- a/src/host/readDataDirect.cpp +++ b/src/host/readDataDirect.cpp @@ -36,7 +36,7 @@ DirectReadData::DirectReadData(_In_ InputBuffer* const pInputBuffer, // Arguments: // - TerminationReason - if this routine is called because a ctrl-c or // ctrl-break was seen, this argument contains CtrlC or CtrlBreak. If -// the owning thread is exiting, it will have ThreadDying. Otherwise 0. +// the owning thread is exiting, it will have ThreadDying. Otherwise, 0. // - fIsUnicode - Should we return UCS-2 unicode data, or should we // run the final data through the current Input Codepage before // returning? diff --git a/src/host/readDataRaw.cpp b/src/host/readDataRaw.cpp index 9351382c07..8c83143244 100644 --- a/src/host/readDataRaw.cpp +++ b/src/host/readDataRaw.cpp @@ -48,7 +48,7 @@ RAW_READ_DATA::~RAW_READ_DATA() = default; // Arguments: // - TerminationReason - if this routine is called because a ctrl-c or // ctrl-break was seen, this argument contains CtrlC or CtrlBreak. If -// the owning thread is exiting, it will have ThreadDying. Otherwise 0. +// the owning thread is exiting, it will have ThreadDying. Otherwise, 0. // - fIsUnicode - Whether to convert the final data to A (using // Console Input CP) at the end or treat everything as Unicode (UCS-2) // - pReplyStatus - The status code to return to the client diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 3e1ae49d4b..3fe3b7b859 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1221,7 +1221,7 @@ void SCREEN_INFORMATION::_AdjustViewportSize(const til::rect* const prcClientNew const til::size* const pcoordSize) { // If the left is the only one that changed (and not the right - // also), then adjust from the left. Otherwise if the right + // also), then adjust from the left. Otherwise, if the right // changes or both changed, bias toward leaving the top-left // corner in place and resize from the bottom right. // -- @@ -2407,7 +2407,7 @@ const FontInfo& SCREEN_INFORMATION::GetCurrentFont() const noexcept // Method Description: // - Gets the desired font of the screen buffer. If we try loading this font and -// have to fallback to another, then GetCurrentFont()!=GetDesiredFont(). +// have to fall back to another, then GetCurrentFont()!=GetDesiredFont(). // We store this separately, so that if we need to reload the font, we can // try again with our preferred font info (in the desired font info) instead // of re-using the looked up value from before. diff --git a/src/host/selection.cpp b/src/host/selection.cpp index d7dcf21ebe..5bc077e9ab 100644 --- a/src/host/selection.cpp +++ b/src/host/selection.cpp @@ -415,7 +415,7 @@ void Selection::ClearSelection(const bool fStartingNewSelection) // If we were using alternate selection, cancel it here before starting a new area. d->fUseAlternateSelection = false; - // Only unblock if we're not immediately starting a new selection. Otherwise stay blocked. + // Only unblock if we're not immediately starting a new selection. Otherwise, stay blocked. if (!fStartingNewSelection) { UnblockWriteConsole(CONSOLE_SELECTING); diff --git a/src/host/ut_host/ConsoleArgumentsTests.cpp b/src/host/ut_host/ConsoleArgumentsTests.cpp index a9148dfd2e..b03b9099db 100644 --- a/src/host/ut_host/ConsoleArgumentsTests.cpp +++ b/src/host/ut_host/ConsoleArgumentsTests.cpp @@ -812,7 +812,7 @@ void ConsoleArgumentsTests::InitialSizeTests() true); // successful parse? commandline = L"conhost.exe --width foo"; - ArgTestsRunner(L"#6 look for an ivalid commandline passing a string", + ArgTestsRunner(L"#6 look for an invalid commandline passing a string", commandline, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, @@ -833,7 +833,7 @@ void ConsoleArgumentsTests::InitialSizeTests() false); // successful parse? commandline = L"conhost.exe --width 2foo"; - ArgTestsRunner(L"#7 look for an ivalid commandline passing a string with a number at the start", + ArgTestsRunner(L"#7 look for an invalid commandline passing a string with a number at the start", commandline, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, @@ -854,7 +854,7 @@ void ConsoleArgumentsTests::InitialSizeTests() false); // successful parse? commandline = L"conhost.exe --width 65535"; - ArgTestsRunner(L"#7 look for an ivalid commandline passing a value that's too big", + ArgTestsRunner(L"#7 look for an invalid commandline passing a value that's too big", commandline, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 77b0f303cf..943d3199db 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -5982,7 +5982,7 @@ void ScreenBufferTests::ClearAlternateBuffer() auto useMain = wil::scope_exit([&] { altBuffer.UseMainScreenBuffer(); }); - // Set the position to home, otherwise it's inherited from the main buffer. + // Set the position to home; otherwise, it's inherited from the main buffer. VERIFY_SUCCEEDED(altBuffer.SetCursorPosition({ 0, 0 }, true)); WriteText(altBuffer.GetTextBuffer()); diff --git a/src/host/utils.cpp b/src/host/utils.cpp index d5ac1fc90d..292450ca5b 100644 --- a/src/host/utils.cpp +++ b/src/host/utils.cpp @@ -124,7 +124,7 @@ UINT s_LoadStringEx(_In_ HINSTANCE hModule, _In_ UINT wID, _Out_writes_(cchBuffe lpsz += cch; // Step to start if next string } - // chhBufferMax == 0 means return a pointer to the read-only resource buffer. + // cchBufferMax == 0 means return a pointer to the read-only resource buffer. if (cchBufferMax == 0) { *(LPTSTR*)lpBuffer = lpsz; diff --git a/src/host/writeData.cpp b/src/host/writeData.cpp index 6250155461..51d017bfc0 100644 --- a/src/host/writeData.cpp +++ b/src/host/writeData.cpp @@ -79,7 +79,7 @@ void WriteData::SetUtf8ConsumedCharacters(const size_t cchUtf8Consumed) // - Called back at a later time to resume the writing operation when the output object becomes unblocked. // Arguments: // - TerminationReason - if this routine is called because a ctrl-c or ctrl-break was seen, this argument -// contains CtrlC or CtrlBreak. If the owning thread is exiting, it will have ThreadDying. Otherwise 0. +// contains CtrlC or CtrlBreak. If the owning thread is exiting, it will have ThreadDying. Otherwise, 0. // - fIsUnicode - Input data was in UCS-2 unicode or it needs to be converted with the current Output Codepage // - pReplyStatus - The status code to return to the client application that originally called the API (before it was queued to wait) // - pNumBytes - The number of bytes of data that the server/driver will need to transmit back to the client process diff --git a/src/inc/til/io.h b/src/inc/til/io.h index 9e28b99630..bfc9c3bce3 100644 --- a/src/inc/til/io.h +++ b/src/inc/til/io.h @@ -23,7 +23,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" // Arguments: // - handle: a HANDLE to the file to check // Return Value: - // - true if it had the expected permissions. False otherwise. + // - true if it had the expected permissions; otherwise, false. _TIL_INLINEPREFIX bool isOwnedByAdministrators(const HANDLE& handle) { // If the file is owned by the administrators group, trust the diff --git a/src/inc/til/small_vector.h b/src/inc/til/small_vector.h index 40bc012ff1..027f0484b5 100644 --- a/src/inc/til/small_vector.h +++ b/src/inc/til/small_vector.h @@ -871,7 +871,7 @@ namespace til // An optimization for the most common vector type which is trivially and copyable and noexcept constructible. // Compared to the complex form below, we don't need the 2 moves and 1 destroy, because is_trivially_copyable_v implies - // that we can just memmove() the items in one fell swoop. We don't need a try/catch either because func() is noexcept. + // that we can just memmove() the items all at once. We don't need a try/catch either because func() is noexcept. if constexpr (noexcept(func(begin())) && std::is_trivially_copyable_v) { _size = new_size; diff --git a/src/interactivity/base/InteractivityFactory.cpp b/src/interactivity/base/InteractivityFactory.cpp index 8b5647bac6..ecc5c2bc18 100644 --- a/src/interactivity/base/InteractivityFactory.cpp +++ b/src/interactivity/base/InteractivityFactory.cpp @@ -293,7 +293,7 @@ using namespace Microsoft::Console::Interactivity; // - hwnd: Receives the value of the newly created window's HWND. // - owner: the HWND that should be the initial owner of the pseudo window. // Return Value: -// - STATUS_SUCCESS on success, otherwise an appropriate error. +// - STATUS_SUCCESS on success; otherwise, an appropriate error. [[nodiscard]] NTSTATUS InteractivityFactory::CreatePseudoWindow(HWND& hwnd) { hwnd = nullptr; diff --git a/src/interactivity/win32/WindowMetrics.cpp b/src/interactivity/win32/WindowMetrics.cpp index 637a08feba..f809ebc48d 100644 --- a/src/interactivity/win32/WindowMetrics.cpp +++ b/src/interactivity/win32/WindowMetrics.cpp @@ -75,7 +75,7 @@ til::rect WindowMetrics::GetMaxWindowRectInPixels() // - Gets the maximum possible window rectangle in pixels. Based on the monitor the window is on or the primary monitor if no window exists yet. // Arguments: // - prcSuggested - If we were given a suggested rectangle for where the window is going, we can pass it in here to find out the max size on that monitor. -// - If this value is zero and we had a valid window handle, we'll use that instead. Otherwise the value of 0 will make us use the primary monitor. +// - If this value is zero and we had a valid window handle, we'll use that instead. Otherwise, the value of 0 will make us use the primary monitor. // - pDpiSuggested - The dpi that matches the suggested rect. We will attempt to compute this during the function, but if we fail for some reason, // - the original value passed in will be left untouched. // Return Value: diff --git a/src/propsheet/preview.cpp b/src/propsheet/preview.cpp index 64ed9fae40..9becd7b703 100644 --- a/src/propsheet/preview.cpp +++ b/src/propsheet/preview.cpp @@ -422,7 +422,7 @@ VOID PreviewPaint( * return = n1 * m / n2 * This can be used to make an aspect ration calculation where n1/n2 * is the aspect ratio and m is a known value. The return value will - * be the value that corresponds to m with the correct apsect ratio. + * be the value that corresponds to m with the correct aspect ratio. */ LONG AspectScale( diff --git a/src/propslib/DelegationConfig.cpp b/src/propslib/DelegationConfig.cpp index 29339baa2e..2ba4d85ed5 100644 --- a/src/propslib/DelegationConfig.cpp +++ b/src/propslib/DelegationConfig.cpp @@ -293,7 +293,7 @@ try RETURN_IF_NTSTATUS_FAILED(RegistrySerialization::s_OpenConsoleKey(¤tUserKey, &consoleKey)); - // Create method for registry is a "create if not exists, otherwise open" function. + // Create method for registry is a "create if not exists; otherwise, open" function. wil::unique_hkey startupKey; RETURN_IF_NTSTATUS_FAILED(RegistrySerialization::s_CreateKey(consoleKey.get(), L"%%Startup", &startupKey)); diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index d2e9f8bf26..6bebd719e8 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -268,7 +268,7 @@ try } // PaintCursor() is only called when the cursor is visible, but we need to invalidate the cursor area - // even if it isn't. Otherwise a transition from a visible to an invisible cursor wouldn't be rendered. + // even if it isn't. Otherwise, a transition from a visible to an invisible cursor wouldn't be rendered. if (const auto r = _api.invalidatedCursorArea; r.non_empty()) { _p.dirtyRectInPx.left = std::min(_p.dirtyRectInPx.left, r.left * _p.s->font->cellSize.x); diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 2acb128591..60ee6aaf1d 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -2324,7 +2324,7 @@ void BackendD3D::_executeCustomShader(RenderingPayload& p) { // Before we do anything else we have to unbound _renderTargetView from being - // a render target, otherwise we can't use it as a shader resource below. + // a render target; otherwise, we can't use it as a shader resource below. p.deviceContext->OMSetRenderTargets(1, _renderTargetView.addressof(), nullptr); // IA: Input Assembler diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index 3f70ae6748..6668e2ce3c 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -265,7 +265,7 @@ namespace Microsoft::Console::Render::Atlas private: // These two functions don't need to use scoped objects or standard allocators, - // since this class is in fact an scoped allocator object itself. + // since this class is in fact a scoped allocator object itself. #pragma warning(push) #pragma warning(disable : 26402) // Return a scoped object instead of a heap-allocated if it has a move constructor (r.3). #pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique instead (r.11). diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index c923ef8e3f..9567b8cd49 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -1116,7 +1116,7 @@ bool Renderer::_isInHoveredInterval(const til::point coordTarget) const noexcept // Arguments: // - // Return Value: -// - nullopt if the cursor is off or out-of-frame, otherwise a CursorOptions +// - nullopt if the cursor is off or out-of-frame; otherwise, a CursorOptions void Renderer::_updateCursorInfo() { // Get cursor position in buffer diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index d8f98ef50f..bf583f7850 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -560,7 +560,7 @@ GdiEngine::~GdiEngine() // Arguments: // - newTitle: the new string to use for the title of the window // Return Value: -// - S_OK if PostMessageW succeeded, otherwise E_FAIL +// - S_OK if PostMessageW succeeded; otherwise, E_FAIL [[nodiscard]] HRESULT GdiEngine::_DoUpdateTitle(_In_ const std::wstring_view /*newTitle*/) noexcept { // the CM_UPDATE_TITLE handler in windowproc will query the updated title. diff --git a/src/server/IoDispatchers.cpp b/src/server/IoDispatchers.cpp index da08215756..0e5cf60da8 100644 --- a/src/server/IoDispatchers.cpp +++ b/src/server/IoDispatchers.cpp @@ -486,7 +486,7 @@ PCONSOLE_API_MSG IoDispatchers::ConsoleHandleConnectionRequest(_In_ PCONSOLE_API return pReceiveMsg; } - // For future code archeologists: GH#2988 + // For future code archaeologists: GH#2988 // // Here, the console calls ConsoleControl(ConsoleSetForeground,...) with a // flag depending on if the console is focused or not. This is surprisingly diff --git a/src/terminal/adapter/FontBuffer.cpp b/src/terminal/adapter/FontBuffer.cpp index dabee138f7..bd0a2e2c94 100644 --- a/src/terminal/adapter/FontBuffer.cpp +++ b/src/terminal/adapter/FontBuffer.cpp @@ -259,7 +259,7 @@ void FontBuffer::_prepareCharacterBuffer() { // If any of the attributes have changed since the last time characters // were downloaded, the font dimensions will need to be recalculated, and - // the buffer will need to be cleared. Otherwise we'll just be adding to + // the buffer will need to be cleared. Otherwise, we'll just be adding to // the existing font, assuming the current dimensions. if (_cellMatrix != _pendingCellMatrix || _cellHeight != _pendingCellHeight || @@ -316,7 +316,7 @@ void FontBuffer::_addSixelValue(const VTInt value) noexcept { if (_currentChar < MAX_CHARS && _sixelColumn < _textWidth) { - // Each sixel updates six pixels of a single column, so we setup a bit + // Each sixel updates six pixels of a single column, so we set up a bit // mask for the column we want to update, and then set that bit in each // row for which there is a corresponding "on" bit in the input value. const auto outputColumnBit = (0x8000 >> (_sixelColumn + _textOffset)); @@ -397,7 +397,7 @@ std::tuple FontBuffer::_calculateDimensions() const } // Now we're going to test whether the dimensions are in range for a number - // of known terminals. We use the declared dimensions if given, otherwise + // of known terminals. We use the declared dimensions if given; otherwise, // estimate the size from the used sixel values. If comparing a sixel-based // height, though, we need to round up the target cell height to account for // the fact that our used height will always be a multiple of six. diff --git a/src/terminal/adapter/MacroBuffer.hpp b/src/terminal/adapter/MacroBuffer.hpp index 92c1c07c73..86e5dfed88 100644 --- a/src/terminal/adapter/MacroBuffer.hpp +++ b/src/terminal/adapter/MacroBuffer.hpp @@ -30,7 +30,7 @@ namespace Microsoft::Console::VirtualTerminal public: // The original DEC terminals only supported 6K of memory, which is // probably a bit low for modern usage. But we also don't want to make - // this value too large, otherwise it could be used in a denial-of- + // this value too large; otherwise, it could be used in a denial-of- // service attack. So for now this is probably a sufficient limit, but // we may need to increase it in the future if we intend to support // macros containing sixel sequences. diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index cb8e4feb5a..23e12f979f 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1009,7 +1009,7 @@ void AdaptDispatch::_ChangeRectOrStreamAttributes(const til::rect& changeArea, c // top line is altered from the left offset up to the end of the line. The // bottom line is altered from the start up to the right offset. All the // lines in-between have their entire length altered. The right coordinate - // must be greater than the left, otherwise the operation is ignored. + // must be greater than the left; otherwise, the operation is ignored. else if (lineCount > 1 && changeRect.right > changeRect.left) { const auto pageWidth = page.Width(); @@ -1322,7 +1322,7 @@ void AdaptDispatch::RequestChecksumRectangularArea(const VTInt id, const VTInt p // As part of the checksum, we need to include the color indices of each // cell, and in the case of default colors, those indices come from the // color alias table. But if they're not in the bottom 16 range, we just - // fallback to using white on black (7 and 0). + // fall back to using white on black (7 and 0). auto defaultFgIndex = _renderSettings.GetColorAliasIndex(ColorAlias::DefaultForeground); auto defaultBgIndex = _renderSettings.GetColorAliasIndex(ColorAlias::DefaultBackground); defaultFgIndex = defaultFgIndex < 16 ? defaultFgIndex : 7; @@ -2392,7 +2392,9 @@ void AdaptDispatch::CarriageReturn() // Arguments: // - page - Target page on which the line feed is executed. // - withReturn - Set to true if a carriage return should be performed as well. -// - wrapForced - Set to true is the line feed was the result of the line wrapping. if the viewport panned down. False if not. +// - wrapForced - Set to true if the line feed was the result of the line wrapping. +// Return Value: +// - true if the viewport panned down; otherwise, false. bool AdaptDispatch::_DoLineFeed(const Page& page, const bool withReturn, const bool wrapForced) { auto& textBuffer = page.Buffer(); @@ -2468,7 +2470,7 @@ bool AdaptDispatch::_DoLineFeed(const Page& page, const bool withReturn, const b _api.NotifyBufferRotation(1); // We trigger a scroll rather than a redraw, since that's more efficient, - // but we need to turn the cursor off before doing so, otherwise a ghost + // but we need to turn the cursor off before doing so; otherwise, a ghost // cursor can be left behind in the previous position. cursor.SetIsOn(false); textBuffer.TriggerScroll({ 0, -1 }); @@ -2882,7 +2884,7 @@ void AdaptDispatch::AcceptC1Controls(const bool enabled) void AdaptDispatch::SendC1Controls(const bool enabled) { // If this is an attempt to enable C1 controls, the input code page must be - // one of the DOCS choices (UTF-8 or ISO-8859-1), otherwise there's a risk + // one of the DOCS choices (UTF-8 or ISO-8859-1); otherwise, there's a risk // that those controls won't have a valid encoding. const auto codepage = _api.GetInputCodePage(); if (enabled == false || codepage == CP_UTF8 || codepage == 28591) @@ -3354,7 +3356,7 @@ void AdaptDispatch::SetXtermColorResource(const size_t resource, const DWORD col // Method Description: // - Reports the value of one Xterm Color Resource, if it is set. // Return Value: -// True if handled successfully. False otherwise. +// - true if handled successfully; otherwise, false. void AdaptDispatch::RequestXtermColorResource(const size_t resource) { assert(resource >= 10); @@ -4187,7 +4189,7 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting() { // Although we don't yet support any operations with parameter // prefixes, it's important that we still parse the prefix and - // include it in the ID. Otherwise we'll mistakenly respond to + // include it in the ID. Otherwise, we'll mistakenly respond to // prefixed queries that we don't actually recognise. const auto isParameterPrefix = ch >= L'<' && ch <= L'?'; const auto isParameter = ch >= L'0' && ch < L'9'; diff --git a/src/terminal/adapter/ut_adapter/inputTest.cpp b/src/terminal/adapter/ut_adapter/inputTest.cpp index ab893be772..d672004cd1 100644 --- a/src/terminal/adapter/ut_adapter/inputTest.cpp +++ b/src/terminal/adapter/ut_adapter/inputTest.cpp @@ -78,7 +78,7 @@ public: irTest.Event.KeyEvent.bKeyDown = TRUE; // If we want to test a key with the Right Alt modifier, we must generate - // an event for the Alt key first, otherwise the modifier will be dropped. + // an event for the Alt key first; otherwise, the modifier will be dropped. if (WI_IsFlagSet(uiKeystate, RIGHT_ALT_PRESSED)) { irTest.Event.KeyEvent.wVirtualKeyCode = VK_MENU; diff --git a/src/terminal/input/mouseInput.cpp b/src/terminal/input/mouseInput.cpp index c36ce160b4..68ef6ed601 100644 --- a/src/terminal/input/mouseInput.cpp +++ b/src/terminal/input/mouseInput.cpp @@ -269,7 +269,7 @@ static constexpr wchar_t _encodeDefaultCoordinate(const til::CoordType sCoordina // Parameters: // - // Return value: -// - true, if we are tracking mouse input. False, otherwise +// - true, if we are tracking mouse input; otherwise, false. bool TerminalInput::IsTrackingMouseInput() const noexcept { return _inputMode.any(Mode::DefaultMouseTracking, Mode::ButtonEventMouseTracking, Mode::AnyEventMouseTracking); diff --git a/src/terminal/input/terminalInput.cpp b/src/terminal/input/terminalInput.cpp index ac0795bdc4..65dfa6f33c 100644 --- a/src/terminal/input/terminalInput.cpp +++ b/src/terminal/input/terminalInput.cpp @@ -171,7 +171,7 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event) if (matchingLastKeyPress && !_inputMode.test(Mode::AutoRepeat)) { // Note that we must return an empty string here to imply that we've handled - // the event, otherwise the key press can still end up being submitted. + // the event; otherwise, the key press can still end up being submitted. return _makeNoOutput(); } _lastVirtualKeyCode = virtualKeyCode; diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 12954512bd..c45e2fc2f4 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -1511,7 +1511,7 @@ void StateMachine::_EventOscString(const wchar_t wch) // - Handle the two-character termination of a OSC sequence. // Events in this state will: // 1. Trigger the OSC action associated with the param on an OscTerminator -// 2. Otherwise treat this as a normal escape character event. +// 2. Otherwise, treat this as a normal escape character event. // Arguments: // - wch - Character that triggered the event // Return Value: diff --git a/src/tools/U8U16Test/U8U16Test.cpp b/src/tools/U8U16Test/U8U16Test.cpp index 89ed982453..5c4d6a2f10 100644 --- a/src/tools/U8U16Test/U8U16Test.cpp +++ b/src/tools/U8U16Test/U8U16Test.cpp @@ -57,7 +57,7 @@ u8state::u8state() noexcept : { // If the Lead Byte indicates that the last bytes in the string is a partial UTF-8 code point then cache them: // Use the bitmask at index `sequenceLen`. Compare the result with the operand having the same index. If they - // are not equal then the sequence has to be cached because it is a partial code point. Otherwise the + // are not equal then the sequence has to be cached because it is a partial code point. Otherwise, the // sequence is a complete UTF-8 code point and the whole string is ready for the conversion to hstring. if ((*backIter & _cmpMasks.at(sequenceLen)) != _cmpOperands.at(sequenceLen)) { diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 9b3d7040a5..d91c57a08d 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -568,7 +568,7 @@ try // - the anchors have been populated // This means that we've found a contiguous range where the text attribute was found. // No point in searching through the rest of the search space. - // TLDR: keep updating the second anchor and make the range wider until the attribute changes. + // TL;DR: keep updating the second anchor and make the range wider until the attribute changes. break; } } @@ -663,7 +663,7 @@ CATCH_RETURN(); // - pRetVal - the attributeId's sub-type for the first cell in the range (i.e. foreground color) // - attr - the text attribute we're checking // Return Value: -// - true, if the attributeId is supported. false, otherwise. +// - true, if the attributeId is supported. Otherwise, false. // - pRetVal is populated with the appropriate response relevant to the returned bool. bool UiaTextRangeBase::_initializeAttrQuery(TEXTATTRIBUTEID attributeId, VARIANT* pRetVal, const TextAttribute& attr) const { @@ -1310,7 +1310,7 @@ til::CoordType UiaTextRangeBase::_getViewportHeight(const til::inclusive_rect& v { assert(viewport.bottom >= viewport.top); // + 1 because til::inclusive_rect is inclusive on both sides so subtracting top - // and bottom gets rid of 1 more then it should. + // and bottom gets rid of 1 more, then it should. return viewport.bottom - viewport.top + 1; } diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index 79c97ddaa2..144ea987a8 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -417,7 +417,7 @@ static void _ClosePseudoConsole(_In_ PseudoConsole* pPty) noexcept // INHERIT_CURSOR: This will cause the created conpty to attempt to inherit the // cursor position of the parent terminal application. This can be useful // for applications like `ssh`, where ssh (currently running in a terminal) -// might want to create a pseudoterminal session for an child application +// might want to create a pseudoterminal session for a child application // and the child inherit the cursor position of ssh. // The created conpty will immediately emit a "Device Status Request" VT // sequence to hOutput, that should be replied to on hInput in the format diff --git a/tools/GenerateAppxFromManifest.ps1 b/tools/GenerateAppxFromManifest.ps1 index 3bf0bba826..a52c1cfc15 100644 --- a/tools/GenerateAppxFromManifest.ps1 +++ b/tools/GenerateAppxFromManifest.ps1 @@ -21,7 +21,7 @@ param ( [xml]$appxPrototypeData = Get-Content $AppxManifestPrototype # You need to make sure each element we add is part of the same namespace as the -# Package, otherwise powershell will append a bunch of `xmlns=""` properties +# Package; otherwise, powershell will append a bunch of `xmlns=""` properties # that will make the appx deployment reject the manifest. $rootNS = $appxPrototypeData.Package.NamespaceURI @@ -38,7 +38,7 @@ $files | ForEach-Object { $InProcessServer = $appxPrototypeData.CreateNode("element", "InProcessServer", $rootNS) $Path = $appxPrototypeData.CreateNode("element", "Path", $rootNS) - # You need to stash the result here, otherwise a blank line will be echoed to + # You need to stash the result here; otherwise, a blank line will be echoed to # the console. $placeholder = $Path.InnerText = $_.name diff --git a/tools/README.md b/tools/README.md index 94e841a0b1..f69efb3a21 100644 --- a/tools/README.md +++ b/tools/README.md @@ -30,7 +30,7 @@ the `%DEFAULT_CONFIGURATION%` configuration, which is `Debug` if you use `razzle ## opencon (and openbash, openps) `opencon` can be used to launch the **last built** OpenConsole binary. If given an -argument, it will try and run that program in the launched window. Otherwise it +argument, it will try and run that program in the launched window. Otherwise, it will default to cmd.exe. `openbash` is similar, it immediately launches bash.exe (the Windows Subsystem From 6e1f4a72bed24514943d88d04a368ba9465c7ad7 Mon Sep 17 00:00:00 2001 From: James Pack Date: Tue, 24 Jun 2025 16:56:49 -0400 Subject: [PATCH 25/74] Add arm64 build support to the build script (#18946) It built successfully following the documentation on my Snapdragon Surface Laptop :) --- tools/OpenConsole.psm1 | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/OpenConsole.psm1 b/tools/OpenConsole.psm1 index 05fc5af4c2..c11d46570f 100644 --- a/tools/OpenConsole.psm1 +++ b/tools/OpenConsole.psm1 @@ -83,6 +83,7 @@ function Set-MsbuildDevEnvironment switch ($env:PROCESSOR_ARCHITECTURE) { "amd64" { $arch = "x64" } "x86" { $arch = "x86" } + "arm64" { $arch = "arm64" } default { throw "Unknown architecture: $switch" } } From cf95460a2622f33e48303aeb2b9cc03a9ee324e7 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 24 Jun 2025 16:33:27 -0500 Subject: [PATCH 26/74] Try to get the client name during DefTerm handoff (#19014) --- src/cascadia/TerminalConnection/ConptyConnection.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 613ad50c91..5778c0b6ed 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -313,6 +313,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } CATCH_LOG() + try + { + auto processImageName{ wil::QueryFullProcessImageNameW(_piClient.hProcess) }; + _clientName = std::filesystem::path{ std::move(processImageName) }.filename().wstring(); + } + CATCH_LOG() + _pipe = std::move(pipe.server); *in = pipe.client.release(); *out = pipeClientClone.release(); From 6bf315a4c904fbc01187e155a55117dfd7e7f893 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 24 Jun 2025 14:58:33 -0700 Subject: [PATCH 27/74] Fix crash when closing multiple panes simultaneously (#19023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes a crash when multiple panes were closed simultaneously (i.e. using broadcast input). The root cause of this crash was that we would get a null pointer exception when trying to access a member/function off of a null `_content`. This is because `Pane::_CloseChild()` would always pass over the content from the non-closed pane and attempt to hook everything up to it. The fix was to operate similarly to `Pane::Close()` and raise a `Closed` event and return early. Since there's no alternative content to attach to, might as well just close the entire pane. This propagates up the stack of listeners to update the UI appropriately and close the parent pane and eventually the entire tab, if necessary.   Closes #18071 Closes #17432 ## Validation Steps Performed 1. Open 2 panes 2. Use broadcast input to send "exit" to both panes 3. ✅ Terminal doesn't crash and the tab closes gracefully --- src/cascadia/TerminalApp/Pane.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 30ca4714eb..d8c7cd47d8 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -1414,6 +1414,13 @@ void Pane::_CloseChild(const bool closeFirst) // take the control, profile, id and isDefTermSession of the pane that _wasn't_ closed. _setPaneContent(remainingChild->_takePaneContent()); + if (!_content) + { + // GH#18071: our content is still null after taking the other pane's content, + // so just notify our parent that we're closed. + Closed.raise(nullptr, nullptr); + return; + } _id = remainingChild->Id(); // Revoke the old event handlers. Remove both the handlers for the panes From fc0a06c3b6cae2ede1326eef202a800244365ebd Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 25 Jun 2025 18:31:28 +0200 Subject: [PATCH 28/74] Preserve the cursor row during Clear Buffer (#18976) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces an ABI change to the ConptyClearPseudoConsole signal. Otherwise, we have to make it so that the API call always retains the row the cursor is on, but I feel like that makes it worse. Closes #18732 Closes #18878 ## Validation Steps Performed * Launch `ConsoleMonitor.exe` * Create some text above & below the cursor in PowerShell * Clear Buffer * Buffer is cleared except for the cursor row ✅ * ...same in ConPTY ✅ --- .../TerminalConnection/ConptyConnection.cpp | 4 +- .../TerminalConnection/ConptyConnection.h | 2 +- .../TerminalConnection/ConptyConnection.idl | 2 +- src/cascadia/TerminalControl/ControlCore.cpp | 52 +++++++++++++------ .../UnitTests_Control/ControlCoreTests.cpp | 10 ++-- src/host/PtySignalInputThread.cpp | 17 ++++-- src/host/PtySignalInputThread.hpp | 7 ++- src/inc/conpty-static.h | 2 +- src/winconpty/winconpty.cpp | 9 ++-- src/winconpty/winconpty.h | 1 - 10 files changed, 69 insertions(+), 37 deletions(-) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 5778c0b6ed..6f15a8c1ed 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -563,13 +563,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } } - void ConptyConnection::ClearBuffer() + void ConptyConnection::ClearBuffer(bool keepCursorRow) { // If we haven't connected yet, then we really don't need to do // anything. The connection should already start clear! if (_isConnected()) { - THROW_IF_FAILED(ConptyClearPseudoConsole(_hPC.get())); + THROW_IF_FAILED(ConptyClearPseudoConsole(_hPC.get(), keepCursorRow)); } } diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index 69edd7b7a8..4d847fdcd1 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -25,7 +25,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void Resize(uint32_t rows, uint32_t columns); void ResetSize(); void Close() noexcept; - void ClearBuffer(); + void ClearBuffer(bool keepCursorRow); void ShowHide(const bool show); diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl index e1ed83cc0c..5264238c79 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.idl +++ b/src/cascadia/TerminalConnection/ConptyConnection.idl @@ -15,7 +15,7 @@ namespace Microsoft.Terminal.TerminalConnection UInt16 ShowWindow { get; }; void ResetSize(); - void ClearBuffer(); + void ClearBuffer(Boolean keepCursorRow); void ShowHide(Boolean show); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index b2ad7b7549..6a4147637d 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -2264,23 +2264,42 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void ControlCore::ClearBuffer(Control::ClearBufferType clearType) { - std::wstring_view command; - switch (clearType) - { - case ClearBufferType::Screen: - command = L"\x1b[H\x1b[2J"; - break; - case ClearBufferType::Scrollback: - command = L"\x1b[3J"; - break; - case ClearBufferType::All: - command = L"\x1b[H\x1b[2J\x1b[3J"; - break; - } - { const auto lock = _terminal->LockForWriting(); - _terminal->Write(command); + // In absolute buffer coordinates, including the scrollback (= Y is offset by the scrollback height). + const auto viewport = _terminal->GetViewport(); + // The absolute cursor coordinate. + const auto cursor = _terminal->GetViewportRelativeCursorPosition(); + + // GH#18732: Users want the row the cursor is on to be preserved across clears. + std::wstring sequence; + + if (clearType == ClearBufferType::Scrollback || clearType == ClearBufferType::All) + { + sequence.append(L"\x1b[3J"); + } + + if (clearType == ClearBufferType::Screen || clearType == ClearBufferType::All) + { + // Erase any viewport contents below (but not including) the cursor row. + if (viewport.Height() - cursor.y > 1) + { + fmt::format_to(std::back_inserter(sequence), FMT_COMPILE(L"\x1b[{};1H\x1b[J"), cursor.y + 2); + } + + // Erase any viewport contents above (but not including) the cursor row. + if (cursor.y > 0) + { + // An SU sequence would be simpler than this DL sequence, + // but SU isn't well standardized between terminals. + // Generally speaking, it's best avoiding it. + fmt::format_to(std::back_inserter(sequence), FMT_COMPILE(L"\x1b[H\x1b[{}M"), cursor.y); + } + + fmt::format_to(std::back_inserter(sequence), FMT_COMPILE(L"\x1b[1;{}H"), cursor.x + 1); + } + + _terminal->Write(sequence); } if (clearType == Control::ClearBufferType::Screen || clearType == Control::ClearBufferType::All) @@ -2289,8 +2308,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { // Since the clearing of ConPTY occurs asynchronously, this call can result weird issues, // where a console application still sees contents that we've already deleted, etc. - // The correct way would be for ConPTY to emit the appropriate CSI n J sequences. - conpty.ClearBuffer(); + conpty.ClearBuffer(true); } } } diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp index 999809cb39..69b00b69db 100644 --- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp @@ -248,7 +248,7 @@ namespace ControlUnitTests _standardInit(core); Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' " - L"(leaving the cursor afer 'Bar')"); + L"(leaving the cursor after 'Bar')"); for (auto i = 0; i < 40; ++i) { conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n")); @@ -285,7 +285,7 @@ namespace ControlUnitTests _standardInit(core); Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' " - L"(leaving the cursor afer 'Bar')"); + L"(leaving the cursor after 'Bar')"); for (auto i = 0; i < 40; ++i) { conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n")); @@ -304,9 +304,9 @@ namespace ControlUnitTests Log::Comment(L"Check the buffer after the clear"); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); - VERIFY_ARE_EQUAL(41, core->ScrollOffset()); + VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(20, core->ViewHeight()); - VERIFY_ARE_EQUAL(61, core->BufferHeight()); + VERIFY_ARE_EQUAL(41, core->BufferHeight()); // In this test, we can't actually check if we cleared the buffer // contents. ConPTY will handle the actual clearing of the buffer @@ -322,7 +322,7 @@ namespace ControlUnitTests _standardInit(core); Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' " - L"(leaving the cursor afer 'Bar')"); + L"(leaving the cursor after 'Bar')"); for (auto i = 0; i < 40; ++i) { conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n")); diff --git a/src/host/PtySignalInputThread.cpp b/src/host/PtySignalInputThread.cpp index 2829ebdbcc..0b03612d9a 100644 --- a/src/host/PtySignalInputThread.cpp +++ b/src/host/PtySignalInputThread.cpp @@ -124,7 +124,13 @@ try } case PtySignal::ClearBuffer: { - _DoClearBuffer(); + ClearBufferData msg = { 0 }; + if (!_GetData(&msg, sizeof(msg))) + { + return S_OK; + } + + _DoClearBuffer(msg.keepCursorRow != 0); break; } case PtySignal::ResizeWindow: @@ -180,7 +186,7 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data) _api.ResizeWindow(data.sx, data.sy); } -void PtySignalInputThread::_DoClearBuffer() const +void PtySignalInputThread::_DoClearBuffer(const bool keepCursorRow) const { LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); @@ -196,8 +202,11 @@ void PtySignalInputThread::_DoClearBuffer() const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto& screenInfo = gci.GetActiveOutputBuffer(); - auto& stateMachine = screenInfo.GetStateMachine(); - stateMachine.ProcessString(L"\x1b[H\x1b[2J"); + auto& tb = screenInfo.GetTextBuffer(); + const auto cursor = tb.GetCursor().GetPosition(); + + tb.ClearScrollback(cursor.y, keepCursorRow ? 1 : 0); + tb.GetCursor().SetPosition({ keepCursorRow ? cursor.x : 0, 0 }); } void PtySignalInputThread::_DoShowHide(const ShowHideData& data) diff --git a/src/host/PtySignalInputThread.hpp b/src/host/PtySignalInputThread.hpp index eead316ba9..9ff4fb0c22 100644 --- a/src/host/PtySignalInputThread.hpp +++ b/src/host/PtySignalInputThread.hpp @@ -55,6 +55,11 @@ namespace Microsoft::Console unsigned short show; // used as a bool, but passed as a ushort }; + struct ClearBufferData + { + unsigned short keepCursorRow; + }; + struct SetParentData { uint64_t handle; @@ -64,7 +69,7 @@ namespace Microsoft::Console [[nodiscard]] bool _GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, const DWORD cbBuffer); void _DoResizeWindow(const ResizeWindowData& data); void _DoSetWindowParent(const SetParentData& data); - void _DoClearBuffer() const; + void _DoClearBuffer(bool keepCursorRow) const; void _DoShowHide(const ShowHideData& data); void _Shutdown(); diff --git a/src/inc/conpty-static.h b/src/inc/conpty-static.h index aa31c67fa3..3aca15dba7 100644 --- a/src/inc/conpty-static.h +++ b/src/inc/conpty-static.h @@ -38,7 +38,7 @@ CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsoleAsUser(HANDLE hToken, COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC); CONPTY_EXPORT HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size); -CONPTY_EXPORT HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC); +CONPTY_EXPORT HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC, BOOL keepCursorRow); CONPTY_EXPORT HRESULT WINAPI ConptyShowHidePseudoConsole(HPCON hPC, bool show); CONPTY_EXPORT HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent); CONPTY_EXPORT HRESULT WINAPI ConptyReleasePseudoConsole(HPCON hPC); diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index 144ea987a8..969a8fbdfe 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -278,15 +278,16 @@ HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const CO // Return Value: // - S_OK if the call succeeded, else an appropriate HRESULT for failing to // write the clear message to the pty. -HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty) +static HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty, BOOL keepCursorRow) noexcept { if (pPty == nullptr) { return E_INVALIDARG; } - unsigned short signalPacket[1]; + unsigned short signalPacket[2]; signalPacket[0] = PTY_SIGNAL_CLEAR_WINDOW; + signalPacket[1] = keepCursorRow ? 1 : 0; const auto fSuccess = WriteFile(pPty->hSignal, signalPacket, sizeof(signalPacket), nullptr, nullptr); return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError()); @@ -492,13 +493,13 @@ extern "C" HRESULT WINAPI ConptyResizePseudoConsole(_In_ HPCON hPC, _In_ COORD s // - This is used exclusively by ConPTY to support GH#1193, GH#1882. This allows // a terminal to clear the contents of the ConPTY buffer, which is important // if the user would like to be able to clear the terminal-side buffer. -extern "C" HRESULT WINAPI ConptyClearPseudoConsole(_In_ HPCON hPC) +extern "C" HRESULT WINAPI ConptyClearPseudoConsole(_In_ HPCON hPC, BOOL keepCursorRow) { const PseudoConsole* const pPty = (PseudoConsole*)hPC; auto hr = pPty == nullptr ? E_INVALIDARG : S_OK; if (SUCCEEDED(hr)) { - hr = _ClearPseudoConsole(pPty); + hr = _ClearPseudoConsole(pPty, keepCursorRow); } return hr; } diff --git a/src/winconpty/winconpty.h b/src/winconpty/winconpty.h index 4bb43ddf8f..8a245226ca 100644 --- a/src/winconpty/winconpty.h +++ b/src/winconpty/winconpty.h @@ -68,7 +68,6 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken, _Inout_ PseudoConsole* pPty); HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const COORD size); -HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty); HRESULT _ShowHidePseudoConsole(_In_ const PseudoConsole* const pPty, const bool show); HRESULT _ReparentPseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const HWND newParent); void _ClosePseudoConsoleMembers(_In_ PseudoConsole* pPty); From a25d968fe0026ac5abea62a67d798c89e1280d32 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 1 Jul 2025 21:00:00 +0200 Subject: [PATCH 29/74] Move ConPTY handoff logic into WindowEmperor (#19088) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changes the ConPTY handoff COM server from `REGCLS_SINGLEUSE` to `REGCLS_MULTIPLEUSE`. The former causes a race condition, because handoff runs concurrently with the creation of WinUI windows. This can then result in the a window getting the wrong handoff. It then moves the "root" of ConPTY handoff from `TerminalPage` (WindowEmperor -> AppHost -> TerminalWindow -> TerminalPage) into `WindowEmperor` (WindowEmperor). Closes #19049 ## Validation Steps Performed * Launching cmd from the Start Menu shows a "Command Prompt" tab ✅ * Win+R -> `cmd` creates windows in the foreground ✅ * Win+R -> `cmd /c start /max cmd` creates a fullscreen tab ✅ * This even works for multiple windows, unlike with Canary ✅ * Win+R -> `cmd /c start /min cmd` does not work ❌ * It also doesn't work in Canary, so it's not a bug in this PR ✅ --- .../TerminalApp/AppActionHandlers.cpp | 2 +- .../TerminalApp/AppCommandlineArgs.cpp | 69 +++----- src/cascadia/TerminalApp/AppCommandlineArgs.h | 2 - src/cascadia/TerminalApp/Remoting.cpp | 18 +- src/cascadia/TerminalApp/Remoting.h | 10 +- src/cascadia/TerminalApp/Remoting.idl | 8 +- src/cascadia/TerminalApp/TerminalPage.cpp | 160 ++++-------------- src/cascadia/TerminalApp/TerminalPage.h | 11 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 47 ++--- src/cascadia/TerminalApp/TerminalWindow.h | 1 + .../TerminalConnection/CTerminalHandoff.cpp | 46 ++--- src/cascadia/WindowsTerminal/AppHost.cpp | 5 + src/cascadia/WindowsTerminal/IslandWindow.cpp | 19 ++- .../WindowsTerminal/WindowEmperor.cpp | 56 ++++-- src/cascadia/WindowsTerminal/WindowEmperor.h | 2 +- src/cascadia/WindowsTerminal/pch.h | 3 +- 16 files changed, 166 insertions(+), 293 deletions(-) diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 50bd7cd122..afb3e0a6e5 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -756,7 +756,7 @@ namespace winrt::TerminalApp::implementation if (!actions.empty()) { actionArgs.Handled(true); - ProcessStartupActions(std::move(actions), false); + ProcessStartupActions(std::move(actions)); } } } diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index 1e4b7a3b10..1b7881dc27 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -961,18 +961,6 @@ std::vector& AppCommandlineArgs::GetStartupActions() return _startupActions; } -// Method Description: -// - Returns whether we should start listening for inbound PTY connections -// coming from the operating system default application feature. -// Arguments: -// - -// Return Value: -// - True if the listener should be started. False otherwise. -bool AppCommandlineArgs::IsHandoffListener() const noexcept -{ - return _isHandoffListener; -} - // Method Description: // - Get the string of text that should be displayed to the user on exit. This // is usually helpful for cases where the user entered some sort of invalid @@ -1015,34 +1003,28 @@ bool AppCommandlineArgs::ShouldExitEarly() const noexcept // - void AppCommandlineArgs::ValidateStartupCommands() { - // Only check over the actions list for the potential to add a new-tab - // command if we are not starting for the purposes of receiving an inbound - // handoff connection from the operating system. - if (!_isHandoffListener) + // If we only have a single x-save command, then set our target to the + // current terminal window. This will prevent us from spawning a new + // window just to save the commandline. + if (_startupActions.size() == 1 && + _startupActions.front().Action() == ShortcutAction::SaveSnippet && + _windowTarget.empty()) { - // If we only have a single x-save command, then set our target to the - // current terminal window. This will prevent us from spawning a new - // window just to save the commandline. - if (_startupActions.size() == 1 && - _startupActions.front().Action() == ShortcutAction::SaveSnippet && - _windowTarget.empty()) - { - _windowTarget = "0"; - } - // If we parsed no commands, or the first command we've parsed is not a new - // tab action, prepend a new-tab command to the front of the list. - // (also, we don't need to do this if the only action is a x-save) - else if (_startupActions.empty() || - (_startupActions.front().Action() != ShortcutAction::NewTab && - _startupActions.front().Action() != ShortcutAction::SaveSnippet)) - { - // Build the NewTab action from the values we've parsed on the commandline. - NewTerminalArgs newTerminalArgs{}; - NewTabArgs args{ newTerminalArgs }; - ActionAndArgs newTabAction{ ShortcutAction::NewTab, args }; - // push the arg onto the front - _startupActions.insert(_startupActions.begin(), 1, newTabAction); - } + _windowTarget = "0"; + } + // If we parsed no commands, or the first command we've parsed is not a new + // tab action, prepend a new-tab command to the front of the list. + // (also, we don't need to do this if the only action is a x-save) + else if (_startupActions.empty() || + (_startupActions.front().Action() != ShortcutAction::NewTab && + _startupActions.front().Action() != ShortcutAction::SaveSnippet)) + { + // Build the NewTab action from the values we've parsed on the commandline. + NewTerminalArgs newTerminalArgs{}; + NewTabArgs args{ newTerminalArgs }; + ActionAndArgs newTabAction{ ShortcutAction::NewTab, args }; + // push the arg onto the front + _startupActions.insert(_startupActions.begin(), 1, newTabAction); } } std::optional AppCommandlineArgs::GetPersistedLayoutIdx() const noexcept @@ -1082,13 +1064,9 @@ std::optional AppCommandlineArgs::GetSize() const noexcept // - 0 if the commandline was successfully parsed int AppCommandlineArgs::ParseArgs(winrt::array_view args) { - for (const auto& arg : args) + if (args.size() == 2 && args[1] == L"-Embedding") { - if (arg == L"-Embedding") - { - _isHandoffListener = true; - return 0; - } + return 0; } auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args); @@ -1195,7 +1173,6 @@ void AppCommandlineArgs::FullResetState() _startupActions.clear(); _exitMessage = ""; _shouldExitEarly = false; - _isHandoffListener = false; _windowTarget = {}; } diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.h b/src/cascadia/TerminalApp/AppCommandlineArgs.h index 9f580e957b..0a9de3999c 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.h +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.h @@ -35,7 +35,6 @@ public: void ValidateStartupCommands(); std::vector& GetStartupActions(); - bool IsHandoffListener() const noexcept; const std::string& GetExitMessage() const noexcept; bool ShouldExitEarly() const noexcept; @@ -132,7 +131,6 @@ private: std::optional _launchMode{ std::nullopt }; std::optional _position{ std::nullopt }; std::optional _size{ std::nullopt }; - bool _isHandoffListener{ false }; std::vector _startupActions; std::string _exitMessage; bool _shouldExitEarly{ false }; diff --git a/src/cascadia/TerminalApp/Remoting.cpp b/src/cascadia/TerminalApp/Remoting.cpp index 0c8693ed46..078a0d7f11 100644 --- a/src/cascadia/TerminalApp/Remoting.cpp +++ b/src/cascadia/TerminalApp/Remoting.cpp @@ -15,19 +15,6 @@ using namespace winrt::Windows::Foundation; namespace winrt::TerminalApp::implementation { - CommandlineArgs::CommandlineArgs(winrt::array_view args, winrt::hstring currentDirectory, uint32_t showWindowCommand, winrt::hstring envString) : - _args{ args.begin(), args.end() }, - CurrentDirectory{ std::move(currentDirectory) }, - ShowWindowCommand{ showWindowCommand }, - CurrentEnvironment{ std::move(envString) } - { - _parseResult = _parsed.ParseArgs(_args); - if (_parseResult == 0) - { - _parsed.ValidateStartupCommands(); - } - } - ::TerminalApp::AppCommandlineArgs& CommandlineArgs::ParsedArgs() noexcept { return _parsed; @@ -56,6 +43,11 @@ namespace winrt::TerminalApp::implementation void CommandlineArgs::Commandline(const winrt::array_view& value) { _args = { value.begin(), value.end() }; + _parseResult = _parsed.ParseArgs(_args); + if (_parseResult == 0) + { + _parsed.ValidateStartupCommands(); + } } winrt::com_array CommandlineArgs::Commandline() diff --git a/src/cascadia/TerminalApp/Remoting.h b/src/cascadia/TerminalApp/Remoting.h index 2ad297f31e..9d4a48df76 100644 --- a/src/cascadia/TerminalApp/Remoting.h +++ b/src/cascadia/TerminalApp/Remoting.h @@ -13,21 +13,17 @@ namespace winrt::TerminalApp::implementation { struct CommandlineArgs : public CommandlineArgsT { - CommandlineArgs() = default; - CommandlineArgs(winrt::array_view args, winrt::hstring currentDirectory, uint32_t showWindowCommand, winrt::hstring envString); - ::TerminalApp::AppCommandlineArgs& ParsedArgs() noexcept; winrt::com_array& CommandlineRef() noexcept; // These bits are exposed via WinRT: - public: int32_t ExitCode() const noexcept; winrt::hstring ExitMessage() const; winrt::hstring TargetWindow() const; + til::property Connection; void Commandline(const winrt::array_view& value); winrt::com_array Commandline(); - til::property CurrentDirectory; til::property CurrentEnvironment; til::property ShowWindowCommand{ static_cast(SW_NORMAL) }; // SW_NORMAL is 1, 0 is SW_HIDE @@ -86,11 +82,9 @@ namespace winrt::TerminalApp::implementation WINRT_PROPERTY(uint64_t, Id); WINRT_PROPERTY(winrt::hstring, WindowName); - WINRT_PROPERTY(TerminalApp::CommandlineArgs, Command); + WINRT_PROPERTY(TerminalApp::CommandlineArgs, Command, nullptr); WINRT_PROPERTY(winrt::hstring, Content); WINRT_PROPERTY(Windows::Foundation::IReference, InitialBounds); - - private: }; } diff --git a/src/cascadia/TerminalApp/Remoting.idl b/src/cascadia/TerminalApp/Remoting.idl index 17f1318422..07fde64e50 100644 --- a/src/cascadia/TerminalApp/Remoting.idl +++ b/src/cascadia/TerminalApp/Remoting.idl @@ -6,16 +6,16 @@ namespace TerminalApp runtimeclass CommandlineArgs { CommandlineArgs(); - CommandlineArgs(String[] args, String cwd, UInt32 showWindowCommand, String env); Int32 ExitCode { get; }; String ExitMessage { get; }; String TargetWindow { get; }; + Microsoft.Terminal.TerminalConnection.ITerminalConnection Connection; String[] Commandline; - String CurrentDirectory { get; }; - UInt32 ShowWindowCommand { get; }; - String CurrentEnvironment { get; }; + String CurrentDirectory; + UInt32 ShowWindowCommand; + String CurrentEnvironment; }; enum MonitorBehavior diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index f900e05cb7..1de587c26f 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include "../../types/inc/utils.hpp" @@ -293,12 +292,11 @@ namespace winrt::TerminalApp::implementation // - true if we're not elevated but all relevant pane-spawning actions are elevated bool TerminalPage::ShouldImmediatelyHandoffToElevated(const CascadiaSettings& settings) const { - // GH#12267: Don't forget about defterm handoff here. If we're being - // created for embedding, then _yea_, we don't need to handoff to an - // elevated window. - if (_startupActions.empty() || IsRunningElevated() || _shouldStartInboundListener) + if (_startupActions.empty() || _startupConnection || IsRunningElevated()) { - // there aren't startup actions, or we're elevated. In that case, go for it. + // No point in handing off if we got no startup actions, or we're already elevated. + // Also, we shouldn't need to elevate handoff ConPTY connections. + assert(!_startupConnection); return false; } @@ -488,46 +486,16 @@ namespace winrt::TerminalApp::implementation { _startupState = StartupState::InStartup; - ProcessStartupActions(std::move(_startupActions), true); - - // If we were told that the COM server needs to be started to listen for incoming - // default application connections, start it now. - // This MUST be done after we've registered the event listener for the new connections - // or the COM server might start receiving requests on another thread and dispatch - // them to nowhere. - _StartInboundListener(); - } - } - - // Routine Description: - // - Will start the listener for inbound console handoffs if we have already determined - // that we should do so. - // NOTE: Must be after TerminalPage::_OnNewConnection has been connected up. - // Arguments: - // - - Looks at _shouldStartInboundListener - // Return Value: - // - - May fail fast if setup fails as that would leave us in a weird state. - void TerminalPage::_StartInboundListener() - { - if (_shouldStartInboundListener) - { - _shouldStartInboundListener = false; - - // Hook up inbound connection event handler - _newConnectionRevoker = ConptyConnection::NewConnection(winrt::auto_revoke, { this, &TerminalPage::_OnNewConnection }); - - try + if (_startupConnection) { - winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::StartInboundListener(); + CreateTabFromConnection(std::move(_startupConnection)); } - // If we failed to start the listener, it will throw. - // We don't want to fail fast here because if a peasant has some trouble with - // starting the listener, we don't want it to crash and take all its tabs down - // with it. - catch (...) + else if (!_startupActions.empty()) { - LOG_CAUGHT_EXCEPTION(); + ProcessStartupActions(std::move(_startupActions)); } + + _CompleteInitialization(); } } @@ -545,7 +513,7 @@ namespace winrt::TerminalApp::implementation // nt -d .` from inside another directory to work as expected. // Return Value: // - - safe_void_coroutine TerminalPage::ProcessStartupActions(std::vector actions, const bool initial, const winrt::hstring cwd, const winrt::hstring env) + safe_void_coroutine TerminalPage::ProcessStartupActions(std::vector actions, const winrt::hstring cwd, const winrt::hstring env) { const auto strong = get_strong(); @@ -597,11 +565,29 @@ namespace winrt::TerminalApp::implementation content.Focus(FocusState::Programmatic); } } + } - if (initial) + void TerminalPage::CreateTabFromConnection(ITerminalConnection connection) + { + NewTerminalArgs newTerminalArgs; + + if (const auto conpty = connection.try_as()) { - _CompleteInitialization(); + newTerminalArgs.Commandline(conpty.Commandline()); + newTerminalArgs.TabTitle(conpty.StartingTitle()); } + + // GH #12370: We absolutely cannot allow a defterm connection to + // auto-elevate. Defterm doesn't work for elevated scenarios in the + // first place. If we try accepting the connection, the spawning an + // elevated version of the Terminal with that profile... that's a + // recipe for disaster. We won't ever open up a tab in this window. + newTerminalArgs.Elevate(false); + const auto newPane = _MakePane(newTerminalArgs, nullptr, std::move(connection)); + newPane->WalkTree([](const auto& pane) { + pane->FinalizeConfigurationGivenDefault(); + }); + _CreateNewTabFromPane(newPane); } // Method Description: @@ -629,7 +615,7 @@ namespace winrt::TerminalApp::implementation // GH#12267: Make sure that we don't instantly close ourselves when // we're readying to accept a defterm connection. In that case, we don't // have a tab yet, but will once we're initialized. - if (_tabs.Size() == 0 && !_shouldStartInboundListener && !_isEmbeddingInboundListener) + if (_tabs.Size() == 0) { CloseWindowRequested.raise(*this, nullptr); co_return; @@ -3653,25 +3639,9 @@ namespace winrt::TerminalApp::implementation _startupActions = std::move(actions); } - // Routine Description: - // - Notifies this Terminal Page that it should start the incoming connection - // listener for command-line tools attempting to join this Terminal - // through the default application channel. - // Arguments: - // - isEmbedding - True if COM started us to be a server. False if we're doing it of our own accord. - // Return Value: - // - - void TerminalPage::SetInboundListener(bool isEmbedding) + void TerminalPage::SetStartupConnection(ITerminalConnection connection) { - _shouldStartInboundListener = true; - _isEmbeddingInboundListener = isEmbedding; - - // If the page has already passed the NotInitialized state, - // then it is ready-enough for us to just start this immediately. - if (_startupState != StartupState::NotInitialized) - { - _StartInboundListener(); - } + _startupConnection = std::move(connection); } winrt::TerminalApp::IDialogPresenter TerminalPage::DialogPresenter() const @@ -4073,68 +4043,6 @@ namespace winrt::TerminalApp::implementation ChangeMaximizeRequested.raise(*this, nullptr); } - HRESULT TerminalPage::_OnNewConnection(const ConptyConnection& connection) - { - _newConnectionRevoker.revoke(); - - // We need to be on the UI thread in order for _OpenNewTab to run successfully. - // HasThreadAccess will return true if we're currently on a UI thread and false otherwise. - // When we're on a COM thread, we'll need to dispatch the calls to the UI thread - // and wait on it hence the locking mechanism. - if (!Dispatcher().HasThreadAccess()) - { - til::latch latch{ 1 }; - auto finalVal = S_OK; - - Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [&]() { - finalVal = _OnNewConnection(connection); - latch.count_down(); - }); - - latch.wait(); - return finalVal; - } - - try - { - NewTerminalArgs newTerminalArgs; - newTerminalArgs.Commandline(connection.Commandline()); - newTerminalArgs.TabTitle(connection.StartingTitle()); - // GH #12370: We absolutely cannot allow a defterm connection to - // auto-elevate. Defterm doesn't work for elevated scenarios in the - // first place. If we try accepting the connection, the spawning an - // elevated version of the Terminal with that profile... that's a - // recipe for disaster. We won't ever open up a tab in this window. - newTerminalArgs.Elevate(false); - const auto newPane = _MakePane(newTerminalArgs, nullptr, connection); - newPane->WalkTree([](const auto& pane) { - pane->FinalizeConfigurationGivenDefault(); - }); - _CreateNewTabFromPane(newPane); - - // Request a summon of this window to the foreground - SummonWindowRequested.raise(*this, nullptr); - - // TEMPORARY SOLUTION - // If the connection has requested for the window to be maximized, - // manually maximize it here. Ideally, we should be _initializing_ - // the session maximized, instead of manually maximizing it after initialization. - // However, because of the current way our defterm handoff works, - // we are unable to get the connection info before the terminal session - // has already started. - - // Make sure that there were no other tabs already existing (in - // the case that we are in glomming mode), because we don't want - // to be maximizing other existing sessions that did not ask for it. - if (_tabs.Size() == 1 && connection.ShowWindow() == SW_SHOWMAXIMIZED) - { - RequestSetMaximized(true); - } - return S_OK; - } - CATCH_RETURN() - } - TerminalApp::IPaneContent TerminalPage::_makeSettingsContent() { if (auto app{ winrt::Windows::UI::Xaml::Application::Current().try_as() }) diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 1fd773fb3b..4879f65fc6 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -129,8 +129,8 @@ namespace winrt::TerminalApp::implementation void RequestSetMaximized(bool newMaximized); void SetStartupActions(std::vector actions); + void SetStartupConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); - void SetInboundListener(bool isEmbedding); static std::vector ConvertExecuteCommandlineToActions(const Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args); winrt::TerminalApp::IDialogPresenter DialogPresenter() const; @@ -147,9 +147,9 @@ namespace winrt::TerminalApp::implementation void ShowTerminalWorkingDirectory(); safe_void_coroutine ProcessStartupActions(std::vector actions, - const bool initial, const winrt::hstring cwd = winrt::hstring{}, const winrt::hstring env = winrt::hstring{}); + void CreateTabFromConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); TerminalApp::WindowProperties WindowProperties() const noexcept { return _WindowProperties; }; @@ -257,8 +257,7 @@ namespace winrt::TerminalApp::implementation StartupState _startupState{ StartupState::NotInitialized }; std::vector _startupActions; - bool _shouldStartInboundListener{ false }; - bool _isEmbeddingInboundListener{ false }; + winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _startupConnection{ nullptr }; std::shared_ptr _windowIdToast{ nullptr }; std::shared_ptr _actionSavedToast{ nullptr }; @@ -282,8 +281,6 @@ namespace winrt::TerminalApp::implementation winrt::Windows::Foundation::Point dragOffset{ 0, 0 }; } _stashed; - winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker; - safe_void_coroutine _NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e); __declspec(noinline) CommandPalette _loadCommandPaletteSlowPath(); @@ -469,8 +466,6 @@ namespace winrt::TerminalApp::implementation void _SetNewTabButtonColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor); void _ClearNewTabButtonColor(); - void _StartInboundListener(); - safe_void_coroutine _CompleteInitialization(); void _FocusActiveControl(IInspectable sender, IInspectable eventArgs); diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index c2afef3412..0b329a8ee4 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -152,38 +152,23 @@ namespace winrt::TerminalApp::implementation // instead. // * if we have commandline arguments, Pass commandline args into the // TerminalPage. - if (!_initialContentArgs.empty()) + if (_startupConnection) { - _root->SetStartupActions(_initialContentArgs); + _root->SetStartupConnection(std::move(_startupConnection)); } - else + else if (!_initialContentArgs.empty()) + { + _root->SetStartupActions(std::move(_initialContentArgs)); + } + else if (const auto& layout = LoadPersistedLayout()) { // layout will only ever be non-null if there were >0 tabs persisted in // .TabLayout(). We can re-evaluate that as a part of TODO: GH#12633 - if (const auto& layout = LoadPersistedLayout()) - { - std::vector actions; - for (const auto& a : layout.TabLayout()) - { - actions.emplace_back(a); - } - _root->SetStartupActions(actions); - } - else if (_appArgs) - { - _root->SetStartupActions(_appArgs->ParsedArgs().GetStartupActions()); - } + _root->SetStartupActions(wil::to_vector(layout.TabLayout())); } - - // Check if we were started as a COM server for inbound connections of console sessions - // coming out of the operating system default application feature. If so, - // tell TerminalPage to start the listener as we have to make sure it has the chance - // to register a handler to hear about the requests first and is all ready to receive - // them before the COM server registers itself. Otherwise, the request might come - // in and be routed to an event with no handlers or a non-ready Page. - if (_appArgs && _appArgs->ParsedArgs().IsHandoffListener()) + else if (_appArgs) { - _root->SetInboundListener(true); + _root->SetStartupActions(_appArgs->ParsedArgs().GetStartupActions()); } return _root->Initialize(hwnd); @@ -1054,6 +1039,7 @@ namespace winrt::TerminalApp::implementation int32_t TerminalWindow::SetStartupCommandline(TerminalApp::CommandlineArgs args) { _appArgs = winrt::get_self(args); + _startupConnection = args.Connection(); auto& parsedArgs = _appArgs->ParsedArgs(); _WindowProperties->SetInitialCwd(_appArgs->CurrentDirectory()); @@ -1114,13 +1100,16 @@ namespace winrt::TerminalApp::implementation auto& parsedArgs = _appArgs->ParsedArgs(); auto& actions = parsedArgs.GetStartupActions(); - _root->ProcessStartupActions(actions, false, _appArgs->CurrentDirectory(), _appArgs->CurrentEnvironment()); - - if (parsedArgs.IsHandoffListener()) + if (auto conn = args.Connection()) { - _root->SetInboundListener(true); + _root->CreateTabFromConnection(std::move(conn)); + } + else if (!actions.empty()) + { + _root->ProcessStartupActions(actions, _appArgs->CurrentDirectory(), _appArgs->CurrentEnvironment()); } } + // Return the result of parsing with commandline, though it may or may not be used. return _appArgs->ExitCode(); } diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index a7db7a8034..5127b3465c 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -169,6 +169,7 @@ namespace winrt::TerminalApp::implementation winrt::com_ptr _root{ nullptr }; wil::com_ptr _appArgs{ nullptr }; + winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _startupConnection{ nullptr }; bool _hasCommandLineArguments{ false }; bool _gotSettingsStartupActions{ false }; std::vector _settingsStartupArgs{}; diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp index 01b25c9ffe..c132a74615 100644 --- a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp +++ b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp @@ -39,7 +39,7 @@ try ComPtr unk; RETURN_IF_FAILED(classFactory.As(&unk)); - RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &g_cTerminalHandoffRegistration)); + RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &g_cTerminalHandoffRegistration)); return S_OK; } @@ -83,41 +83,19 @@ HRESULT CTerminalHandoff::s_StopListening() // from the registered handler event function. HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo) { - try - { - // Because we are REGCLS_SINGLEUSE... we need to `CoRevokeClassObject` after we handle this ONE call. - // COM does not automatically clean that up for us. We must do it. - LOG_IF_FAILED(s_StopListening()); + // Report an error if no one registered a handoff function before calling this. + RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); - // Report an error if no one registered a handoff function before calling this. - THROW_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); - - // Call registered handler from when we started listening. - THROW_IF_FAILED(_pfnHandoff(in, out, signal, reference, server, client, startupInfo)); + // Call registered handler from when we started listening. + RETURN_IF_FAILED(_pfnHandoff(in, out, signal, reference, server, client, startupInfo)); #pragma warning(suppress : 26477) - TraceLoggingWrite( - g_hTerminalConnectionProvider, - "ReceiveTerminalHandoff_Success", - TraceLoggingDescription("successfully received a terminal handoff"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + TraceLoggingWrite( + g_hTerminalConnectionProvider, + "ReceiveTerminalHandoff_Success", + TraceLoggingDescription("successfully received a terminal handoff"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - return S_OK; - } - catch (...) - { - const auto hr = wil::ResultFromCaughtException(); - -#pragma warning(suppress : 26477) - TraceLoggingWrite( - g_hTerminalConnectionProvider, - "ReceiveTerminalHandoff_Failed", - TraceLoggingDescription("failed while receiving a terminal handoff"), - TraceLoggingHResult(hr), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - - return hr; - } + return S_OK; } diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 1e388f0f32..33d4b31cfb 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -77,6 +77,11 @@ AppHost::AppHost(WindowEmperor* manager, const winrt::TerminalApp::AppLogic& log _windowCallbacks.ShouldExitFullscreen = _window->ShouldExitFullscreen({ &_windowLogic, &winrt::TerminalApp::TerminalWindow::RequestExitFullscreen }); _window->MakeWindow(); + + // Does window creation mean the window was activated (WM_ACTIVATE)? No. + // But it simplifies `WindowEmperor::_mostRecentWindow()`, because now the creation of a + // new window marks it as the most recent one immediately, even before it becomes active. + QueryPerformanceCounter(&_lastActivatedTime); } bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down) diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 7e3ef659b6..ba128e2814 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -1242,9 +1242,12 @@ void IslandWindow::_SetIsFullscreen(const bool fullscreenEnabled) // - void IslandWindow::SummonWindow(winrt::TerminalApp::SummonWindowBehavior args) { - auto actualDropdownDuration = args.DropdownDuration(); + const auto toggleVisibility = args ? args.ToggleVisibility() : false; + const auto toMonitor = args ? args.ToMonitor() : winrt::TerminalApp::MonitorBehavior::InPlace; + auto dropdownDuration = args ? args.DropdownDuration() : 0; + // If the user requested an animation, let's check if animations are enabled in the OS. - if (actualDropdownDuration > 0) + if (dropdownDuration > 0) { auto animationsEnabled = TRUE; SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0); @@ -1258,7 +1261,7 @@ void IslandWindow::SummonWindow(winrt::TerminalApp::SummonWindowBehavior args) // _globalActivateWindow/_globalDismissWindow might do if they think // there should be an animation (like making the window appear with // SetWindowPlacement rather than ShowWindow) - actualDropdownDuration = 0; + dropdownDuration = 0; } } @@ -1269,33 +1272,33 @@ void IslandWindow::SummonWindow(winrt::TerminalApp::SummonWindowBehavior args) // - activate the window // - else // - dismiss the window - if (args.ToggleVisibility() && GetForegroundWindow() == _window.get()) + if (toggleVisibility && GetForegroundWindow() == _window.get()) { auto handled = false; // They want to toggle the window when it is the FG window, and we are // the FG window. However, if we're on a different monitor than the // mouse, then we should move to that monitor instead of dismissing. - if (args.ToMonitor() == winrt::TerminalApp::MonitorBehavior::ToMouse) + if (toMonitor == winrt::TerminalApp::MonitorBehavior::ToMouse) { const til::rect cursorMonitorRect{ _getMonitorForCursor().rcMonitor }; const til::rect currentMonitorRect{ _getMonitorForWindow(GetHandle()).rcMonitor }; if (cursorMonitorRect != currentMonitorRect) { // We're not on the same monitor as the mouse. Go to that monitor. - _globalActivateWindow(actualDropdownDuration, args.ToMonitor()); + _globalActivateWindow(dropdownDuration, toMonitor); handled = true; } } if (!handled) { - _globalDismissWindow(actualDropdownDuration); + _globalDismissWindow(dropdownDuration); } } else { - _globalActivateWindow(actualDropdownDuration, args.ToMonitor()); + _globalActivateWindow(dropdownDuration, toMonitor); } } diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 1a750af650..1bd15789ec 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -345,20 +345,31 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) for (const auto layout : layouts) { hstring args[] = { L"wt", L"-w", L"new", L"-s", winrt::to_hstring(startIdx) }; - _dispatchCommandline({ args, cwd, showCmd, env }); + _dispatchCommandlineCommon(args, cwd, env, showCmd); startIdx += 1; } } - // Create another window if needed: There aren't any yet, or we got an explicit command line. const auto args = commandlineToArgArray(GetCommandLineW()); - if (_windows.empty() || args.size() != 1) - { - _dispatchCommandline({ args, cwd, showCmd, env }); - } - // If we created no windows, e.g. because the args are "/?" we can just exit now. - _postQuitMessageIfNeeded(); + if (args.size() == 2 && args[1] == L"-Embedding") + { + // We were launched for ConPTY handoff. We have no windows and also don't want to exit. + // + // TODO: Here we could start a timer and exit after, say, 5 seconds + // if no windows are created. But that's a minor concern. + } + else + { + // Create another window if needed: There aren't any yet, OR we got an explicit command line. + if (_windows.empty() || args.size() != 1) + { + _dispatchCommandlineCommon(args, cwd, env, showCmd); + } + + // If we created no windows, e.g. because the args are "/?" we can just exit now. + _postQuitMessageIfNeeded(); + } } // ALWAYS change the _real_ CWD of the Terminal to system32, @@ -386,6 +397,19 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) } } + { + TerminalConnection::ConptyConnection::NewConnection([this](TerminalConnection::ConptyConnection conn) { + TerminalApp::CommandlineArgs args; + args.ShowWindowCommand(conn.ShowWindow()); + args.Connection(std::move(conn)); + _dispatchCommandline(std::move(args)); + _summonWindow(SummonWindowSelectionArgs{ + .SummonBehavior = nullptr, + }); + }); + TerminalConnection::ConptyConnection::StartInboundListener(); + } + // Main message loop. It pumps all windows. bool loggedInteraction = false; MSG msg{}; @@ -588,6 +612,16 @@ void WindowEmperor::_dispatchCommandline(winrt::TerminalApp::CommandlineArgs arg } } +void WindowEmperor::_dispatchCommandlineCommon(winrt::array_view args, std::wstring_view currentDirectory, std::wstring_view envString, uint32_t showWindowCommand) +{ + winrt::TerminalApp::CommandlineArgs c; + c.Commandline(args); + c.CurrentDirectory(currentDirectory); + c.CurrentEnvironment(envString); + c.ShowWindowCommand(showWindowCommand); + _dispatchCommandline(std::move(c)); +} + // This is an implementation-detail of _dispatchCommandline(). safe_void_coroutine WindowEmperor::_dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args) { @@ -896,10 +930,8 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c { const auto handoff = deserializeHandoffPayload(static_cast(cds->lpData), static_cast(cds->lpData) + cds->cbData); const winrt::hstring args{ handoff.args }; - const winrt::hstring env{ handoff.env }; - const winrt::hstring cwd{ handoff.cwd }; const auto argv = commandlineToArgArray(args.c_str()); - _dispatchCommandline({ argv, cwd, gsl::narrow_cast(handoff.show), env }); + _dispatchCommandlineCommon(argv, handoff.cwd, handoff.env, handoff.show); } return 0; case WM_HOTKEY: @@ -1198,7 +1230,7 @@ void WindowEmperor::_hotkeyPressed(const long hotkeyIndex) const wil::unique_environstrings_ptr envMem{ GetEnvironmentStringsW() }; const auto env = stringFromDoubleNullTerminated(envMem.get()); const auto cwd = wil::GetCurrentDirectoryW(); - _dispatchCommandline({ argv, cwd, SW_SHOWDEFAULT, std::move(env) }); + _dispatchCommandlineCommon(argv, cwd, env, SW_SHOWDEFAULT); } void WindowEmperor::_registerHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index c84882665f..6995ab4c68 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -52,10 +52,10 @@ private: void _summonAllWindows() const; void _dispatchSpecialKey(const MSG& msg) const; void _dispatchCommandline(winrt::TerminalApp::CommandlineArgs args); + void _dispatchCommandlineCommon(winrt::array_view args, std::wstring_view currentDirectory, std::wstring_view envString, uint32_t showWindowCommand); safe_void_coroutine _dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args); LRESULT _messageHandler(HWND window, UINT message, WPARAM wParam, LPARAM lParam) noexcept; void _createMessageWindow(const wchar_t* className); - bool _shouldSkipClosingWindows() const; void _postQuitMessageIfNeeded() const; safe_void_coroutine _showMessageBox(winrt::hstring message, bool error); void _notificationAreaMenuRequested(WPARAM wParam); diff --git a/src/cascadia/WindowsTerminal/pch.h b/src/cascadia/WindowsTerminal/pch.h index 583b67a0e4..87a0ade306 100644 --- a/src/cascadia/WindowsTerminal/pch.h +++ b/src/cascadia/WindowsTerminal/pch.h @@ -67,8 +67,9 @@ Abstract: #include #include -#include #include +#include +#include #include #include From 43b59d504b0d0de7c03167f1576005f181fcfa4b Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 2 Jul 2025 18:01:39 -0500 Subject: [PATCH 30/74] build: partially revert AzCopy changes from 22c509f (#19092) When we first transitioned to the `R1` network isolation environment--which required us to _not_ contact powershellgallery.com--we had to give up on the `AzureFileCopy` task. Since then, the `AzurePowerShell` task has also become somewhat broken while OneBranch fixed the issue that prevented us from using `AzureFileCopy`. In short, we now need to: - Install the Azure PowerShell modules directly from our own feed (which satisfies the network constraint) - ... using `PSResourceGet` ... - ... for Windows PowerShell 5.1 ... - which is used by `AzureFileCopy` to later perform authentication. --- .github/actions/spelling/expect/expect.txt | 2 -- .../job-deploy-to-azure-storage.yml | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 3507bf387b..363133dd1a 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -78,7 +78,6 @@ autoscrolling Autowrap AVerify awch -AZCOPY azurecr AZZ backgrounded @@ -1342,7 +1341,6 @@ PROPTITLE propvar propvariant psa -PSCRED PSECURITY pseudoconsole psh diff --git a/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml b/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml index 8c2836c059..2f68ba92c3 100644 --- a/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml +++ b/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml @@ -75,13 +75,30 @@ jobs: } displayName: "Wrangle Unpackaged builds into place, rename" - - task: AzurePowerShell@5 + - task: PowerShell@2 + displayName: Install Azure Modules from custom PowerShell Gallery Repo + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + pwsh: false # We are preparing modules for AzureFileCopy, which uses PowerShell 5.1 + targetType: inline + script: |- + $MachineToken = $env:SYSTEM_ACCESSTOKEN | ConvertTo-SecureString -AsPlainText -Force + $Credential = [PSCredential]::new("ONEBRANCH_TOKEN", $MachineToken) + $MachineToken = $null + $Feed = "https://pkgs.dev.azure.com/shine-oss/terminal/_packaging/TerminalDependencies/nuget/v3/index.json" + Register-PSResourceRepository -Name "PSGalleryUpstream" -Uri $Feed -Trusted + Get-PSResourceRepository + + Install-PSResource -Name Az.Accounts, Az.Storage, Az.Network, Az.Resources, Az.Compute -Repository "PSGalleryUpstream" -Credential $Credential + + - task: AzureFileCopy@6 displayName: Publish to Storage Account inputs: + sourcePath: _out/* + Destination: AzureBlob azureSubscription: ${{ parameters.subscription }} - azurePowerShellVersion: LatestVersion - pwsh: true - ScriptType: InlineScript - Inline: |- - $Env:AZCOPY_AUTO_LOGIN_TYPE="PSCRED" - & AzCopy copy "_out\*" "https://${{ parameters.storageAccount }}.blob.core.windows.net/${{ parameters.storageContainer }}/" --content-type application/octet-stream + storage: ${{ parameters.storageAccount }} + ContainerName: ${{ parameters.storageContainer }} + AdditionalArgumentsForBlobCopy: "--content-type application/octet-stream" + From 97f0a06fbe0d01521c14e7b1461faea2e5a83d5a Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 3 Jul 2025 01:49:33 +0200 Subject: [PATCH 31/74] Fix another VT input double-encoding issue (#19083) Closes #17264 Closes https://github.com/microsoft/edit/issues/182 Long shot, but probably also... Closes #18579 Closes #19082 --- src/terminal/adapter/InteractDispatch.cpp | 33 +++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index 09cc2c970c..8ed76b9d7c 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -62,15 +62,38 @@ void InteractDispatch::WriteString(const std::wstring_view string) { if (!string.empty()) { - const auto codepage = _api.GetOutputCodePage(); - InputEventQueue keyEvents; + const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); +#pragma warning(suppress : 26429) // Symbol 'inputBuffer' is never tested for nullness, it can be marked as not_null (f.23). + const auto inputBuffer = gci.GetActiveInputBuffer(); - for (const auto& wch : string) + // The input *may* be keyboard input in which case we must call CharToKeyEvents. + // + // However, it could also be legitimate VT sequences (e.g. a bracketed paste sequence). + // If we called `InputBuffer::Write` with those, we would end up indirectly + // calling `TerminalInput::HandleKey` and "double encode" the sequence. + // The effect of this is noticeable with the German keyboard layout, for instance, + // where the [ key maps to AltGr+8, and we fail to map it back to [ later. + // + // It's worth noting that all of this is bad design in either case. + // The way it should work is that we write INPUT_RECORDs and Strings as-is into the + // InputBuffer, and only during retrieval they're converted into one or the other. + // This prevents any kinds of double-encoding issues. + if (inputBuffer->IsInVirtualTerminalInputMode()) { - CharToKeyEvents(wch, codepage, keyEvents); + inputBuffer->WriteString(string); } + else + { + const auto codepage = _api.GetOutputCodePage(); + InputEventQueue keyEvents; - WriteInput(keyEvents); + for (const auto& wch : string) + { + CharToKeyEvents(wch, codepage, keyEvents); + } + + inputBuffer->Write(keyEvents); + } } } From 4500d428319ee3d13835c13910204f9e080b8d56 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Thu, 3 Jul 2025 12:16:04 -0500 Subject: [PATCH 32/74] build: enable the compliance theatre build options (#19094) This makes some compiler warnings into actual bug reports that get filed on us. --- build/pipelines/ob-nightly.yml | 1 + .../pipeline-onebranch-full-release-build.yml | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/build/pipelines/ob-nightly.yml b/build/pipelines/ob-nightly.yml index 033d075cd6..63eba379d8 100644 --- a/build/pipelines/ob-nightly.yml +++ b/build/pipelines/ob-nightly.yml @@ -50,6 +50,7 @@ extends: parameters: pool: { type: windows } variables: + ob_sdl_prefast_enabled: false # This is a collection of powershell scripts ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: "$(Build.SourcesDirectory)/_none" diff --git a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml index 0f09271a72..3746402e3e 100644 --- a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml +++ b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml @@ -88,6 +88,9 @@ extends: enabled: false globalSdl: # https://aka.ms/obpipelines/sdl enableCheckCFlags: false # CheckCFlags is broken and exploding our builds; to remove, :g/BAD-FLAGS/d + isNativeCode: true + prefast: + enabled: true asyncSdl: enabled: true tsaOptionsFile: 'build/config/tsa.json' @@ -115,6 +118,8 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_runDuring: Build + ob_sdl_checkCompliantCompilerWarnings: true ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(JobOutputDirectory) @@ -149,6 +154,7 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_enabled: false # This is a C# build job ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(JobOutputDirectory) @@ -180,6 +186,7 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_enabled: false # This is a collection of powershell scripts ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(JobOutputDirectory) @@ -234,6 +241,7 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_enabled: false # This is a collection of powershell scripts ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(JobOutputDirectory) @@ -252,6 +260,7 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_enabled: false # This is a collection of powershell scripts ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(JobOutputDirectory) @@ -277,6 +286,7 @@ extends: variables: ob_sdl_checkcflags_enabled: false # BAD-FLAGS ob_sdl_xfgcheck_enabled: false # BAD-FLAGS + ob_sdl_prefast_enabled: false # This is a collection of powershell scripts ob_git_checkout: false # This job checks itself out ob_git_skip_checkout_none: true ob_outputDirectory: $(Build.ArtifactStagingDirectory) From ac07afebcb949687db8cadf60605f11ee804ec15 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 4 Jul 2025 00:19:12 +0200 Subject: [PATCH 33/74] Fix a crash during commandline handoff (#19096) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The crash occurs because WinRT `abort()`s when it encounters a `std::wstring_view` without null-terminator. Closes #19093 ## Validation Steps Performed * Set `wtd` as the default terminal * Launch `cmd` * Launch `wtd` * 2 windows ✅ --- .../WindowsTerminal/WindowEmperor.cpp | 36 ++++++++++--------- src/cascadia/WindowsTerminal/WindowEmperor.h | 2 +- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 1bd15789ec..7cd5223a50 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -49,7 +49,7 @@ static std::vector commandlineToArgArray(const wchar_t* commandL } // Returns the length of a double-null encoded string *excluding* the trailing double-null character. -static std::wstring_view stringFromDoubleNullTerminated(const wchar_t* beg) +static wil::zwstring_view stringFromDoubleNullTerminated(const wchar_t* beg) { auto end = beg; @@ -57,7 +57,7 @@ static std::wstring_view stringFromDoubleNullTerminated(const wchar_t* beg) { } - return { beg, end }; + return { beg, gsl::narrow_cast(end - beg) }; } // Appends an uint32_t to a byte vector. @@ -78,36 +78,39 @@ static const uint8_t* deserializeUint32(const uint8_t* it, const uint8_t* end, u return it + sizeof(uint32_t); } -// Writes an uint32_t length prefix, followed by the string data, to the output vector. -static void serializeString(std::vector& out, std::wstring_view str) +// Writes a null-terminated string to `out`: A uint32_t length prefix, +// *including null byte*, followed by the string data, followed by the null-terminator. +static void serializeString(std::vector& out, wil::zwstring_view str) { const auto ptr = reinterpret_cast(str.data()); - const auto len = gsl::narrow(str.size()); - serializeUint32(out, len); + const auto len = str.size() + 1; + serializeUint32(out, gsl::narrow(len)); out.insert(out.end(), ptr, ptr + len * sizeof(wchar_t)); } -// Parses the next string from the input iterator. Performs bounds-checks. +// Counter-part to `serializeString`. Performs bounds-checks. // Returns an iterator that points past it. -static const uint8_t* deserializeString(const uint8_t* it, const uint8_t* end, std::wstring_view& str) +static const uint8_t* deserializeString(const uint8_t* it, const uint8_t* end, wil::zwstring_view& str) { uint32_t len; it = deserializeUint32(it, end, len); - if (static_cast(end - it) < len * sizeof(wchar_t)) + const auto bytes = static_cast(len) * sizeof(wchar_t); + + if (bytes == 0 || static_cast(end - it) < bytes) { throw std::out_of_range("Not enough data for string content"); } - str = { reinterpret_cast(it), len }; - return it + len * sizeof(wchar_t); + str = { reinterpret_cast(it), len - 1 }; + return it + bytes; } struct Handoff { - std::wstring_view args; - std::wstring_view env; - std::wstring_view cwd; + wil::zwstring_view args; + wil::zwstring_view env; + wil::zwstring_view cwd; uint32_t show; }; @@ -612,7 +615,7 @@ void WindowEmperor::_dispatchCommandline(winrt::TerminalApp::CommandlineArgs arg } } -void WindowEmperor::_dispatchCommandlineCommon(winrt::array_view args, std::wstring_view currentDirectory, std::wstring_view envString, uint32_t showWindowCommand) +void WindowEmperor::_dispatchCommandlineCommon(winrt::array_view args, wil::zwstring_view currentDirectory, wil::zwstring_view envString, uint32_t showWindowCommand) { winrt::TerminalApp::CommandlineArgs c; c.Commandline(args); @@ -929,8 +932,7 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c if (const auto cds = reinterpret_cast(lParam); cds->dwData == TERMINAL_HANDOFF_MAGIC) { const auto handoff = deserializeHandoffPayload(static_cast(cds->lpData), static_cast(cds->lpData) + cds->cbData); - const winrt::hstring args{ handoff.args }; - const auto argv = commandlineToArgArray(args.c_str()); + const auto argv = commandlineToArgArray(handoff.args.c_str()); _dispatchCommandlineCommon(argv, handoff.cwd, handoff.env, handoff.show); } return 0; diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index 6995ab4c68..b0213e7635 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -52,7 +52,7 @@ private: void _summonAllWindows() const; void _dispatchSpecialKey(const MSG& msg) const; void _dispatchCommandline(winrt::TerminalApp::CommandlineArgs args); - void _dispatchCommandlineCommon(winrt::array_view args, std::wstring_view currentDirectory, std::wstring_view envString, uint32_t showWindowCommand); + void _dispatchCommandlineCommon(winrt::array_view args, wil::zwstring_view currentDirectory, wil::zwstring_view envString, uint32_t showWindowCommand); safe_void_coroutine _dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args); LRESULT _messageHandler(HWND window, UINT message, WPARAM wParam, LPARAM lParam) noexcept; void _createMessageWindow(const wchar_t* className); From 02f173d504d82a05da048981c56b7402c11b6c0c Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 8 Jul 2025 17:27:21 -0500 Subject: [PATCH 34/74] Include the hash of the SID in the Window Class and Mutant (#19109) Right now, we do not use a sufficiently unique name to disambiguate Terminal instances running on the same desktop. Mutexes (mutants) are named objects that live in the user's session, under `Sessions\1\BaseNamedObjects` (for the local desktop session). When multiple users are logged into the same session--such as with "Run as different user"--they share a local BaseNamedObjects namespace. Ugh. We cannot use [`CreatePrivateNamespace`] as it requires a boundary descriptor, and the only boundary descriptors supported by the current API are based on package identity. I also fear that `CreatePrivateNamespace` is subject to a race condition with `OpenPrivateNamespace`; Create will not Open an existing one, so we would need to back off and retry either opening or creating. Yuck. After this commit, we will hash the user's SID into the name of both the window class and the mutant, right after the path hash (if running unpackaged). Closes #18704 [`CreatePrivateNamespace`]: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprivatenamespacea --- src/cascadia/WindowsTerminal/WindowEmperor.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 7cd5223a50..c19d2f22d5 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "AppHost.h" #include "resource.h" @@ -275,7 +276,7 @@ AppHost* WindowEmperor::_mostRecentWindow() const noexcept void WindowEmperor::HandleCommandlineArgs(int nCmdShow) { std::wstring windowClassName; - windowClassName.reserve(47); // "Windows Terminal Preview Admin 0123456789012345" + windowClassName.reserve(64); // "Windows Terminal Preview Admin 0123456789012345 0123456789012345" #if defined(WT_BRANDING_RELEASE) windowClassName.append(L"Windows Terminal"); #elif defined(WT_BRANDING_PREVIEW) @@ -300,6 +301,18 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) #endif } + { + wil::unique_handle processToken{ GetCurrentProcessToken() }; + const auto userTokenInfo{ wil::get_token_information(processToken.get()) }; + const auto sidLength{ GetLengthSid(userTokenInfo->User.Sid) }; + const auto hash{ til::hash(userTokenInfo->User.Sid, sidLength) }; +#ifdef _WIN64 + fmt::format_to(std::back_inserter(windowClassName), FMT_COMPILE(L" {:016x}"), hash); +#else + fmt::format_to(std::back_inserter(windowClassName), FMT_COMPILE(L" {:08x}"), hash); +#endif + } + // Windows Terminal is a single-instance application. Either acquire ownership // over the mutex, or hand off the command line to the existing instance. const auto mutex = acquireMutexOrAttemptHandoff(windowClassName.c_str(), nCmdShow); From c4fbb58f69b0a5cc86c245505311d0a0b3cc1399 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 9 Jul 2025 15:42:23 -0500 Subject: [PATCH 35/74] During session save, use the profile's GUID rather than its Name (#19113) This will prevent Terminal from erroneously selecting a hidden (deleted, disabled or otherwise) profile of the same name during restoration and subsequently using the wrong settings. I am not certain why we used the name at all! Closes #19105 --- src/cascadia/TerminalApp/TerminalPaneContent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index c85009ea0c..42e6e195f0 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -95,7 +95,7 @@ namespace winrt::TerminalApp::implementation NewTerminalArgs args{}; const auto& controlSettings = _control.Settings(); - args.Profile(controlSettings.ProfileName()); + args.Profile(::Microsoft::Console::Utils::GuidToString(_profile.Guid())); // If we know the user's working directory use it instead of the profile. if (const auto dir = _control.WorkingDirectory(); !dir.empty()) { From f14718f738bea5744ab6b61c3d63aaf998d97ad7 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 9 Jul 2025 16:36:13 -0500 Subject: [PATCH 36/74] version: fix the LCID in our VERSIONINFO, add descriptions to each file (#19114) Closes #19106 --- custom.props | 3 +++ src/cascadia/ElevateShim/elevate-shim.vcxproj | 1 + src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj | 1 + src/cascadia/TerminalApp/dll/TerminalApp.vcxproj | 1 + src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj | 1 + src/cascadia/TerminalConnection/TerminalConnection.vcxproj | 1 + src/cascadia/TerminalControl/dll/TerminalControl.vcxproj | 1 + .../Microsoft.Terminal.Settings.Editor.vcxproj | 1 + .../dll/Microsoft.Terminal.Settings.Model.vcxproj | 1 + src/cascadia/UIHelpers/UIHelpers.vcxproj | 1 + src/cascadia/UIMarkdown/UIMarkdown.vcxproj | 1 + src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj | 1 + src/cascadia/wt/wt.vcxproj | 1 + src/host/exe/Host.EXE.vcxproj | 1 + src/propsheet/propsheet.vcxproj | 1 + src/winconpty/dll/winconptydll.vcxproj | 1 + 16 files changed, 18 insertions(+) diff --git a/custom.props b/custom.props index 8e358b3e0b..d3fea955c2 100644 --- a/custom.props +++ b/custom.props @@ -7,5 +7,8 @@ 1 24 Windows Terminal + 1033 + + \xa9 Microsoft Corporation. All rights reserved. diff --git a/src/cascadia/ElevateShim/elevate-shim.vcxproj b/src/cascadia/ElevateShim/elevate-shim.vcxproj index 95b6240659..80dccc6b0c 100644 --- a/src/cascadia/ElevateShim/elevate-shim.vcxproj +++ b/src/cascadia/ElevateShim/elevate-shim.vcxproj @@ -7,6 +7,7 @@ elevate-shim elevate-shim Application + Windows Terminal Administrator Launch Helper diff --git a/src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj b/src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj index 4207b58282..d4cc256839 100644 --- a/src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj +++ b/src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj @@ -10,6 +10,7 @@ Console false + Windows Terminal Open Here Shell Extension true diff --git a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj index 8e158d51da..6a20f0ac9b 100644 --- a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj @@ -11,6 +11,7 @@ true true + Windows Terminal Main UI Library true diff --git a/src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj b/src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj index eada6967cb..29719f4850 100644 --- a/src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj +++ b/src/cascadia/TerminalAzBridge/TerminalAzBridge.vcxproj @@ -11,6 +11,7 @@ false Windows Store Windows + Windows Terminal Azure Cloud Shell Connector diff --git a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj index 8eda993772..2c7d276c50 100644 --- a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj +++ b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj @@ -8,6 +8,7 @@ Console true true + Windows Terminal Connection Library true diff --git a/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj b/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj index e6cd662dd1..2af748bb22 100644 --- a/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj +++ b/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj @@ -21,6 +21,7 @@ projects compile properly when they depend on this "Microsoft.winmd." --> 3 + Windows Terminal Control Library diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index da55bbd757..20f1538978 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -31,6 +31,7 @@ --> 4 nested + Windows Terminal Settings UI Library true diff --git a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj index 6ab7c60689..eaf33503a4 100644 --- a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj +++ b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj @@ -11,6 +11,7 @@ true true + Windows Terminal Settings Model Library true diff --git a/src/cascadia/UIHelpers/UIHelpers.vcxproj b/src/cascadia/UIHelpers/UIHelpers.vcxproj index eaae5b75ac..61a82db255 100644 --- a/src/cascadia/UIHelpers/UIHelpers.vcxproj +++ b/src/cascadia/UIHelpers/UIHelpers.vcxproj @@ -7,6 +7,7 @@ DynamicLibrary Console true + Windows Terminal UI Support Library true diff --git a/src/cascadia/UIMarkdown/UIMarkdown.vcxproj b/src/cascadia/UIMarkdown/UIMarkdown.vcxproj index c23ec98a87..f347b49979 100644 --- a/src/cascadia/UIMarkdown/UIMarkdown.vcxproj +++ b/src/cascadia/UIMarkdown/UIMarkdown.vcxproj @@ -17,6 +17,7 @@ --> 4 nested + Windows Terminal Markdown Control Library true diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj index ef96673272..5135d643fb 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj @@ -13,6 +13,7 @@ true false Windows + Windows Terminal Host true diff --git a/src/cascadia/wt/wt.vcxproj b/src/cascadia/wt/wt.vcxproj index 20e4b3f6f2..6d7edd9d1c 100644 --- a/src/cascadia/wt/wt.vcxproj +++ b/src/cascadia/wt/wt.vcxproj @@ -7,6 +7,7 @@ wt wt Application + Windows Terminal Launcher Shim diff --git a/src/host/exe/Host.EXE.vcxproj b/src/host/exe/Host.EXE.vcxproj index f055eb9146..3deb6f37f3 100644 --- a/src/host/exe/Host.EXE.vcxproj +++ b/src/host/exe/Host.EXE.vcxproj @@ -7,6 +7,7 @@ Host.EXE OpenConsole Application + Console Window and PTY Host (Open Source) true diff --git a/src/propsheet/propsheet.vcxproj b/src/propsheet/propsheet.vcxproj index dd31fa42d3..77ea31ef93 100644 --- a/src/propsheet/propsheet.vcxproj +++ b/src/propsheet/propsheet.vcxproj @@ -7,6 +7,7 @@ Propsheet.DLL console DynamicLibrary + Windows Console Host Property Sheet diff --git a/src/winconpty/dll/winconptydll.vcxproj b/src/winconpty/dll/winconptydll.vcxproj index ef86147e67..80ed63fa23 100644 --- a/src/winconpty/dll/winconptydll.vcxproj +++ b/src/winconpty/dll/winconptydll.vcxproj @@ -7,6 +7,7 @@ winconpty.DLL conpty DynamicLibrary + ConPTY Interface Library From 0cbb6b1f2f32a8c3242b6cb40c8eee0c3cd9fcd4 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 14 Jul 2025 15:40:20 -0500 Subject: [PATCH 37/74] Rewrite HighlightedTextControl and remove HighlightedText (#19130) `HighlightedTextControl` is a XAML user control that contains a text block and takes vector of `HighlightedText`. `HighlightedText` uses tuples `(string, boolean)`. Allocating an entire object to store a string and an integer felt like a waste, especially when we were doing it thousands of times for the command palette _and just to pass them into another object that stores a string and a few more integers (`Run`)._ The new `HighlightedTextControl` is a standard templated control, and supports styling of both the inner text block and of the highlighted runs. It no longer takes a `HighlightedText`, but rather a standard string and a set of runs (tuple `(int start, int end)`); these can be stored more efficiently, and this change moves the construction of text and runs directly into `HighlightedTextControl` itself as an implementation detail rather than an API contract. ### XAML Properties - `Text`: the string to highlight - `HighlightedRuns`: a vector of `(start, end)` pairs, indicating which regions are intended to be highlighted. Can be empty (which indicates there is no highlight and that the entire string is styled normally.) - `TextBlockStyle`: the `Style` applied to the inner text block; optional; allows consumers to change how both normal and highlighted text looks. - `HighlightedRunStyle`: a `Style` applied only to the highlighted runs; optional; allows consumers to change how highlighted text looks. If left NULL, highlighted runs will be bold. `HighlightedRunStyle` is a little bodgy. It only applies to `Run` objects (which is fine, and XAML somewhat supports), but since `Run` is not a `FrameworkElement`, it _doesn't actually have a `Style` member._ We need to crack open the style and apply it manually, entry by entry. `FontWeight` is special because XAML is a special little flower. --- .github/actions/spelling/expect/expect.txt | 1 + .../FilteredCommandTests.cpp | 77 ++++---- src/cascadia/TerminalApp/App.xaml | 2 + src/cascadia/TerminalApp/CommandPalette.xaml | 9 +- src/cascadia/TerminalApp/FilteredCommand.cpp | 55 +++--- src/cascadia/TerminalApp/FilteredCommand.h | 2 +- src/cascadia/TerminalApp/FilteredCommand.idl | 2 +- src/cascadia/TerminalApp/HighlightedText.cpp | 31 --- src/cascadia/TerminalApp/HighlightedText.h | 35 ---- src/cascadia/TerminalApp/HighlightedText.idl | 22 --- .../TerminalApp/HighlightedTextControl.cpp | 180 +++++++++++++----- .../TerminalApp/HighlightedTextControl.h | 17 +- .../TerminalApp/HighlightedTextControl.idl | 21 +- .../TerminalApp/HighlightedTextControl.xaml | 11 -- .../HighlightedTextControlStyle.xaml | 23 +++ .../TerminalApp/SnippetsPaneContent.xaml | 3 +- .../TerminalApp/SuggestionsControl.xaml | 6 +- .../TerminalApp/TerminalAppLib.vcxproj | 11 +- .../TerminalAppLib.vcxproj.filters | 11 +- src/cascadia/TerminalSettingsEditor/Utils.h | 21 -- src/cascadia/inc/cppwinrt_utils.h | 40 ++++ 21 files changed, 305 insertions(+), 275 deletions(-) delete mode 100644 src/cascadia/TerminalApp/HighlightedText.cpp delete mode 100644 src/cascadia/TerminalApp/HighlightedText.h delete mode 100644 src/cascadia/TerminalApp/HighlightedText.idl delete mode 100644 src/cascadia/TerminalApp/HighlightedTextControl.xaml create mode 100644 src/cascadia/TerminalApp/HighlightedTextControlStyle.xaml diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 363133dd1a..357606d5fa 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -2,6 +2,7 @@ aaaaabbb aabbcc ABANDONFONT abbcc +abcc abgr ABORTIFHUNG ACCESSTOKEN diff --git a/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp b/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp index 023ac04702..3175d80749 100644 --- a/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/FilteredCommandTests.cpp @@ -28,74 +28,81 @@ namespace TerminalAppLocalTests TEST_METHOD(VerifyCompareIgnoreCase); }; + static void _verifySegment(auto&& segments, uint32_t index, uint64_t start, uint64_t end) + { + const auto& segment{ segments.GetAt(index) }; + VERIFY_ARE_EQUAL(segment.Start, start, NoThrowString().Format(L"segment %zu", index)); + VERIFY_ARE_EQUAL(segment.End, end, NoThrowString().Format(L"segment %zu", index)); + } + void FilteredCommandTests::VerifyHighlighting() { auto result = RunOnUIThread([]() { + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; + const auto paletteItem{ winrt::make(L"AAAAAABBBBBBCCC") }; const auto filteredCommand = winrt::make_self(paletteItem); { Log::Comment(L"Testing command name segmentation with no filter"); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 1u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_IS_NULL(segments); // No matches = no segments } { Log::Comment(L"Testing command name segmentation with empty filter"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L""))); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 1u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_IS_NULL(segments); // No matches = no segments } { Log::Comment(L"Testing command name segmentation with filter equal to the string"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"AAAAAABBBBBBCCC"))); - auto segments = filteredCommand->HighlightedName().Segments(); + const auto segments = filteredCommand->NameHighlights(); + VERIFY_ARE_EQUAL(segments.Size(), 1u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); - VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); + _verifySegment(segments, 0, 0, 14); // one segment for the entire string } { Log::Comment(L"Testing command name segmentation with filter with first character matching"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"A"))); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 2u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A"); - VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAABBBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_ARE_EQUAL(segments.Size(), 1u); // only one bold segment + _verifySegment(segments, 0, 0, 0); // it only covers the first character } { Log::Comment(L"Testing command name segmentation with filter with other case"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"a"))); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 2u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"A"); - VERIFY_IS_TRUE(segments.GetAt(0).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AAAAABBBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(1).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_ARE_EQUAL(segments.Size(), 1u); // only one bold segment + _verifySegment(segments, 0, 0, 0); // it only covers the first character } { Log::Comment(L"Testing command name segmentation with filter matching several characters"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"ab"))); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 3u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAA"); - VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(1).TextSegment(), L"AB"); - VERIFY_IS_TRUE(segments.GetAt(1).IsHighlighted()); - VERIFY_ARE_EQUAL(segments.GetAt(2).TextSegment(), L"BBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(2).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_ARE_EQUAL(segments.Size(), 1u); // one bold segment + _verifySegment(segments, 0, 5, 6); // middle 'ab' + } + { + Log::Comment(L"Testing command name segmentation with filter matching several regions"); + filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"abcc"))); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_ARE_EQUAL(segments.Size(), 2u); // two bold segments + _verifySegment(segments, 0, 5, 6); // middle 'ab' + _verifySegment(segments, 1, 12, 13); // start of 'cc' } { Log::Comment(L"Testing command name segmentation with non matching filter"); filteredCommand->UpdateFilter(std::make_shared(fzf::matcher::ParsePattern(L"abcd"))); - auto segments = filteredCommand->HighlightedName().Segments(); - VERIFY_ARE_EQUAL(segments.Size(), 1u); - VERIFY_ARE_EQUAL(segments.GetAt(0).TextSegment(), L"AAAAAABBBBBBCCC"); - VERIFY_IS_FALSE(segments.GetAt(0).IsHighlighted()); + const auto segments = filteredCommand->NameHighlights(); + + VERIFY_IS_NULL(segments); // No matches = no segments } }); diff --git a/src/cascadia/TerminalApp/App.xaml b/src/cascadia/TerminalApp/App.xaml index cc0ce1517d..4210b65b0a 100644 --- a/src/cascadia/TerminalApp/App.xaml +++ b/src/cascadia/TerminalApp/App.xaml @@ -243,6 +243,8 @@ + + diff --git a/src/cascadia/TerminalApp/CommandPalette.xaml b/src/cascadia/TerminalApp/CommandPalette.xaml index 7a4b598d5b..b646c2e4a3 100644 --- a/src/cascadia/TerminalApp/CommandPalette.xaml +++ b/src/cascadia/TerminalApp/CommandPalette.xaml @@ -73,7 +73,8 @@ + HighlightedRuns="{x:Bind NameHighlights, Mode=OneWay}" + Text="{x:Bind Item.Name, Mode=OneWay}" /> + + + + + diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.xaml b/src/cascadia/TerminalApp/SnippetsPaneContent.xaml index b47187f5e6..b6d054ae52 100644 --- a/src/cascadia/TerminalApp/SnippetsPaneContent.xaml +++ b/src/cascadia/TerminalApp/SnippetsPaneContent.xaml @@ -170,8 +170,9 @@ + Text="{x:Bind FilteredCommand.Item.Name, Mode=OneWay}" /> - - + @@ -99,11 +98,10 @@ PaletteItemTemplateSelector.idl Code - + TabBase.idl - TaskbarState.idl @@ -192,11 +190,10 @@ - - + @@ -209,12 +206,10 @@ PaletteItemTemplateSelector.idl Code - TabBase.idl - TaskbarState.idl @@ -319,8 +314,6 @@ - - AboutDialog.xaml @@ -331,7 +324,7 @@ Designer - + @@ -341,7 +334,6 @@ Code - diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters index 5f67df4bdf..52b5113bdb 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters @@ -26,16 +26,7 @@ commandPalette - - commandPalette - - - commandPalette - - - commandPalette - - + commandPalette @@ -62,16 +53,10 @@ commandPalette - + commandPalette - - commandPalette - - - commandPalette - - + commandPalette @@ -135,16 +120,7 @@ commandPalette - - commandPalette - - - commandPalette - - - commandPalette - - + commandPalette From 5b63e24c73b22d8c58ccbb77d0ac88f202fadffd Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 16 Jul 2025 17:10:06 -0500 Subject: [PATCH 41/74] ci: turn on SARIF reporting unconditionally for check-spelling (#19135) The logic currently present occasionally fails to enable reporting for some PRs. --- .github/workflows/spelling2.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spelling2.yml b/.github/workflows/spelling2.yml index f01ac1eea5..9c9cb9ea85 100644 --- a/.github/workflows/spelling2.yml +++ b/.github/workflows/spelling2.yml @@ -104,7 +104,7 @@ jobs: report-timing: 1 warnings: bad-regex,binary-file,deprecated-feature,ignored-expect-variant,large-file,limited-references,no-newline-at-eof,noisy-file,non-alpha-in-dictionary,token-is-substring,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,no-files-to-check,unclosed-block-ignore-begin,unclosed-block-ignore-end experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} - use_sarif: ${{ (!github.event.pull_request || (github.event.pull_request.head.repo.full_name == github.repository)) && 1 }} + use_sarif: 1 check_extra_dictionaries: "" dictionary_source_prefixes: > { From 7b841628dfd332dbd6e69a44dc1b85ec17ca5837 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 16 Jul 2025 21:13:12 -0500 Subject: [PATCH 42/74] Re-enable web-source icons in Stable and Preview builds (#19137) Disables a controversial part of #19044. Refs #19075 --- .../TerminalSettingsModel/CascadiaSettings.cpp | 11 ++++++++--- src/features.xml | 11 +++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index 9dc2c156f2..1528b97a4e 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -499,9 +499,14 @@ static bool _validateSingleMediaResource(std::wstring_view resource) return false; } - const auto scheme{ resourceUri.SchemeName() }; - // Only file: URIs and ms-* URIs are permissible. http, https, ftp, gopher, etc. are not. - return til::equals_insensitive_ascii(scheme, L"file") || til::starts_with_insensitive_ascii(scheme, L"ms-"); + if constexpr (Feature_DisableWebSourceIcons::IsEnabled()) + { + const auto scheme{ resourceUri.SchemeName() }; + // Only file: URIs and ms-* URIs are permissible. http, https, ftp, gopher, etc. are not. + return til::equals_insensitive_ascii(scheme, L"file") || til::starts_with_insensitive_ascii(scheme, L"ms-"); + } + + return true; } catch (...) { diff --git a/src/features.xml b/src/features.xml index 894875ecd5..d5c7942104 100644 --- a/src/features.xml +++ b/src/features.xml @@ -202,4 +202,15 @@
+ + Feature_DisableWebSourceIcons + Disables icon paths that make web requests + 19075 + AlwaysDisabled + + Dev + Canary + + + From a04e410a39563b84aecbf7d71b57ee63fea1fed7 Mon Sep 17 00:00:00 2001 From: Paulina Kalicka <71526180+paulinek13@users.noreply.github.com> Date: Fri, 18 Jul 2025 00:09:52 +0200 Subject: [PATCH 43/74] feat: add option to enable zoom with `ctrl + scroll` (#19127) This PR adds a new global setting `scrollToZoom` that allows users to enable font zooming with scrolling. When disabled, **this setting prevents accidental font size changes** that can occur when users scroll while holding the Ctrl key. Note: after disabling this setting, users may still change font size using `Ctrl+` and `Ctrl-` keyboard shortcuts. Other Ctrl+Scroll functionality (like transparency adjustments) remains unaffected. ## Validation Steps Performed - Verified the setting can be toggled in the Settings UI (Interaction tab) - Confirmed that when disabled, holding Ctrl and scrolling no longer changes font size - Validated that the setting persists across terminal restarts --- Note: I used the existing `FocusFollowMouse` setting as a reference for implementing this. Closes #11710 Closes #3793 Closes #11906 Closes #3990 --- doc/cascadia/profiles.schema.json | 5 +++++ src/cascadia/TerminalControl/ControlInteractivity.cpp | 2 +- src/cascadia/TerminalControl/IControlSettings.idl | 1 + src/cascadia/TerminalSettingsEditor/Interaction.xaml | 6 ++++++ src/cascadia/TerminalSettingsEditor/InteractionViewModel.h | 1 + .../TerminalSettingsEditor/InteractionViewModel.idl | 1 + .../TerminalSettingsEditor/Resources/en-US/Resources.resw | 4 ++++ src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl | 1 + src/cascadia/TerminalSettingsModel/MTSMSettings.h | 1 + src/cascadia/TerminalSettingsModel/TerminalSettings.cpp | 1 + src/cascadia/TerminalSettingsModel/TerminalSettings.h | 1 + src/cascadia/inc/ControlProperties.h | 1 + 12 files changed, 24 insertions(+), 1 deletion(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index a5011bb980..10f5a8b87f 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2376,6 +2376,11 @@ "description": "When set to true, the terminal will focus the pane on mouse hover.", "type": "boolean" }, + "experimental.scrollToZoom": { + "default": true, + "description": "When set to true, holding the Ctrl key while scrolling will increase or decrease the terminal font size.", + "type": "boolean" + }, "compatibility.allowHeadless": { "default": false, "description": "When set to true, Windows Terminal will run in the background. This allows globalSummon and quakeMode actions to work even when no windows are open.", diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index f289912145..ba740a4364 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -519,7 +519,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _mouseTransparencyHandler(delta); } - else if (ctrlPressed) + else if (ctrlPressed && _core->Settings().ScrollToZoom()) { _mouseZoomHandler(delta); } diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index ad621fdff8..2bd4bfc854 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -60,6 +60,7 @@ namespace Microsoft.Terminal.Control Boolean CopyOnSelect { get; }; Microsoft.Terminal.Control.CopyFormat CopyFormatting { get; }; Boolean FocusFollowMouse { get; }; + Boolean ScrollToZoom { get; }; String Commandline { get; }; String StartingDirectory { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.xaml b/src/cascadia/TerminalSettingsEditor/Interaction.xaml index 10d45c7899..eba8b8129e 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.xaml +++ b/src/cascadia/TerminalSettingsEditor/Interaction.xaml @@ -83,6 +83,12 @@ Style="{StaticResource ToggleSwitchInExpanderStyle}" /> + + + + + Automatically focus pane on mouse hover Header for a control to toggle the "focus follow mouse" setting. When enabled, hovering over a pane puts it in focus. + + Adjust terminal font size by scrolling while holding the Ctrl key + Header for a control to toggle font size changes with scrolling. When enabled, holding the Ctrl key while scrolling will increase or decrease the terminal font size. + Pane animations Header for a control to toggle animations on panes. "Enabled" value enables the animations. diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 9bf5267733..5a99e05bc3 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -90,6 +90,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(Boolean, DisableAnimations); INHERITABLE_SETTING(String, StartupActions); INHERITABLE_SETTING(Boolean, FocusFollowMouse); + INHERITABLE_SETTING(Boolean, ScrollToZoom); INHERITABLE_SETTING(WindowingMode, WindowingBehavior); INHERITABLE_SETTING(Boolean, TrimBlockSelection); INHERITABLE_SETTING(Boolean, DetectURLs); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 01bd5ce5c8..725cddbb68 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -24,6 +24,7 @@ Author(s): X(hstring, WordDelimiters, "wordDelimiters", DEFAULT_WORD_DELIMITERS) \ X(bool, CopyOnSelect, "copyOnSelect", false) \ X(bool, FocusFollowMouse, "focusFollowMouse", false) \ + X(bool, ScrollToZoom, "experimental.scrollToZoom", true) \ X(winrt::Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI, "rendering.graphicsAPI") \ X(bool, DisablePartialInvalidation, "rendering.disablePartialInvalidation", false) \ X(bool, SoftwareRendering, "rendering.software", false) \ diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 9d3540b145..5b34afbc5a 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -367,6 +367,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _CopyOnSelect = globalSettings.CopyOnSelect(); _CopyFormatting = globalSettings.CopyFormatting(); _FocusFollowMouse = globalSettings.FocusFollowMouse(); + _ScrollToZoom = globalSettings.ScrollToZoom(); _GraphicsAPI = globalSettings.GraphicsAPI(); _DisablePartialInvalidation = globalSettings.DisablePartialInvalidation(); _SoftwareRendering = globalSettings.SoftwareRendering(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 9591ad3487..b9f4af3d30 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -94,6 +94,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, bool, CopyOnSelect, false); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0); INHERITABLE_SETTING(Model::TerminalSettings, bool, FocusFollowMouse, false); + INHERITABLE_SETTING(Model::TerminalSettings, bool, ScrollToZoom, true); INHERITABLE_SETTING(Model::TerminalSettings, bool, AllowVtChecksumReport, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, TrimBlockSelection, true); INHERITABLE_SETTING(Model::TerminalSettings, bool, DetectURLs, true); diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index 107773da06..ab6fa1fe01 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -42,6 +42,7 @@ X(winrt::hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS) \ X(bool, CopyOnSelect, false) \ X(bool, FocusFollowMouse, false) \ + X(bool, ScrollToZoom, true) \ X(winrt::Windows::Foundation::IReference, TabColor, nullptr) \ X(winrt::Windows::Foundation::IReference, StartingTabColor, nullptr) \ X(bool, TrimBlockSelection, true) \ From fedf7b3b6c190c7b11819da2a2b5892cc54db961 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 18 Jul 2025 19:09:12 -0500 Subject: [PATCH 44/74] Undo the damage done to FilteredCommand in #17330 (#19148) PR #17330 changed FilteredCommand so that FilteredTask could derive from it. It also **did not** implement FilteredTask as a derived class. We've been carrying around the debt of a decision we un-decided for like a year. --- src/cascadia/TerminalApp/FilteredCommand.cpp | 20 +++----------------- src/cascadia/TerminalApp/FilteredCommand.h | 5 +---- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/src/cascadia/TerminalApp/FilteredCommand.cpp b/src/cascadia/TerminalApp/FilteredCommand.cpp index 794476cdfb..d8b23abdfb 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.cpp +++ b/src/cascadia/TerminalApp/FilteredCommand.cpp @@ -21,25 +21,11 @@ namespace winrt::TerminalApp::implementation { // This class is a wrapper of IPaletteItem, that is used as an item of a filterable list in CommandPalette. // It manages a highlighted text that is computed by matching search filter characters to item name - FilteredCommand::FilteredCommand(const winrt::TerminalApp::IPaletteItem& item) + FilteredCommand::FilteredCommand(const winrt::TerminalApp::IPaletteItem& item) : + _Item{ item }, _Weight{ 0 } { - // Actually implement the ctor in _constructFilteredCommand - _constructFilteredCommand(item); - } - - // We need to actually implement the ctor in a separate helper. This is - // because we have a FilteredTask class which derives from FilteredCommand. - // HOWEVER, for cppwinrt ~ r e a s o n s ~, it doesn't actually derive from - // FilteredCommand directly, so we can't just use the FilteredCommand ctor - // directly in the base class. - void FilteredCommand::_constructFilteredCommand(const winrt::TerminalApp::IPaletteItem& item) - { - _Item = item; - _Weight = 0; - - _update(); - // Recompute the highlighted name if the item name changes + // Our Item will not change, so we don't need to update the revoker if it does. _itemChangedRevoker = _Item.as().PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](auto& /*sender*/, auto& e) { auto filteredCommand{ weakThis.get() }; if (filteredCommand && e.PropertyName() == L"Name") diff --git a/src/cascadia/TerminalApp/FilteredCommand.h b/src/cascadia/TerminalApp/FilteredCommand.h index e13aa476ad..e91995d9e0 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.h +++ b/src/cascadia/TerminalApp/FilteredCommand.h @@ -20,7 +20,7 @@ namespace winrt::TerminalApp::implementation FilteredCommand() = default; FilteredCommand(const winrt::TerminalApp::IPaletteItem& item); - virtual void UpdateFilter(std::shared_ptr pattern); + void UpdateFilter(std::shared_ptr pattern); static int Compare(const winrt::TerminalApp::FilteredCommand& first, const winrt::TerminalApp::FilteredCommand& second); @@ -29,9 +29,6 @@ namespace winrt::TerminalApp::implementation WINRT_OBSERVABLE_PROPERTY(winrt::Windows::Foundation::Collections::IVector, NameHighlights, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(int, Weight, PropertyChanged.raise); - protected: - void _constructFilteredCommand(const winrt::TerminalApp::IPaletteItem& item); - private: std::shared_ptr _pattern; void _update(); From 3979e82c2bc31adbac8057eba59e7474ba434554 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 21 Jul 2025 17:11:26 -0500 Subject: [PATCH 45/74] Remove IconElement caching from PaletteItem (#19149) In #19132, we introduced caching for BasePaletteItem::ResolvedIcon as a late optimization. We actually can't cache those, because they're UI elements, with parents and relationships and all. When you filter a list with icons and the list elements change physical position, they're assigned new templates-- and new parents. Fixes e7939bb4e Co-authored-by: Eric Nelson --- src/cascadia/TerminalApp/BasePaletteItem.h | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/cascadia/TerminalApp/BasePaletteItem.h b/src/cascadia/TerminalApp/BasePaletteItem.h index 1a74253759..c4ee9ce301 100644 --- a/src/cascadia/TerminalApp/BasePaletteItem.h +++ b/src/cascadia/TerminalApp/BasePaletteItem.h @@ -14,14 +14,14 @@ namespace winrt::TerminalApp::implementation Windows::UI::Xaml::Controls::IconElement ResolvedIcon() { const auto icon{ static_cast(this)->Icon() }; - if (!_resolvedIcon && !icon.empty()) + if (!icon.empty()) { const auto resolvedIcon{ Microsoft::Terminal::UI::IconPathConverter::IconWUX(icon) }; resolvedIcon.Width(16); resolvedIcon.Height(16); - _resolvedIcon = resolvedIcon; + return resolvedIcon; } - return _resolvedIcon; + return nullptr; } til::property_changed_event PropertyChanged; @@ -34,11 +34,7 @@ namespace winrt::TerminalApp::implementation void InvalidateResolvedIcon() { - _resolvedIcon = nullptr; BaseRaisePropertyChanged(L"ResolvedIcon"); } - - private: - Windows::UI::Xaml::Controls::IconElement _resolvedIcon{ nullptr }; }; } From 452fa87937f0335bb2f34036168d9f66ca6d7ca2 Mon Sep 17 00:00:00 2001 From: Paulina Kalicka <71526180+paulinek13@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:24:46 +0200 Subject: [PATCH 46/74] feat: add option to adjust opacity with Ctrl+Shift+scroll (#19151) ## Summary of the Pull Request This PR introduces an experimental setting that allows to toggle opacity changes with scrolling. ## References and Relevant Issues #3793 ## Detailed Description of the Pull Request / Additional comments By default, holding Ctrl + Shift while scrolling changes the terminal's opacity. This PR adds an option to disable that behavior. ## Validation Steps Performed I built the project locally and verified that the new feature works as intended. ## PR Checklist - [x] Resolves https://github.com/microsoft/terminal/issues/3793#issuecomment-3085684640 - [x] Tests ~~added/~~ passed - [x] Documentation updated - If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: https://github.com/MicrosoftDocs/terminal/pull/873 - [X] Schema updated (if necessary) --- doc/cascadia/profiles.schema.json | 5 +++++ src/cascadia/TerminalControl/ControlInteractivity.cpp | 4 ++-- src/cascadia/TerminalControl/IControlSettings.idl | 1 + src/cascadia/TerminalSettingsEditor/Interaction.xaml | 6 ++++++ src/cascadia/TerminalSettingsEditor/InteractionViewModel.h | 1 + .../TerminalSettingsEditor/InteractionViewModel.idl | 1 + .../TerminalSettingsEditor/Resources/en-US/Resources.resw | 4 ++++ src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl | 1 + src/cascadia/TerminalSettingsModel/MTSMSettings.h | 1 + src/cascadia/TerminalSettingsModel/TerminalSettings.cpp | 1 + src/cascadia/TerminalSettingsModel/TerminalSettings.h | 1 + src/cascadia/inc/ControlProperties.h | 1 + 12 files changed, 25 insertions(+), 2 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 10f5a8b87f..275db4fa37 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2381,6 +2381,11 @@ "description": "When set to true, holding the Ctrl key while scrolling will increase or decrease the terminal font size.", "type": "boolean" }, + "experimental.scrollToChangeOpacity": { + "default": true, + "description": "When set to true, holding the Ctrl and Shift keys while scrolling will change the window opacity.", + "type": "boolean" + }, "compatibility.allowHeadless": { "default": false, "description": "When set to true, Windows Terminal will run in the background. This allows globalSummon and quakeMode actions to work even when no windows are open.", diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index ba740a4364..d9bc37571b 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -515,11 +515,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto ctrlPressed = modifiers.IsCtrlPressed(); const auto shiftPressed = modifiers.IsShiftPressed(); - if (ctrlPressed && shiftPressed) + if (ctrlPressed && shiftPressed && _core->Settings().ScrollToChangeOpacity()) { _mouseTransparencyHandler(delta); } - else if (ctrlPressed && _core->Settings().ScrollToZoom()) + else if (ctrlPressed && !shiftPressed && _core->Settings().ScrollToZoom()) { _mouseZoomHandler(delta); } diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 2bd4bfc854..c273574ed7 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -61,6 +61,7 @@ namespace Microsoft.Terminal.Control Microsoft.Terminal.Control.CopyFormat CopyFormatting { get; }; Boolean FocusFollowMouse { get; }; Boolean ScrollToZoom { get; }; + Boolean ScrollToChangeOpacity { get; }; String Commandline { get; }; String StartingDirectory { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.xaml b/src/cascadia/TerminalSettingsEditor/Interaction.xaml index eba8b8129e..84a5019ea6 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.xaml +++ b/src/cascadia/TerminalSettingsEditor/Interaction.xaml @@ -89,6 +89,12 @@ Style="{StaticResource ToggleSwitchInExpanderStyle}" /> + + + + + Adjust terminal font size by scrolling while holding the Ctrl key Header for a control to toggle font size changes with scrolling. When enabled, holding the Ctrl key while scrolling will increase or decrease the terminal font size. + + Adjust terminal opacity by scrolling while holding the Ctrl and Shift keys + Header for a control to toggle opacity changes with scrolling. When enabled, holding the Ctrl and Shift keys while scrolling will increase or decrease the window opacity. + Pane animations Header for a control to toggle animations on panes. "Enabled" value enables the animations. diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 5a99e05bc3..7e6c1180e4 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -91,6 +91,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(String, StartupActions); INHERITABLE_SETTING(Boolean, FocusFollowMouse); INHERITABLE_SETTING(Boolean, ScrollToZoom); + INHERITABLE_SETTING(Boolean, ScrollToChangeOpacity); INHERITABLE_SETTING(WindowingMode, WindowingBehavior); INHERITABLE_SETTING(Boolean, TrimBlockSelection); INHERITABLE_SETTING(Boolean, DetectURLs); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 725cddbb68..5fa1fdd59c 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -25,6 +25,7 @@ Author(s): X(bool, CopyOnSelect, "copyOnSelect", false) \ X(bool, FocusFollowMouse, "focusFollowMouse", false) \ X(bool, ScrollToZoom, "experimental.scrollToZoom", true) \ + X(bool, ScrollToChangeOpacity, "experimental.scrollToChangeOpacity", true) \ X(winrt::Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI, "rendering.graphicsAPI") \ X(bool, DisablePartialInvalidation, "rendering.disablePartialInvalidation", false) \ X(bool, SoftwareRendering, "rendering.software", false) \ diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 5b34afbc5a..5e7e5ec29f 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -368,6 +368,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _CopyFormatting = globalSettings.CopyFormatting(); _FocusFollowMouse = globalSettings.FocusFollowMouse(); _ScrollToZoom = globalSettings.ScrollToZoom(); + _ScrollToChangeOpacity = globalSettings.ScrollToChangeOpacity(); _GraphicsAPI = globalSettings.GraphicsAPI(); _DisablePartialInvalidation = globalSettings.DisablePartialInvalidation(); _SoftwareRendering = globalSettings.SoftwareRendering(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index b9f4af3d30..a2f20bd4b2 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -95,6 +95,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0); INHERITABLE_SETTING(Model::TerminalSettings, bool, FocusFollowMouse, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, ScrollToZoom, true); + INHERITABLE_SETTING(Model::TerminalSettings, bool, ScrollToChangeOpacity, true); INHERITABLE_SETTING(Model::TerminalSettings, bool, AllowVtChecksumReport, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, TrimBlockSelection, true); INHERITABLE_SETTING(Model::TerminalSettings, bool, DetectURLs, true); diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index ab6fa1fe01..d4e12a94b4 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -43,6 +43,7 @@ X(bool, CopyOnSelect, false) \ X(bool, FocusFollowMouse, false) \ X(bool, ScrollToZoom, true) \ + X(bool, ScrollToChangeOpacity, true) \ X(winrt::Windows::Foundation::IReference, TabColor, nullptr) \ X(winrt::Windows::Foundation::IReference, StartingTabColor, nullptr) \ X(bool, TrimBlockSelection, true) \ From 8c20d2052df20a6cb40b2f6d0e0b3d1dbbfb529b Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 21 Jul 2025 15:33:52 -0700 Subject: [PATCH 47/74] Add telemetry for new tab menu traffic (#19142) ## Summary of the Pull Request Adds new telemetry events to track traffic through the new tab menu. Specifically, the following events are added: - `NewTabMenuDefaultButtonClicked`: Event emitted when the default button from the new tab split button is invoked - `NewTabMenuOpened`: Event emitted when the new tab menu is opened - `NewTabMenuClosed`: Event emitted when the new tab menu is closed - `NewTabMenuItemClicked`: Event emitted when an item from the new tab menu is invoked - Has an `ItemType` parameter that can be set to `Settings`, `CommandPalette`, `About, `Profile`, `Action` - Has a `TabCount` parameter that keeps tracked of the number of tabs in the window before changing the state - `NewTabMenuCreatedNewTerminalSession`: Event emitted when a new terminal was created via the new tab menu - Has a `SessionType` parameter that can be set to `ElevatedWindow`, `Window`, `Pane`, `Tab` - Instead of `TabCount`, has a `NewTabCount` that keeps track of the _new_ number of tabs after the session has been created - `NewTabMenuItemElevateSubmenuItemClicked`: Event emitted when the elevate submenu item from the new tab menu is invoked ## Validation Steps Performed Used TVPP to see events generated from interacting with the new tab menu. --- src/cascadia/TerminalApp/TerminalPage.cpp | 122 +++++++++++++++++++++- 1 file changed, 117 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 1de587c26f..87ccb19a32 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -237,6 +237,14 @@ namespace winrt::TerminalApp::implementation _newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) { if (auto page{ weakThis.get() }) { + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuDefaultButtonClicked", + TraceLoggingDescription("Event emitted when the default button from the new tab split button is invoked"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + page->_OpenNewTerminalViaDropdown(NewTerminalArgs()); } }); @@ -834,14 +842,36 @@ namespace winrt::TerminalApp::implementation // Since the previous focus location might be discarded in the background, // e.g., the command palette will be dismissed by the menu, // and then closing the fly-out will move the focus to wrong location. - newTabFlyout.Opening([this](auto&&, auto&&) { - _FocusCurrentTab(true); + newTabFlyout.Opening([weakThis{ get_weak() }](auto&&, auto&&) { + if (auto page{ weakThis.get() }) + { + page->_FocusCurrentTab(true); + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuOpened", + TraceLoggingDescription("Event emitted when the new tab menu is opened"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The Count of tabs currently opened in this window"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + } }); // Necessary for fly-out sub items to get focus on a tab before collapsing. Related to #15049 - newTabFlyout.Closing([this](auto&&, auto&&) { - if (!_commandPaletteIs(Visibility::Visible)) + newTabFlyout.Closing([weakThis{ get_weak() }](auto&&, auto&&) { + if (auto page{ weakThis.get() }) { - _FocusCurrentTab(true); + if (!page->_commandPaletteIs(Visibility::Visible)) + { + page->_FocusCurrentTab(true); + } + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuClosed", + TraceLoggingDescription("Event emitted when the new tab menu is closed"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The Count of tabs currently opened in this window"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } }); _newTabButton.Flyout(newTabFlyout); @@ -1047,6 +1077,15 @@ namespace winrt::TerminalApp::implementation profileMenuItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) { if (auto page{ weakThis.get() }) { + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemClicked", + TraceLoggingDescription("Event emitted when an item from the new tab menu is invoked"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue("Profile", "ItemType", "The type of item that was clicked in the new tab menu"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + NewTerminalArgs newTerminalArgs{ profileIndex }; page->_OpenNewTerminalViaDropdown(newTerminalArgs); } @@ -1093,6 +1132,15 @@ namespace winrt::TerminalApp::implementation actionMenuItem.Click([action, weakThis{ get_weak() }](auto&&, auto&&) { if (auto page{ weakThis.get() }) { + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemClicked", + TraceLoggingDescription("Event emitted when an item from the new tab menu is invoked"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue("Action", "ItemType", "The type of item that was clicked in the new tab menu"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + page->_actionDispatch->DoAction(action.ActionAndArgs()); } }); @@ -1154,6 +1202,7 @@ namespace winrt::TerminalApp::implementation const auto dispatchToElevatedWindow = ctrlPressed && !IsRunningElevated(); + auto sessionType = ""; if ((shiftPressed || dispatchToElevatedWindow) && !debugTap) { // Manually fill in the evaluated profile. @@ -1171,10 +1220,12 @@ namespace winrt::TerminalApp::implementation if (dispatchToElevatedWindow) { _OpenElevatedWT(newTerminalArgs); + sessionType = "ElevatedWindow"; } else { _OpenNewWindow(newTerminalArgs); + sessionType = "Window"; } } else @@ -1193,12 +1244,23 @@ namespace winrt::TerminalApp::implementation SplitDirection::Automatic, 0.5f, newPane); + sessionType = "Pane"; } else { _CreateNewTabFromPane(newPane); + sessionType = "Tab"; } } + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuCreatedNewTerminalSession", + TraceLoggingDescription("Event emitted when a new terminal was created via the new tab menu"), + TraceLoggingValue(NumberOfTabs(), "NewTabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue(sessionType, "SessionType", "The type of session that was created"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } std::wstring TerminalPage::_evaluatePathForCwd(const std::wstring_view path) @@ -1405,6 +1467,30 @@ namespace winrt::TerminalApp::implementation { target = SettingsTarget::DefaultsFile; } + + const auto targetAsString = [&target]() { + switch (target) + { + case SettingsTarget::SettingsFile: + return "SettingsFile"; + case SettingsTarget::DefaultsFile: + return "DefaultsFile"; + case SettingsTarget::SettingsUI: + default: + return "UI"; + } + }(); + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemClicked", + TraceLoggingDescription("Event emitted when an item from the new tab menu is invoked"), + TraceLoggingValue(NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue("Settings", "ItemType", "The type of item that was clicked in the new tab menu"), + TraceLoggingValue(targetAsString, "SettingsTarget", "The target settings file or UI"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + _LaunchSettings(target); } @@ -1416,6 +1502,15 @@ namespace winrt::TerminalApp::implementation auto p = LoadCommandPalette(); p.EnableCommandPaletteMode(CommandPaletteLaunchMode::Action); p.Visibility(Visibility::Visible); + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemClicked", + TraceLoggingDescription("Event emitted when an item from the new tab menu is invoked"), + TraceLoggingValue(NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue("CommandPalette", "ItemType", "The type of item that was clicked in the new tab menu"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } // Method Description: @@ -1428,6 +1523,15 @@ namespace winrt::TerminalApp::implementation const RoutedEventArgs&) { _ShowAboutDialog(); + + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemClicked", + TraceLoggingDescription("Event emitted when an item from the new tab menu is invoked"), + TraceLoggingValue(NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingValue("About", "ItemType", "The type of item that was clicked in the new tab menu"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } // Method Description: @@ -5365,6 +5469,14 @@ namespace winrt::TerminalApp::implementation runAsAdminItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) { if (auto page{ weakThis.get() }) { + TraceLoggingWrite( + g_hTerminalAppProvider, + "NewTabMenuItemElevateSubmenuItemClicked", + TraceLoggingDescription("Event emitted when the elevate submenu item from the new tab menu is invoked"), + TraceLoggingValue(page->NumberOfTabs(), "TabCount", "The count of tabs currently opened in this window"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + NewTerminalArgs args{ profileIndex }; args.Elevate(true); page->_OpenNewTerminalViaDropdown(args); From cb0289fff263924b0564a7bf6e876765965a2d37 Mon Sep 17 00:00:00 2001 From: HO-COOH Date: Tue, 22 Jul 2025 06:35:09 +0800 Subject: [PATCH 48/74] Center text in the Default Terminal dropdown (#19072) This is a small UI fix so that we center the default terminal application ComboBox text. --- src/cascadia/TerminalSettingsEditor/Launch.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index 44feee7016..cf9967882a 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -116,6 +116,7 @@ From f22295ef2c230f79340763b91a57f3f6c6739a24 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 22 Jul 2025 10:04:19 -0500 Subject: [PATCH 49/74] Merge TabBase+TerminalTab into just Tab (#19136) This removes the need to construct two objects per tab (TabBase, Actual Tab) and the other overhead inherent in WinRT composition-based inheritance. Important renames: - `GetTerminalTabImpl` -> `GetTabImpl` This pull request does not rename `TerminalTabStatus`; that is left as work for a future PR. Closes #17529 --- .../LocalTests_TerminalApp/TabTests.cpp | 54 +- .../TerminalApp/AppActionHandlers.cpp | 31 +- src/cascadia/TerminalApp/CommandPalette.cpp | 4 +- src/cascadia/TerminalApp/CommandPalette.h | 6 +- src/cascadia/TerminalApp/CommandPalette.idl | 6 +- .../TerminalApp/CommandPaletteItems.cpp | 10 +- .../TerminalApp/CommandPaletteItems.h | 11 +- src/cascadia/TerminalApp/Pane.cpp | 2 +- src/cascadia/TerminalApp/Pane.h | 4 +- .../TerminalApp/SuggestionsControl.idl | 1 - .../TerminalApp/{TerminalTab.cpp => Tab.cpp} | 853 ++++++++++++++++-- .../TerminalApp/{TerminalTab.h => Tab.h} | 88 +- .../TerminalApp/{TabBase.idl => Tab.idl} | 6 +- src/cascadia/TerminalApp/TabBase.cpp | 753 ---------------- src/cascadia/TerminalApp/TabBase.h | 92 -- src/cascadia/TerminalApp/TabManagement.cpp | 58 +- .../TerminalApp/TerminalAppLib.vcxproj | 17 +- .../TerminalAppLib.vcxproj.filters | 5 +- src/cascadia/TerminalApp/TerminalPage.cpp | 150 ++- src/cascadia/TerminalApp/TerminalPage.h | 66 +- src/cascadia/TerminalApp/TerminalTab.idl | 13 - .../TerminalApp/dll/TerminalApp.vcxproj | 1 - 22 files changed, 1034 insertions(+), 1197 deletions(-) rename src/cascadia/TerminalApp/{TerminalTab.cpp => Tab.cpp} (64%) rename src/cascadia/TerminalApp/{TerminalTab.h => Tab.h} (67%) rename src/cascadia/TerminalApp/{TabBase.idl => Tab.idl} (78%) delete mode 100644 src/cascadia/TerminalApp/TabBase.cpp delete mode 100644 src/cascadia/TerminalApp/TabBase.h delete mode 100644 src/cascadia/TerminalApp/TerminalTab.idl diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index 941d5fa06f..4e1744c66c 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -8,7 +8,7 @@ #include "../TerminalApp/MinMaxCloseControl.h" #include "../TerminalApp/TabRowControl.h" #include "../TerminalApp/ShortcutActionDispatch.h" -#include "../TerminalApp/TerminalTab.h" +#include "../TerminalApp/Tab.h" #include "../TerminalApp/CommandPalette.h" #include "../TerminalApp/ContentManager.h" #include "CppWinrtTailored.h" @@ -307,7 +307,7 @@ namespace TerminalAppLocalTests // reliably in the unit tests. Log::Comment(L"Ensure we set the first tab as the selected one."); auto tab = page->_tabs.GetAt(0); - auto tabImpl = page->_GetTerminalTabImpl(tab); + auto tabImpl = page->_GetTabImpl(tab); page->_tabView.SelectedItem(tabImpl->TabViewItem()); page->_UpdatedSelectedTab(tab); }); @@ -510,7 +510,7 @@ namespace TerminalAppLocalTests result = RunOnUIThread([&page]() { VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(1, tab->GetLeafPaneCount()); }); VERIFY_SUCCEEDED(result); @@ -520,7 +520,7 @@ namespace TerminalAppLocalTests page->_SplitPane(nullptr, SplitDirection::Automatic, 0.5f, page->_MakePane(nullptr, page->_GetFocusedTab(), nullptr)); VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, tab->GetLeafPaneCount()); }); VERIFY_SUCCEEDED(result); @@ -538,7 +538,7 @@ namespace TerminalAppLocalTests page->_SplitPane(nullptr, SplitDirection::Automatic, 0.5f, page->_MakePane(nullptr, page->_GetFocusedTab(), nullptr)); VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(3, tab->GetLeafPaneCount(), L"We should successfully duplicate a pane hosting a deleted profile."); @@ -706,7 +706,7 @@ namespace TerminalAppLocalTests SplitPaneArgs args{ SplitType::Duplicate }; ActionEventArgs eventArgs{ args }; page->_HandleSplitPane(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); @@ -717,7 +717,7 @@ namespace TerminalAppLocalTests result = RunOnUIThread([&page]() { ActionEventArgs eventArgs{}; page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); @@ -727,7 +727,7 @@ namespace TerminalAppLocalTests result = RunOnUIThread([&page]() { ActionEventArgs eventArgs{}; page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); @@ -744,7 +744,7 @@ namespace TerminalAppLocalTests SplitPaneArgs args{ SplitType::Duplicate }; ActionEventArgs eventArgs{ args }; page->_HandleSplitPane(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); @@ -758,7 +758,7 @@ namespace TerminalAppLocalTests page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); @@ -772,7 +772,7 @@ namespace TerminalAppLocalTests page->_HandleMoveFocus(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); @@ -789,7 +789,7 @@ namespace TerminalAppLocalTests SplitPaneArgs args{ SplitType::Duplicate }; ActionEventArgs eventArgs{ args }; page->_HandleSplitPane(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); @@ -803,7 +803,7 @@ namespace TerminalAppLocalTests page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); @@ -816,7 +816,7 @@ namespace TerminalAppLocalTests page->_HandleClosePane(nullptr, eventArgs); - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); @@ -827,7 +827,7 @@ namespace TerminalAppLocalTests Log::Comment(L"Check to ensure there's only one pane left."); result = RunOnUIThread([&page]() { - auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto firstTab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(1, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); @@ -850,7 +850,7 @@ namespace TerminalAppLocalTests uint32_t firstId = 0, secondId = 0, thirdId = 0, fourthId = 0; TestOnUIThread([&]() { VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); firstId = tab->_activePane->Id().value(); // We start with 1 tab, split vertically to get // ------------------- @@ -876,7 +876,7 @@ namespace TerminalAppLocalTests // | | | // ------------------- page->_SplitPane(nullptr, SplitDirection::Down, 0.5f, page->_MakePane(nullptr, page->_GetFocusedTab(), nullptr)); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); // Split again to make the 3rd tab thirdId = tab->_activePane->Id().value(); }); @@ -896,13 +896,13 @@ namespace TerminalAppLocalTests // | | | // ------------------- page->_SplitPane(nullptr, SplitDirection::Down, 0.5f, page->_MakePane(nullptr, page->_GetFocusedTab(), nullptr)); - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); fourthId = tab->_activePane->Id().value(); }); Sleep(250); TestOnUIThread([&]() { - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount()); // just to be complete, make sure we actually have 4 different ids VERIFY_ARE_NOT_EQUAL(firstId, fourthId); @@ -936,7 +936,7 @@ namespace TerminalAppLocalTests Sleep(250); TestOnUIThread([&]() { - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount()); // Our currently focused pane should be `4` VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value()); @@ -967,7 +967,7 @@ namespace TerminalAppLocalTests Sleep(250); TestOnUIThread([&]() { - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount()); // Our currently focused pane should be `4` VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value()); @@ -998,7 +998,7 @@ namespace TerminalAppLocalTests Sleep(250); TestOnUIThread([&]() { - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount()); // Our currently focused pane should be `4` VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value()); @@ -1029,7 +1029,7 @@ namespace TerminalAppLocalTests Sleep(250); TestOnUIThread([&]() { - auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + auto tab = page->_GetTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount()); // Our currently focused pane should be `4` VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value()); @@ -1174,16 +1174,16 @@ namespace TerminalAppLocalTests Log::Comment(L"give alphabetical names to all switch tab actions"); TestOnUIThread([&page]() { - page->_GetTerminalTabImpl(page->_tabs.GetAt(0))->Title(L"a"); + page->_GetTabImpl(page->_tabs.GetAt(0))->Title(L"a"); }); TestOnUIThread([&page]() { - page->_GetTerminalTabImpl(page->_tabs.GetAt(1))->Title(L"b"); + page->_GetTabImpl(page->_tabs.GetAt(1))->Title(L"b"); }); TestOnUIThread([&page]() { - page->_GetTerminalTabImpl(page->_tabs.GetAt(2))->Title(L"c"); + page->_GetTabImpl(page->_tabs.GetAt(2))->Title(L"c"); }); TestOnUIThread([&page]() { - page->_GetTerminalTabImpl(page->_tabs.GetAt(3))->Title(L"d"); + page->_GetTabImpl(page->_tabs.GetAt(3))->Title(L"d"); }); TestOnUIThread([&page]() { diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index afb3e0a6e5..65845ffc6c 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -41,13 +41,13 @@ namespace winrt::TerminalApp::implementation } return _GetActiveControl(); } - winrt::com_ptr TerminalPage::_senderOrFocusedTab(const IInspectable& sender) + winrt::com_ptr TerminalPage::_senderOrFocusedTab(const IInspectable& sender) { if (sender) { - if (auto tab{ sender.try_as() }) + if (auto tab = sender.try_as()) { - return _GetTerminalTabImpl(tab); + return _GetTabImpl(tab); } } return _GetFocusedTabImpl(); @@ -193,17 +193,17 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleCloseOtherPanes(const IInspectable& sender, const ActionEventArgs& args) { - if (const auto& terminalTab{ _senderOrFocusedTab(sender) }) + if (const auto& activeTab{ _senderOrFocusedTab(sender) }) { - const auto activePane = terminalTab->GetActivePane(); - if (terminalTab->GetRootPane() != activePane) + const auto activePane = activeTab->GetActivePane(); + if (activeTab->GetRootPane() != activePane) { _UnZoomIfNeeded(); // Accumulate list of all unfocused leaf panes, ignore read-only panes std::vector unfocusedPaneIds; const auto activePaneId = activePane->Id(); - terminalTab->GetRootPane()->WalkTree([&](auto&& p) { + activeTab->GetRootPane()->WalkTree([&](auto&& p) { const auto id = p->Id(); if (id.has_value() && id != activePaneId && !p->ContainsReadOnly()) { @@ -215,7 +215,7 @@ namespace winrt::TerminalApp::implementation { // Start by removing the panes that were least recently added sort(begin(unfocusedPaneIds), end(unfocusedPaneIds), std::less()); - _ClosePanes(terminalTab->get_weak(), std::move(unfocusedPaneIds)); + _ClosePanes(activeTab->get_weak(), std::move(unfocusedPaneIds)); args.Handled(true); return; } @@ -281,9 +281,9 @@ namespace winrt::TerminalApp::implementation const auto& duplicateFromTab{ realArgs.SplitMode() == SplitType::Duplicate ? _GetFocusedTab() : nullptr }; - const auto& terminalTab{ _senderOrFocusedTab(sender) }; + const auto& activeTab{ _senderOrFocusedTab(sender) }; - _SplitPane(terminalTab, + _SplitPane(activeTab, realArgs.SplitDirection(), // This is safe, we're already filtering so the value is (0, 1) realArgs.SplitSize(), @@ -302,14 +302,14 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleTogglePaneZoom(const IInspectable& sender, const ActionEventArgs& args) { - if (const auto terminalTab{ _senderOrFocusedTab(sender) }) + if (const auto activeTab{ _senderOrFocusedTab(sender) }) { // Don't do anything if there's only one pane. It's already zoomed. - if (terminalTab->GetLeafPaneCount() > 1) + if (activeTab->GetLeafPaneCount() > 1) { // Togging the zoom on the tab will cause the tab to inform us of // the new root Content for this tab. - terminalTab->ToggleZoom(); + activeTab->ToggleZoom(); } } @@ -783,7 +783,7 @@ namespace winrt::TerminalApp::implementation } // Since _RemoveTabs is asynchronous, create a snapshot of the tabs we want to remove - std::vector tabsToRemove; + std::vector tabsToRemove; if (index > 0) { std::copy(begin(_tabs), begin(_tabs) + index, std::back_inserter(tabsToRemove)); @@ -822,7 +822,7 @@ namespace winrt::TerminalApp::implementation } // Since _RemoveTabs is asynchronous, create a snapshot of the tabs we want to remove - std::vector tabsToRemove; + std::vector tabsToRemove; std::copy(begin(_tabs) + index + 1, end(_tabs), std::back_inserter(tabsToRemove)); _RemoveTabs(tabsToRemove); @@ -1559,7 +1559,6 @@ namespace winrt::TerminalApp::implementation activeTab->ToggleBroadcastInput(); args.Handled(true); } - // If the focused tab wasn't a TerminalTab, then leave handled=false } void TerminalPage::_HandleRestartConnection(const IInspectable& sender, diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index 02c368e7e8..6caca24036 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -1072,7 +1072,7 @@ namespace winrt::TerminalApp::implementation // Return Value: // - void CommandPalette::_bindTabs( - const Windows::Foundation::Collections::IObservableVector& source, + const Windows::Foundation::Collections::IObservableVector& source, const Windows::Foundation::Collections::IVector& target) { target.Clear(); @@ -1084,7 +1084,7 @@ namespace winrt::TerminalApp::implementation } } - void CommandPalette::SetTabs(const Collections::IObservableVector& tabs, const Collections::IObservableVector& mruTabs) + void CommandPalette::SetTabs(const Collections::IObservableVector& tabs, const Collections::IObservableVector& mruTabs) { _bindTabs(tabs, _tabActions); _bindTabs(mruTabs, _mruTabActions); diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h index 0da7ee1334..8e621b104d 100644 --- a/src/cascadia/TerminalApp/CommandPalette.h +++ b/src/cascadia/TerminalApp/CommandPalette.h @@ -31,7 +31,7 @@ namespace winrt::TerminalApp::implementation Windows::Foundation::Collections::IObservableVector FilteredActions(); - void SetTabs(const Windows::Foundation::Collections::IObservableVector& tabs, const Windows::Foundation::Collections::IObservableVector& mruTabs); + void SetTabs(const Windows::Foundation::Collections::IObservableVector& tabs, const Windows::Foundation::Collections::IObservableVector& mruTabs); void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap); bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); @@ -48,7 +48,7 @@ namespace winrt::TerminalApp::implementation void EnableTabSearchMode(); til::property_changed_event PropertyChanged; - til::typed_event SwitchToTabRequested; + til::typed_event SwitchToTabRequested; til::typed_event CommandLineExecutionRequested; til::typed_event DispatchCommandRequested; til::typed_event PreviewAction; @@ -135,7 +135,7 @@ namespace winrt::TerminalApp::implementation Microsoft::Terminal::Settings::Model::TabSwitcherMode _tabSwitcherMode; uint32_t _switcherStartIdx; - void _bindTabs(const Windows::Foundation::Collections::IObservableVector& source, const Windows::Foundation::Collections::IVector& target); + void _bindTabs(const Windows::Foundation::Collections::IObservableVector& source, const Windows::Foundation::Collections::IVector& target); void _anchorKeyUpHandler(); winrt::Windows::UI::Xaml::Controls::ListView::SizeChanged_revoker _sizeChangedRevoker; diff --git a/src/cascadia/TerminalApp/CommandPalette.idl b/src/cascadia/TerminalApp/CommandPalette.idl index 48787135a9..79fa26d736 100644 --- a/src/cascadia/TerminalApp/CommandPalette.idl +++ b/src/cascadia/TerminalApp/CommandPalette.idl @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "TabBase.idl"; +import "Tab.idl"; import "HighlightedTextControl.idl"; import "FilteredCommand.idl"; @@ -20,7 +20,7 @@ namespace TerminalApp Windows.Foundation.Collections.IObservableVector FilteredActions { get; }; - void SetTabs(Windows.Foundation.Collections.IObservableVector tabs, Windows.Foundation.Collections.IObservableVector mruTabs); + void SetTabs(Windows.Foundation.Collections.IObservableVector tabs, Windows.Foundation.Collections.IObservableVector mruTabs); void SetActionMap(Microsoft.Terminal.Settings.Model.IActionMapView actionMap); @@ -30,7 +30,7 @@ namespace TerminalApp void EnableTabSwitcherMode(UInt32 startIdx, Microsoft.Terminal.Settings.Model.TabSwitcherMode tabSwitcherMode); void EnableTabSearchMode(); - event Windows.Foundation.TypedEventHandler SwitchToTabRequested; + event Windows.Foundation.TypedEventHandler SwitchToTabRequested; event Windows.Foundation.TypedEventHandler DispatchCommandRequested; event Windows.Foundation.TypedEventHandler CommandLineExecutionRequested; event Windows.Foundation.TypedEventHandler PreviewAction; diff --git a/src/cascadia/TerminalApp/CommandPaletteItems.cpp b/src/cascadia/TerminalApp/CommandPaletteItems.cpp index e23009cff9..e7e66a5164 100644 --- a/src/cascadia/TerminalApp/CommandPaletteItems.cpp +++ b/src/cascadia/TerminalApp/CommandPaletteItems.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT license. #include "pch.h" -#include "TerminalTab.h" +#include "Tab.h" #include @@ -19,11 +19,11 @@ using namespace winrt::Microsoft::Terminal::Settings::Model; namespace winrt::TerminalApp::implementation { - TabPaletteItem::TabPaletteItem(const winrt::TerminalApp::TabBase& tab) : + TabPaletteItem::TabPaletteItem(const winrt::TerminalApp::Tab& tab) : _tab{ tab } { _tabChangedRevoker = tab.PropertyChanged(winrt::auto_revoke, [=](auto& sender, auto& e) { - if (auto senderTab{ sender.try_as() }) + if (auto senderTab{ sender.try_as() }) { auto changedProperty = e.PropertyName(); if (changedProperty == L"Title") @@ -38,10 +38,8 @@ namespace winrt::TerminalApp::implementation } }); - if (const auto terminalTab{ tab.try_as() }) + if (const auto status = tab.TabStatus()) { - const auto status = terminalTab.TabStatus(); - _tabStatusChangedRevoker = status.PropertyChanged(winrt::auto_revoke, [=](auto& /*sender*/, auto& /*e*/) { // Sometimes nested bindings do not get updated, // thus let's notify property changed on TabStatus when one of its properties changes diff --git a/src/cascadia/TerminalApp/CommandPaletteItems.h b/src/cascadia/TerminalApp/CommandPaletteItems.h index e625ac1d76..1912ee37b4 100644 --- a/src/cascadia/TerminalApp/CommandPaletteItems.h +++ b/src/cascadia/TerminalApp/CommandPaletteItems.h @@ -71,9 +71,9 @@ namespace winrt::TerminalApp::implementation public TabPaletteItemT, BasePaletteItem { - TabPaletteItem(const winrt::TerminalApp::TabBase& tab); + TabPaletteItem(const winrt::TerminalApp::Tab& tab); - winrt::TerminalApp::TabBase Tab() const noexcept + winrt::TerminalApp::Tab Tab() const noexcept { return _tab.get(); } @@ -105,16 +105,13 @@ namespace winrt::TerminalApp::implementation { if (auto tab = _tab.get()) { - if (auto terminalTab = tab.try_as()) - { - return terminalTab.TabStatus(); - } + return tab.TabStatus(); } return { nullptr }; } private: - winrt::weak_ref _tab; + winrt::weak_ref _tab; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _tabChangedRevoker; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _tabStatusChangedRevoker; }; diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index d8c7cd47d8..1afe0d118a 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -2429,7 +2429,7 @@ std::optional Pane::Id() noexcept // Method Description: // - Sets this pane's ID -// - Panes are given IDs upon creation by TerminalTab +// - Panes are given IDs upon creation by Tab // Arguments: // - The number to set this pane's ID to void Pane::Id(uint32_t id) noexcept diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 8bce64852d..ecc81fad82 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -31,7 +31,7 @@ namespace TerminalAppLocalTests namespace winrt::TerminalApp::implementation { - struct TerminalTab; + struct Tab; } enum class Borders : int @@ -398,6 +398,6 @@ private: LayoutSizeNode& operator=(const LayoutSizeNode& other); }; - friend struct winrt::TerminalApp::implementation::TerminalTab; + friend struct winrt::TerminalApp::implementation::Tab; friend class ::TerminalAppLocalTests::TabTests; }; diff --git a/src/cascadia/TerminalApp/SuggestionsControl.idl b/src/cascadia/TerminalApp/SuggestionsControl.idl index 0faea43928..e2b00784d9 100644 --- a/src/cascadia/TerminalApp/SuggestionsControl.idl +++ b/src/cascadia/TerminalApp/SuggestionsControl.idl @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "TabBase.idl"; import "HighlightedTextControl.idl"; import "FilteredCommand.idl"; diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/Tab.cpp similarity index 64% rename from src/cascadia/TerminalApp/TerminalTab.cpp rename to src/cascadia/TerminalApp/Tab.cpp index c19730b597..6c305c66d1 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -4,9 +4,9 @@ #include "pch.h" #include #include "ColorPickupFlyout.h" -#include "TerminalTab.h" +#include "Tab.h" #include "SettingsPaneContent.h" -#include "TerminalTab.g.cpp" +#include "Tab.g.cpp" #include "Utils.h" #include "ColorHelper.h" #include "AppLogic.h" @@ -30,7 +30,7 @@ namespace winrt namespace winrt::TerminalApp::implementation { - TerminalTab::TerminalTab(std::shared_ptr rootPane) + Tab::Tab(std::shared_ptr rootPane) { _rootPane = rootPane; _activePane = nullptr; @@ -77,7 +77,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_Setup() + void Tab::_Setup() { _rootClosedToken = _rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) { Closed.raise(nullptr, nullptr); @@ -118,7 +118,7 @@ namespace winrt::TerminalApp::implementation // - Removes the bell indicator from the tab header // Arguments: // - sender, e: not used - void TerminalTab::_BellIndicatorTimerTick(const Windows::Foundation::IInspectable& /*sender*/, const Windows::Foundation::IInspectable& /*e*/) + void Tab::_BellIndicatorTimerTick(const Windows::Foundation::IInspectable& /*sender*/, const Windows::Foundation::IInspectable& /*e*/) { ShowBellIndicator(false); _bellIndicatorTimer.Stop(); @@ -130,9 +130,31 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_MakeTabViewItem() + void Tab::_MakeTabViewItem() { - TabBase::_MakeTabViewItem(); + TabViewItem(::winrt::MUX::Controls::TabViewItem{}); + + // GH#3609 If the tab was tapped, and no one else was around to handle + // it, then ask our parent to toss focus into the active control. + TabViewItem().Tapped([weakThis{ get_weak() }](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + tab->RequestFocusActiveControl.raise(); + } + }); + + // BODGY: When the tab is drag/dropped, the TabView gets a + // TabDragStarting. However, the way it is implemented[^1], the + // TabViewItem needs either an Item or a Content for the event to + // include the correct TabViewItem. Otherwise, it will just return the + // first TabViewItem in the TabView with the same Content as the dragged + // tab (which, if the Content is null, will be the _first_ tab). + // + // So here, we'll stick an empty border in, just so that every tab has a + // Content which is not equal to the others. + // + // [^1]: microsoft-ui-xaml/blob/92fbfcd55f05c92ac65569f5d284c5b36492091e/dev/TabView/TabView.cpp#L751-L758 + TabViewItem().Content(winrt::WUX::Controls::Border{}); TabViewItem().DoubleTapped([weakThis = get_weak()](auto&& /*s*/, auto&& /*e*/) { if (auto tab{ weakThis.get() }) @@ -145,7 +167,7 @@ namespace winrt::TerminalApp::implementation _RecalculateAndApplyTabColor(); } - void TerminalTab::_UpdateHeaderControlMaxWidth() + void Tab::_UpdateHeaderControlMaxWidth() { try { @@ -164,6 +186,73 @@ namespace winrt::TerminalApp::implementation CATCH_LOG() } + void Tab::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch) + { + ASSERT_UI_THREAD(); + + _dispatch = dispatch; + } + + void Tab::SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap) + { + ASSERT_UI_THREAD(); + + _actionMap = actionMap; + _UpdateSwitchToTabKeyChord(); + } + + // Method Description: + // - Sets the key chord resulting in switch to the current tab. + // Updates tool tip if required + // Arguments: + // - keyChord - string representation of the key chord that switches to the current tab + // Return Value: + // - + void Tab::_UpdateSwitchToTabKeyChord() + { + const auto id = fmt::format(FMT_COMPILE(L"Terminal.SwitchToTab{}"), _TabViewIndex); + const auto keyChord{ _actionMap.GetKeyBindingForAction(id) }; + const auto keyChordText = keyChord ? KeyChordSerialization::ToString(keyChord) : L""; + + if (_keyChord == keyChordText) + { + return; + } + + _keyChord = keyChordText; + _UpdateToolTip(); + } + + // Method Description: + // - Sets tab tool tip to a concatenation of title and key chord + // Arguments: + // - + // Return Value: + // - + void Tab::_UpdateToolTip() + { + auto titleRun = WUX::Documents::Run(); + titleRun.Text(_CreateToolTipTitle()); + + auto textBlock = WUX::Controls::TextBlock{}; + textBlock.TextWrapping(WUX::TextWrapping::Wrap); + textBlock.TextAlignment(WUX::TextAlignment::Center); + textBlock.Inlines().Append(titleRun); + + if (!_keyChord.empty()) + { + auto keyChordRun = WUX::Documents::Run(); + keyChordRun.Text(_keyChord); + keyChordRun.FontStyle(winrt::Windows::UI::Text::FontStyle::Italic); + textBlock.Inlines().Append(WUX::Documents::LineBreak{}); + textBlock.Inlines().Append(keyChordRun); + } + + WUX::Controls::ToolTip toolTip{}; + toolTip.Content(textBlock); + WUX::Controls::ToolTipService::SetToolTip(TabViewItem(), toolTip); + } + // Method Description: // - Returns nullptr if no children of this tab were the last control to be // focused, the active control of the current pane, or the last active child control @@ -175,7 +264,7 @@ namespace winrt::TerminalApp::implementation // Return Value: // - nullptr if no children were marked `_lastFocused`, else the TermControl // that was last focused. - TermControl TerminalTab::GetActiveTerminalControl() const + TermControl Tab::GetActiveTerminalControl() const { ASSERT_UI_THREAD(); @@ -186,7 +275,7 @@ namespace winrt::TerminalApp::implementation return nullptr; } - IPaneContent TerminalTab::GetActiveContent() const + IPaneContent Tab::GetActiveContent() const { return _activePane ? _activePane->GetContent() : nullptr; } @@ -198,7 +287,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::Initialize() + void Tab::Initialize() { ASSERT_UI_THREAD(); @@ -219,7 +308,7 @@ namespace winrt::TerminalApp::implementation // - focused: our new focus state // Return Value: // - - void TerminalTab::Focus(WUX::FocusState focusState) + void Tab::Focus(WUX::FocusState focusState) { ASSERT_UI_THREAD(); @@ -253,7 +342,7 @@ namespace winrt::TerminalApp::implementation // Return Value: // - nullopt if no children of this tab were the last control to be // focused, else the GUID of the profile of the last control to be focused - Profile TerminalTab::GetFocusedProfile() const noexcept + Profile Tab::GetFocusedProfile() const noexcept { ASSERT_UI_THREAD(); @@ -266,7 +355,7 @@ namespace winrt::TerminalApp::implementation // of the settings that apply to all tabs. // Return Value: // - - void TerminalTab::UpdateSettings(const CascadiaSettings& settings) + void Tab::UpdateSettings(const CascadiaSettings& settings) { ASSERT_UI_THREAD(); @@ -286,7 +375,7 @@ namespace winrt::TerminalApp::implementation // - iconPath: The new path string to use as the IconPath for our TabViewItem // Return Value: // - - void TerminalTab::UpdateIcon(const winrt::hstring& iconPath, const winrt::Microsoft::Terminal::Settings::Model::IconStyle iconStyle) + void Tab::UpdateIcon(const winrt::hstring& iconPath, const winrt::Microsoft::Terminal::Settings::Model::IconStyle iconStyle) { ASSERT_UI_THREAD(); @@ -324,7 +413,7 @@ namespace winrt::TerminalApp::implementation // - Used when we want to show the progress ring, which should replace the icon // Arguments: // - hide: if true, we hide the icon; if false, we show the icon - void TerminalTab::HideIcon(const bool hide) + void Tab::HideIcon(const bool hide) { ASSERT_UI_THREAD(); @@ -348,7 +437,7 @@ namespace winrt::TerminalApp::implementation // - Hide or show the bell indicator in the tab header // Arguments: // - show: if true, we show the indicator; if false, we hide the indicator - void TerminalTab::ShowBellIndicator(const bool show) + void Tab::ShowBellIndicator(const bool show) { ASSERT_UI_THREAD(); @@ -358,14 +447,14 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Activates the timer for the bell indicator in the tab // - Called if a bell raised when the tab already has focus - void TerminalTab::ActivateBellIndicatorTimer() + void Tab::ActivateBellIndicatorTimer() { ASSERT_UI_THREAD(); if (!_bellIndicatorTimer) { _bellIndicatorTimer.Interval(std::chrono::milliseconds(2000)); - _bellIndicatorTimer.Tick({ get_weak(), &TerminalTab::_BellIndicatorTimerTick }); + _bellIndicatorTimer.Tick({ get_weak(), &Tab::_BellIndicatorTimerTick }); } _bellIndicatorTimer.Start(); @@ -378,7 +467,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - the title string of the last focused terminal control in our tree. - winrt::hstring TerminalTab::_GetActiveTitle() const + winrt::hstring Tab::_GetActiveTitle() const { if (!_runtimeTabText.empty()) { @@ -400,7 +489,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::UpdateTitle() + void Tab::UpdateTitle() { ASSERT_UI_THREAD(); @@ -422,7 +511,7 @@ namespace winrt::TerminalApp::implementation // - delta: a number of lines to move the viewport relative to the current viewport. // Return Value: // - - void TerminalTab::Scroll(const int delta) + void Tab::Scroll(const int delta) { ASSERT_UI_THREAD(); @@ -438,7 +527,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - A vector of commands - std::vector TerminalTab::BuildStartupActions(BuildStartupKind kind) const + std::vector Tab::BuildStartupActions(BuildStartupKind kind) const { ASSERT_UI_THREAD(); @@ -525,9 +614,9 @@ namespace winrt::TerminalApp::implementation // could itself be a parent pane/the root node of a tree of panes // Return Value: // - a pair of (the Pane that now holds the original content, the new Pane in the tree) - std::pair, std::shared_ptr> TerminalTab::SplitPane(SplitDirection splitType, - const float splitSize, - std::shared_ptr pane) + std::pair, std::shared_ptr> Tab::SplitPane(SplitDirection splitType, + const float splitSize, + std::shared_ptr pane) { ASSERT_UI_THREAD(); @@ -586,7 +675,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - The removed pane, if the remove succeeded. - std::shared_ptr TerminalTab::DetachPane() + std::shared_ptr Tab::DetachPane() { ASSERT_UI_THREAD(); @@ -615,7 +704,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - The root pane. - std::shared_ptr TerminalTab::DetachRoot() + std::shared_ptr Tab::DetachRoot() { ASSERT_UI_THREAD(); @@ -643,7 +732,7 @@ namespace winrt::TerminalApp::implementation // - pane: The pane to add. // Return Value: // - - void TerminalTab::AttachPane(std::shared_ptr pane) + void Tab::AttachPane(std::shared_ptr pane) { ASSERT_UI_THREAD(); @@ -694,7 +783,7 @@ namespace winrt::TerminalApp::implementation // - colorPicker: The color picker that we should attach to ourselves // Return Value: // - - void TerminalTab::AttachColorPicker(TerminalApp::ColorPickupFlyout& colorPicker) + void Tab::AttachColorPicker(TerminalApp::ColorPickupFlyout& colorPicker) { ASSERT_UI_THREAD(); @@ -734,7 +823,7 @@ namespace winrt::TerminalApp::implementation // its parent. E.g. switch from Horizontal to Vertical. // Return Value: // - - void TerminalTab::ToggleSplitOrientation() + void Tab::ToggleSplitOrientation() { ASSERT_UI_THREAD(); @@ -743,7 +832,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - See Pane::CalcSnappedDimension - float TerminalTab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const + float Tab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const { ASSERT_UI_THREAD(); @@ -757,7 +846,7 @@ namespace winrt::TerminalApp::implementation // - direction: The direction to move the separator in. // Return Value: // - - void TerminalTab::ResizePane(const ResizeDirection& direction) + void Tab::ResizePane(const ResizeDirection& direction) { ASSERT_UI_THREAD(); @@ -774,7 +863,7 @@ namespace winrt::TerminalApp::implementation // Return Value: // - Whether changing the focus succeeded. This allows a keychord to propagate // to the terminal when no other panes are present (GH#6219) - bool TerminalTab::NavigateFocus(const FocusDirection& direction) + bool Tab::NavigateFocus(const FocusDirection& direction) { ASSERT_UI_THREAD(); @@ -806,7 +895,7 @@ namespace winrt::TerminalApp::implementation // - direction: The direction to move the pane in. // Return Value: // - true if two panes were swapped. - bool TerminalTab::SwapPane(const FocusDirection& direction) + bool Tab::SwapPane(const FocusDirection& direction) { ASSERT_UI_THREAD(); @@ -831,7 +920,7 @@ namespace winrt::TerminalApp::implementation return false; } - bool TerminalTab::FocusPane(const uint32_t id) + bool Tab::FocusPane(const uint32_t id) { ASSERT_UI_THREAD(); @@ -847,12 +936,12 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections. - void TerminalTab::Shutdown() + void Tab::Shutdown() { ASSERT_UI_THREAD(); - // Don't forget to call the overridden function. :) - TabBase::Shutdown(); + // NOTE: `TerminalPage::_HandleCloseTabRequested` relies on the content being null after this call. + Content(nullptr); if (_rootPane) { @@ -868,14 +957,14 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::ClosePane() + void Tab::ClosePane() { ASSERT_UI_THREAD(); _activePane->Close(); } - void TerminalTab::SetTabText(winrt::hstring title) + void Tab::SetTabText(winrt::hstring title) { ASSERT_UI_THREAD(); @@ -883,14 +972,14 @@ namespace winrt::TerminalApp::implementation UpdateTitle(); } - winrt::hstring TerminalTab::GetTabText() const + winrt::hstring Tab::GetTabText() const { ASSERT_UI_THREAD(); return _runtimeTabText; } - void TerminalTab::ResetTabText() + void Tab::ResetTabText() { ASSERT_UI_THREAD(); @@ -905,7 +994,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::ActivateTabRenamer() + void Tab::ActivateTabRenamer() { ASSERT_UI_THREAD(); @@ -921,7 +1010,7 @@ namespace winrt::TerminalApp::implementation // - paneId: The ID of the pane that contains the given content. // Return Value: // - - void TerminalTab::_DetachEventHandlersFromContent(const uint32_t paneId) + void Tab::_DetachEventHandlersFromContent(const uint32_t paneId) { auto it = _contentEvents.find(paneId); if (it != _contentEvents.end()) @@ -943,7 +1032,7 @@ namespace winrt::TerminalApp::implementation // - control: the TermControl to add events to. // Return Value: // - - void TerminalTab::_AttachEventHandlersToContent(const uint32_t paneId, const TerminalApp::IPaneContent& content) + void Tab::_AttachEventHandlersToContent(const uint32_t paneId, const TerminalApp::IPaneContent& content) { auto weakThis{ get_weak() }; auto dispatcher = TabViewItem().Dispatcher(); @@ -1059,7 +1148,7 @@ namespace winrt::TerminalApp::implementation if (const auto& terminal{ content.try_as() }) { - events.RestartTerminalRequested = terminal.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TerminalTab::_bubbleRestartTerminalRequested }); + events.RestartTerminalRequested = terminal.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &Tab::_bubbleRestartTerminalRequested }); } if (_tabStatus.IsInputBroadcastActive()) @@ -1085,7 +1174,7 @@ namespace winrt::TerminalApp::implementation // Return Value: // - A TaskbarState object representing the combined taskbar state and // progress percentage of all our panes. - winrt::TerminalApp::TaskbarState TerminalTab::GetCombinedTaskbarState() const + winrt::TerminalApp::TaskbarState Tab::GetCombinedTaskbarState() const { ASSERT_UI_THREAD(); @@ -1111,7 +1200,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_UpdateProgressState() + void Tab::_UpdateProgressState() { const auto state{ GetCombinedTaskbarState() }; @@ -1155,7 +1244,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_UpdateConnectionClosedState() + void Tab::_UpdateConnectionClosedState() { ASSERT_UI_THREAD(); @@ -1176,7 +1265,7 @@ namespace winrt::TerminalApp::implementation } } - void TerminalTab::_RestartActivePaneConnection() + void Tab::_RestartActivePaneConnection() { ActionAndArgs restartConnection{ ShortcutAction::RestartConnection, nullptr }; _dispatch.DoAction(*this, restartConnection); @@ -1190,7 +1279,7 @@ namespace winrt::TerminalApp::implementation // - pane: a Pane to mark as active. // Return Value: // - - void TerminalTab::_UpdateActivePane(std::shared_ptr pane) + void Tab::_UpdateActivePane(std::shared_ptr pane) { // Clear the active state of the entire tree, and mark only the pane as active. _rootPane->ClearActive(); @@ -1255,7 +1344,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_AttachEventHandlersToPane(std::shared_ptr pane) + void Tab::_AttachEventHandlersToPane(std::shared_ptr pane) { auto weakThis{ get_weak() }; std::weak_ptr weakPane{ pane }; @@ -1369,6 +1458,139 @@ namespace winrt::TerminalApp::implementation }); } + void Tab::_AppendMoveMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout) + { + auto weakThis{ get_weak() }; + + // Move to new window + { + Controls::FontIcon moveTabToNewWindowTabSymbol; + moveTabToNewWindowTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); + moveTabToNewWindowTabSymbol.Glyph(L"\xE8A7"); + + _moveToNewWindowMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + MoveTabArgs args{ L"new", MoveTabDirection::Forward }; + ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; + tab->_dispatch.DoAction(*tab, actionAndArgs); + } + }); + _moveToNewWindowMenuItem.Text(RS_(L"MoveTabToNewWindowText")); + _moveToNewWindowMenuItem.Icon(moveTabToNewWindowTabSymbol); + + const auto moveTabToNewWindowToolTip = RS_(L"MoveTabToNewWindowToolTip"); + WUX::Controls::ToolTipService::SetToolTip(_moveToNewWindowMenuItem, box_value(moveTabToNewWindowToolTip)); + Automation::AutomationProperties::SetHelpText(_moveToNewWindowMenuItem, moveTabToNewWindowToolTip); + } + + // Move left + { + _moveLeftMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + MoveTabArgs args{ hstring{}, MoveTabDirection::Backward }; + ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; + tab->_dispatch.DoAction(*tab, actionAndArgs); + } + }); + _moveLeftMenuItem.Text(RS_(L"TabMoveLeft")); + } + + // Move right + { + _moveRightMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + MoveTabArgs args{ hstring{}, MoveTabDirection::Forward }; + ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; + tab->_dispatch.DoAction(*tab, actionAndArgs); + } + }); + _moveRightMenuItem.Text(RS_(L"TabMoveRight")); + } + + // Create a sub-menu for our extended move tab items. + Controls::MenuFlyoutSubItem moveSubMenu; + moveSubMenu.Text(RS_(L"TabMoveSubMenu")); + moveSubMenu.Items().Append(_moveToNewWindowMenuItem); + moveSubMenu.Items().Append(_moveRightMenuItem); + moveSubMenu.Items().Append(_moveLeftMenuItem); + flyout.Items().Append(moveSubMenu); + } + + // Method Description: + // - Append the close menu items to the context menu flyout + // Arguments: + // - flyout - the menu flyout to which the close items must be appended + // Return Value: + // - the sub-item that we use for all the nested "close" entries. This + // enables subclasses to add their own entries to this menu. + winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem Tab::_AppendCloseMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout) + { + auto weakThis{ get_weak() }; + + // Close tabs after + _closeTabsAfterMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + CloseTabsAfterArgs args{ tab->_TabViewIndex }; + ActionAndArgs closeTabsAfter{ ShortcutAction::CloseTabsAfter, args }; + tab->_dispatch.DoAction(*tab, closeTabsAfter); + } + }); + _closeTabsAfterMenuItem.Text(RS_(L"TabCloseAfter")); + const auto closeTabsAfterToolTip = RS_(L"TabCloseAfterToolTip"); + + WUX::Controls::ToolTipService::SetToolTip(_closeTabsAfterMenuItem, box_value(closeTabsAfterToolTip)); + Automation::AutomationProperties::SetHelpText(_closeTabsAfterMenuItem, closeTabsAfterToolTip); + + // Close other tabs + _closeOtherTabsMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + CloseOtherTabsArgs args{ tab->_TabViewIndex }; + ActionAndArgs closeOtherTabs{ ShortcutAction::CloseOtherTabs, args }; + tab->_dispatch.DoAction(*tab, closeOtherTabs); + } + }); + _closeOtherTabsMenuItem.Text(RS_(L"TabCloseOther")); + const auto closeOtherTabsToolTip = RS_(L"TabCloseOtherToolTip"); + + WUX::Controls::ToolTipService::SetToolTip(_closeOtherTabsMenuItem, box_value(closeOtherTabsToolTip)); + Automation::AutomationProperties::SetHelpText(_closeOtherTabsMenuItem, closeOtherTabsToolTip); + + // Close + Controls::MenuFlyoutItem closeTabMenuItem; + Controls::FontIcon closeSymbol; + closeSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); + closeSymbol.Glyph(L"\xE711"); + + closeTabMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + tab->CloseRequested.raise(nullptr, nullptr); + } + }); + closeTabMenuItem.Text(RS_(L"TabClose")); + closeTabMenuItem.Icon(closeSymbol); + const auto closeTabToolTip = RS_(L"TabCloseToolTip"); + + WUX::Controls::ToolTipService::SetToolTip(closeTabMenuItem, box_value(closeTabToolTip)); + Automation::AutomationProperties::SetHelpText(closeTabMenuItem, closeTabToolTip); + + // Create a sub-menu for our extended close items. + Controls::MenuFlyoutSubItem closeSubMenu; + closeSubMenu.Text(RS_(L"TabCloseSubMenu")); + closeSubMenu.Items().Append(_closeTabsAfterMenuItem); + closeSubMenu.Items().Append(_closeOtherTabsMenuItem); + flyout.Items().Append(closeSubMenu); + + flyout.Items().Append(closeTabMenuItem); + + return closeSubMenu; + } + // Method Description: // - Creates a context menu attached to the tab. // Currently contains elements allowing to select or @@ -1377,7 +1599,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::_CreateContextMenu() + void Tab::_CreateContextMenu() { auto weakThis{ get_weak() }; @@ -1388,7 +1610,7 @@ namespace winrt::TerminalApp::implementation colorPickSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); colorPickSymbol.Glyph(L"\xE790"); - chooseColorMenuItem.Click({ get_weak(), &TerminalTab::_chooseColorClicked }); + chooseColorMenuItem.Click({ get_weak(), &Tab::_chooseColorClicked }); chooseColorMenuItem.Text(RS_(L"TabColorChoose")); chooseColorMenuItem.Icon(colorPickSymbol); @@ -1405,7 +1627,7 @@ namespace winrt::TerminalApp::implementation renameTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); renameTabSymbol.Glyph(L"\xE8AC"); // Rename - renameTabMenuItem.Click({ get_weak(), &TerminalTab::_renameTabClicked }); + renameTabMenuItem.Click({ get_weak(), &Tab::_renameTabClicked }); renameTabMenuItem.Text(RS_(L"RenameTabText")); renameTabMenuItem.Icon(renameTabSymbol); @@ -1422,7 +1644,7 @@ namespace winrt::TerminalApp::implementation duplicateTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); duplicateTabSymbol.Glyph(L"\xF5ED"); - duplicateTabMenuItem.Click({ get_weak(), &TerminalTab::_duplicateTabClicked }); + duplicateTabMenuItem.Click({ get_weak(), &Tab::_duplicateTabClicked }); duplicateTabMenuItem.Text(RS_(L"DuplicateTabText")); duplicateTabMenuItem.Icon(duplicateTabSymbol); @@ -1439,7 +1661,7 @@ namespace winrt::TerminalApp::implementation splitTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); splitTabSymbol.Glyph(L"\xF246"); // ViewDashboard - splitTabMenuItem.Click({ get_weak(), &TerminalTab::_splitTabClicked }); + splitTabMenuItem.Click({ get_weak(), &Tab::_splitTabClicked }); splitTabMenuItem.Text(RS_(L"SplitTabText")); splitTabMenuItem.Icon(splitTabSymbol); @@ -1452,7 +1674,7 @@ namespace winrt::TerminalApp::implementation Controls::MenuFlyoutItem closePaneMenuItem = _closePaneMenuItem; { // "Close pane" - closePaneMenuItem.Click({ get_weak(), &TerminalTab::_closePaneClicked }); + closePaneMenuItem.Click({ get_weak(), &Tab::_closePaneClicked }); closePaneMenuItem.Text(RS_(L"ClosePaneText")); const auto closePaneToolTip = RS_(L"ClosePaneToolTip"); @@ -1468,7 +1690,7 @@ namespace winrt::TerminalApp::implementation exportTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); exportTabSymbol.Glyph(L"\xE74E"); // Save - exportTabMenuItem.Click({ get_weak(), &TerminalTab::_exportTextClicked }); + exportTabMenuItem.Click({ get_weak(), &Tab::_exportTextClicked }); exportTabMenuItem.Text(RS_(L"ExportTabText")); exportTabMenuItem.Icon(exportTabSymbol); @@ -1485,7 +1707,7 @@ namespace winrt::TerminalApp::implementation findSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); findSymbol.Glyph(L"\xF78B"); // SearchMedium - findMenuItem.Click({ get_weak(), &TerminalTab::_findClicked }); + findMenuItem.Click({ get_weak(), &Tab::_findClicked }); findMenuItem.Text(RS_(L"FindText")); findMenuItem.Icon(findSymbol); @@ -1556,13 +1778,47 @@ namespace winrt::TerminalApp::implementation TabViewItem().ContextFlyout(contextMenuFlyout); } + // Method Description: + // - Enable menu items based on tab index and total number of tabs + // Arguments: + // - + // Return Value: + // - + void Tab::_EnableMenuItems() + { + const auto tabIndex = TabViewIndex(); + const auto numOfTabs = TabViewNumTabs(); + + // enabled if there are other tabs + _closeOtherTabsMenuItem.IsEnabled(numOfTabs > 1); + + // enabled if there are other tabs on the right + _closeTabsAfterMenuItem.IsEnabled(tabIndex < numOfTabs - 1); + + // enabled if not left-most tab + _moveLeftMenuItem.IsEnabled(tabIndex > 0); + + // enabled if not last tab + _moveRightMenuItem.IsEnabled(tabIndex < numOfTabs - 1); + } + + void Tab::UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs) + { + ASSERT_UI_THREAD(); + + TabViewIndex(idx); + TabViewNumTabs(numTabs); + _EnableMenuItems(); + _UpdateSwitchToTabKeyChord(); + } + // Method Description: // Returns the tab color, if any // Arguments: // - // Return Value: // - The tab's color, if any - std::optional TerminalTab::GetTabColor() + std::optional Tab::GetTabColor() { ASSERT_UI_THREAD(); @@ -1602,7 +1858,7 @@ namespace winrt::TerminalApp::implementation // - color: the color the user picked for their tab // Return Value: // - - void TerminalTab::SetRuntimeTabColor(const winrt::Windows::UI::Color& color) + void Tab::SetRuntimeTabColor(const winrt::Windows::UI::Color& color) { ASSERT_UI_THREAD(); @@ -1619,7 +1875,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::ResetRuntimeTabColor() + void Tab::ResetRuntimeTabColor() { ASSERT_UI_THREAD(); @@ -1628,7 +1884,7 @@ namespace winrt::TerminalApp::implementation _tabStatus.TabColorIndicator(GetTabColor().value_or(Windows::UI::Colors::Transparent())); } - winrt::Windows::UI::Xaml::Media::Brush TerminalTab::_BackgroundBrush() + winrt::Windows::UI::Xaml::Media::Brush Tab::_BackgroundBrush() { Media::Brush terminalBrush{ nullptr }; if (const auto& c{ GetActiveContent() }) @@ -1644,7 +1900,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - The total number of leaf panes hosted by this tab. - int TerminalTab::GetLeafPaneCount() const noexcept + int Tab::GetLeafPaneCount() const noexcept { ASSERT_UI_THREAD(); @@ -1661,9 +1917,9 @@ namespace winrt::TerminalApp::implementation // Return value: // - This will return nullopt if a split of the given size/direction was not possible, // or it will return the split direction with automatic converted to a cardinal direction. - std::optional TerminalTab::PreCalculateCanSplit(SplitDirection splitType, - const float splitSize, - winrt::Windows::Foundation::Size availableSpace) const + std::optional Tab::PreCalculateCanSplit(SplitDirection splitType, + const float splitSize, + winrt::Windows::Foundation::Size availableSpace) const { ASSERT_UI_THREAD(); @@ -1676,7 +1932,7 @@ namespace winrt::TerminalApp::implementation // - newFocus: the new pane to be zoomed // Return Value: // - - void TerminalTab::UpdateZoom(std::shared_ptr newFocus) + void Tab::UpdateZoom(std::shared_ptr newFocus) { ASSERT_UI_THREAD(); @@ -1698,7 +1954,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalTab::ToggleZoom() + void Tab::ToggleZoom() { ASSERT_UI_THREAD(); @@ -1712,7 +1968,7 @@ namespace winrt::TerminalApp::implementation } } - void TerminalTab::EnterZoom() + void Tab::EnterZoom() { ASSERT_UI_THREAD(); @@ -1727,7 +1983,7 @@ namespace winrt::TerminalApp::implementation _tabStatus.IsPaneZoomed(true); Content(_zoomedPane->GetRootElement()); } - void TerminalTab::ExitZoom() + void Tab::ExitZoom() { ASSERT_UI_THREAD(); @@ -1739,7 +1995,7 @@ namespace winrt::TerminalApp::implementation Content(_rootPane->GetRootElement()); } - bool TerminalTab::IsZoomed() + bool Tab::IsZoomed() { ASSERT_UI_THREAD(); @@ -1762,7 +2018,7 @@ namespace winrt::TerminalApp::implementation // - Toggle read-only mode on the active pane // - If a parent pane is selected, this will ensure that all children have // the same read-only status. - void TerminalTab::TogglePaneReadOnly() + void Tab::TogglePaneReadOnly() { ASSERT_UI_THREAD(); @@ -1796,7 +2052,7 @@ namespace winrt::TerminalApp::implementation // - Set read-only mode on the active pane // - If a parent pane is selected, this will ensure that all children have // the same read-only status. - void TerminalTab::SetPaneReadOnly(const bool readOnlyState) + void Tab::SetPaneReadOnly(const bool readOnlyState) { auto hasReadOnly = false; auto allReadOnly = true; @@ -1828,7 +2084,7 @@ namespace winrt::TerminalApp::implementation // - Calculates if the tab is read-only. // The tab is considered read-only if one of the panes is read-only. // If after the calculation the tab is read-only we hide the close button on the tab view item - void TerminalTab::_RecalculateAndApplyReadOnly() + void Tab::_RecalculateAndApplyReadOnly() { const auto control = GetActiveTerminalControl(); if (control) @@ -1845,7 +2101,7 @@ namespace winrt::TerminalApp::implementation _rootPane->WalkTree([](const auto& p) { p->UpdateVisuals(); }); } - std::shared_ptr TerminalTab::GetActivePane() const + std::shared_ptr Tab::GetActivePane() const { ASSERT_UI_THREAD(); @@ -1859,7 +2115,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - The value to populate in the title run of the tool tip - winrt::hstring TerminalTab::_CreateToolTipTitle() + winrt::hstring Tab::_CreateToolTipTitle() { if (const auto& control{ GetActiveTerminalControl() }) { @@ -1875,7 +2131,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Toggle broadcasting input to all the panes in this tab. - void TerminalTab::ToggleBroadcastInput() + void Tab::ToggleBroadcastInput() { const bool newIsBroadcasting = !_tabStatus.IsInputBroadcastActive(); _tabStatus.IsInputBroadcastActive(newIsBroadcasting); @@ -1914,7 +2170,7 @@ namespace winrt::TerminalApp::implementation }); } - void TerminalTab::_addBroadcastHandlers(const TermControl& termControl, ContentEventTokens& events) + void Tab::_addBroadcastHandlers(const TermControl& termControl, ContentEventTokens& events) { auto weakThis{ get_weak() }; // ADD EVENT HANDLERS HERE @@ -1957,48 +2213,451 @@ namespace winrt::TerminalApp::implementation }); } - void TerminalTab::_chooseColorClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused, + const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& unfocused, + const til::color& tabRowColor) + { + ASSERT_UI_THREAD(); + + _themeColor = focused; + _unfocusedThemeColor = unfocused; + _tabRowColor = tabRowColor; + _RecalculateAndApplyTabColor(); + } + + // Method Description: + // - This function dispatches a function to the UI thread to recalculate + // what this tab's current background color should be. If a color is set, + // it will apply the given color to the tab's background. Otherwise, it + // will clear the tab's background color. + // Arguments: + // - + // Return Value: + // - + void Tab::_RecalculateAndApplyTabColor() + { + // GetTabColor will return the color set by the color picker, or the + // color specified in the profile. If neither of those were set, + // then look to _themeColor to see if there's a value there. + // Otherwise, clear our color, falling back to the TabView defaults. + const auto currentColor = GetTabColor(); + if (currentColor.has_value()) + { + _ApplyTabColorOnUIThread(currentColor.value()); + } + else if (_themeColor != nullptr) + { + // Safely get the active control's brush. + const Media::Brush terminalBrush{ _BackgroundBrush() }; + + if (const auto themeBrush{ _themeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) }) + { + // ThemeColor.Evaluate will get us a Brush (because the + // TermControl could have an acrylic BG, for example). Take + // that brush, and get the color out of it. We don't really + // want to have the tab items themselves be acrylic. + _ApplyTabColorOnUIThread(til::color{ ThemeColor::ColorFromBrush(themeBrush) }); + } + else + { + _ClearTabBackgroundColor(); + } + } + else + { + _ClearTabBackgroundColor(); + } + } + + // Method Description: + // - Applies the given color to the background of this tab's TabViewItem. + // - Sets the tab foreground color depending on the luminance of + // the background color + // - This method should only be called on the UI thread. + // Arguments: + // - color: the color the user picked for their tab + // Return Value: + // - + void Tab::_ApplyTabColorOnUIThread(const winrt::Windows::UI::Color& color) + { + Media::SolidColorBrush selectedTabBrush{}; + Media::SolidColorBrush deselectedTabBrush{}; + Media::SolidColorBrush fontBrush{}; + Media::SolidColorBrush deselectedFontBrush{}; + Media::SolidColorBrush secondaryFontBrush{}; + Media::SolidColorBrush hoverTabBrush{}; + Media::SolidColorBrush subtleFillColorSecondaryBrush; + Media::SolidColorBrush subtleFillColorTertiaryBrush; + + // calculate the luminance of the current color and select a font + // color based on that + // see https://www.w3.org/TR/WCAG20/#relativeluminancedef + if (TerminalApp::ColorHelper::IsBrightColor(color)) + { + auto subtleFillColorSecondary = winrt::Windows::UI::Colors::Black(); + subtleFillColorSecondary.A = 0x09; + subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary); + auto subtleFillColorTertiary = winrt::Windows::UI::Colors::Black(); + subtleFillColorTertiary.A = 0x06; + subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary); + } + else + { + auto subtleFillColorSecondary = winrt::Windows::UI::Colors::White(); + subtleFillColorSecondary.A = 0x0F; + subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary); + auto subtleFillColorTertiary = winrt::Windows::UI::Colors::White(); + subtleFillColorTertiary.A = 0x0A; + subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary); + } + + // The tab font should be based on the evaluated appearance of the tab color layered on tab row. + const auto layeredTabColor = til::color{ color }.layer_over(_tabRowColor); + if (TerminalApp::ColorHelper::IsBrightColor(layeredTabColor)) + { + fontBrush.Color(winrt::Windows::UI::Colors::Black()); + auto secondaryFontColor = winrt::Windows::UI::Colors::Black(); + // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L269 + secondaryFontColor.A = 0x9E; + secondaryFontBrush.Color(secondaryFontColor); + } + else + { + fontBrush.Color(winrt::Windows::UI::Colors::White()); + auto secondaryFontColor = winrt::Windows::UI::Colors::White(); + // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L14 + secondaryFontColor.A = 0xC5; + secondaryFontBrush.Color(secondaryFontColor); + } + + selectedTabBrush.Color(color); + + // Start with the current tab color, set to Opacity=.3 + til::color deselectedTabColor{ color }; + deselectedTabColor = deselectedTabColor.with_alpha(77); // 255 * .3 = 77 + + // If we DON'T have a color set from the color picker, or the profile's + // tabColor, but we do have a unfocused color in the theme, use the + // unfocused theme color here instead. + if (!GetTabColor().has_value() && + _unfocusedThemeColor != nullptr) + { + // Safely get the active control's brush. + const Media::Brush terminalBrush{ _BackgroundBrush() }; + + // Get the color of the brush. + if (const auto themeBrush{ _unfocusedThemeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) }) + { + // We did figure out the brush. Get the color out of it. If it + // was "accent" or "terminalBackground", then we're gonna set + // the alpha to .3 manually here. + // (ThemeColor::UnfocusedTabOpacity will do this for us). If the + // user sets both unfocused and focused tab.background to + // terminalBackground, this will allow for some differentiation + // (and is generally just sensible). + deselectedTabColor = til::color{ ThemeColor::ColorFromBrush(themeBrush) }.with_alpha(_unfocusedThemeColor.UnfocusedTabOpacity()); + } + } + + // currently if a tab has a custom color, a deselected state is + // signified by using the same color with a bit of transparency + deselectedTabBrush.Color(deselectedTabColor.with_alpha(255)); + deselectedTabBrush.Opacity(deselectedTabColor.a / 255.f); + + hoverTabBrush.Color(color); + hoverTabBrush.Opacity(0.6); + + // Account for the color of the tab row when setting the color of text + // on inactive tabs. Consider: + // * black active tabs + // * on a white tab row + // * with a transparent inactive tab color + // + // We don't want that to result in white text on a white tab row for + // inactive tabs. + const auto deselectedActualColor = deselectedTabColor.layer_over(_tabRowColor); + if (TerminalApp::ColorHelper::IsBrightColor(deselectedActualColor)) + { + deselectedFontBrush.Color(winrt::Windows::UI::Colors::Black()); + } + else + { + deselectedFontBrush.Color(winrt::Windows::UI::Colors::White()); + } + + // Add the empty theme dictionaries + const auto& tabItemThemeResources{ TabViewItem().Resources().ThemeDictionaries() }; + ResourceDictionary lightThemeDictionary; + ResourceDictionary darkThemeDictionary; + ResourceDictionary highContrastThemeDictionary; + tabItemThemeResources.Insert(winrt::box_value(L"Light"), lightThemeDictionary); + tabItemThemeResources.Insert(winrt::box_value(L"Dark"), darkThemeDictionary); + tabItemThemeResources.Insert(winrt::box_value(L"HighContrast"), highContrastThemeDictionary); + + // Apply the color to the tab + TabViewItem().Background(deselectedTabBrush); + + // Now actually set the resources we want in them. + // Before, we used to put these on the ResourceDictionary directly. + // However, HighContrast mode may require some adjustments. So let's just add + // all three so we can make those adjustments on the HighContrast version. + for (const auto& [k, v] : tabItemThemeResources) + { + const bool isHighContrast = winrt::unbox_value(k) == L"HighContrast"; + const auto& currentDictionary = v.as(); + + // TabViewItem.Background + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), isHighContrast ? fontBrush : hoverTabBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush); + + // TabViewItem.Foreground (aka text) + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForeground"), deselectedFontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), isHighContrast ? selectedTabBrush : fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush); + + // TabViewItem.CloseButton.Foreground (aka X) + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForeground"), deselectedFontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPressed"), isHighContrast ? deselectedFontBrush : secondaryFontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPointerOver"), isHighContrast ? deselectedFontBrush : fontBrush); + + // TabViewItem.CloseButton.Foreground _when_ interacting with the tab + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderPressedCloseButtonForeground"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderPointerOverCloseButtonForeground"), isHighContrast ? selectedTabBrush : fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderSelectedCloseButtonForeground"), fontBrush); + + // TabViewItem.CloseButton.Background (aka X button) + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPressed"), isHighContrast ? selectedTabBrush : subtleFillColorTertiaryBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPointerOver"), isHighContrast ? selectedTabBrush : subtleFillColorSecondaryBrush); + + // A few miscellaneous resources that WinUI said may be removed in the future + currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundPressed"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundPointerOver"), fontBrush); + + // Add a few extra ones for high contrast mode + // BODGY: contrary to the docs, Insert() seems to throw if the value already exists + // Make sure you don't touch any that already exist here! + if (isHighContrast) + { + // TabViewItem.CloseButton.Border: in HC mode, the border makes the button more clearly visible + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushPressed"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushPointerOver"), fontBrush); + currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushSelected"), fontBrush); + } + } + + _RefreshVisualState(); + } + + // Method Description: + // - Clear out any color we've set for the TabViewItem. + // - This method should only be called on the UI thread. + // Arguments: + // - + // Return Value: + // - + void Tab::_ClearTabBackgroundColor() + { + static const winrt::hstring keys[] = { + // TabViewItem.Background + L"TabViewItemHeaderBackground", + L"TabViewItemHeaderBackgroundSelected", + L"TabViewItemHeaderBackgroundPointerOver", + L"TabViewItemHeaderBackgroundPressed", + + // TabViewItem.Foreground (aka text) + L"TabViewItemHeaderForeground", + L"TabViewItemHeaderForegroundSelected", + L"TabViewItemHeaderForegroundPointerOver", + L"TabViewItemHeaderForegroundPressed", + + // TabViewItem.CloseButton.Foreground (aka X) + L"TabViewItemHeaderCloseButtonForeground", + L"TabViewItemHeaderForegroundSelected", + L"TabViewItemHeaderCloseButtonForegroundPointerOver", + L"TabViewItemHeaderCloseButtonForegroundPressed", + + // TabViewItem.CloseButton.Foreground _when_ interacting with the tab + L"TabViewItemHeaderPressedCloseButtonForeground", + L"TabViewItemHeaderPointerOverCloseButtonForeground", + L"TabViewItemHeaderSelectedCloseButtonForeground", + + // TabViewItem.CloseButton.Background (aka X button) + L"TabViewItemHeaderCloseButtonBackground", + L"TabViewItemHeaderCloseButtonBackgroundPressed", + L"TabViewItemHeaderCloseButtonBackgroundPointerOver", + + // A few miscellaneous resources that WinUI said may be removed in the future + L"TabViewButtonForegroundActiveTab", + L"TabViewButtonForegroundPressed", + L"TabViewButtonForegroundPointerOver", + + // TabViewItem.CloseButton.Border: in HC mode, the border makes the button more clearly visible + L"TabViewItemHeaderCloseButtonBorderBrushPressed", + L"TabViewItemHeaderCloseButtonBorderBrushPointerOver", + L"TabViewItemHeaderCloseButtonBorderBrushSelected" + }; + + const auto& tabItemThemeResources{ TabViewItem().Resources().ThemeDictionaries() }; + + // simply clear any of the colors in the tab's dict + for (const auto& keyString : keys) + { + const auto key = winrt::box_value(keyString); + for (const auto& [_, v] : tabItemThemeResources) + { + const auto& themeDictionary = v.as(); + themeDictionary.Remove(key); + } + } + + // GH#11382 DON'T set the background to null. If you do that, then the + // tab won't be hit testable at all. Transparent, however, is a totally + // valid hit test target. That makes sense. + TabViewItem().Background(WUX::Media::SolidColorBrush{ Windows::UI::Colors::Transparent() }); + + _RefreshVisualState(); + } + + // Method Description: + // BODGY + // - Toggles the requested theme of the tab view item, + // so that changes to the tab color are reflected immediately + // - Prior to MUX 2.8, we only toggled the visual state here, but that seemingly + // doesn't work in 2.8. + // - Just changing the Theme also doesn't seem to work by itself - there + // seems to be a way for the tab to set the deselected foreground onto + // itself as it becomes selected. If the mouse isn't over the tab, that + // can result in mismatched fg/bg's (see GH#15184). So that's right, we + // need to do both. + // Arguments: + // - + // Return Value: + // - + void Tab::_RefreshVisualState() + { + const auto& item{ TabViewItem() }; + + const auto& reqTheme = TabViewItem().RequestedTheme(); + item.RequestedTheme(ElementTheme::Light); + item.RequestedTheme(ElementTheme::Dark); + item.RequestedTheme(reqTheme); + + if (TabViewItem().IsSelected()) + { + VisualStateManager::GoToState(item, L"Normal", true); + VisualStateManager::GoToState(item, L"Selected", true); + } + else + { + VisualStateManager::GoToState(item, L"Selected", true); + VisualStateManager::GoToState(item, L"Normal", true); + } + } + + TabCloseButtonVisibility Tab::CloseButtonVisibility() + { + return _closeButtonVisibility; + } + + // Method Description: + // - set our internal state to track if we were requested to have a visible + // tab close button or not. + // - This is called every time the active tab changes. That way, the changes + // in focused tab can be reflected for the "ActiveOnly" state. + void Tab::CloseButtonVisibility(TabCloseButtonVisibility visibility) + { + _closeButtonVisibility = visibility; + _updateIsClosable(); + } + + // Method Description: + // - Update our close button's visibility, to reflect both the ReadOnly + // state of the tab content, and also if if we were told to have a visible + // close button at all. + // - the tab being read-only takes precedence. That will always suppress + // the close button. + // - Otherwise we'll use the state set in CloseButtonVisibility to control + // the tab's visibility. + void Tab::_updateIsClosable() + { + bool isClosable = true; + + if (ReadOnly()) + { + isClosable = false; + } + else + { + switch (_closeButtonVisibility) + { + case TabCloseButtonVisibility::Never: + isClosable = false; + break; + case TabCloseButtonVisibility::Hover: + isClosable = true; + break; + case TabCloseButtonVisibility::ActiveOnly: + isClosable = _focused(); + break; + default: + isClosable = true; + break; + } + } + TabViewItem().IsClosable(isClosable); + } + + bool Tab::_focused() const noexcept + { + return _focusState != FocusState::Unfocused; + } + + void Tab::_chooseColorClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { _dispatch.DoAction(*this, { ShortcutAction::OpenTabColorPicker, nullptr }); } - void TerminalTab::_renameTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_renameTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ActivateTabRenamer(); } - void TerminalTab::_duplicateTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_duplicateTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ActionAndArgs actionAndArgs{ ShortcutAction::DuplicateTab, nullptr }; _dispatch.DoAction(*this, actionAndArgs); } - void TerminalTab::_splitTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_splitTabClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ActionAndArgs actionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate } }; _dispatch.DoAction(*this, actionAndArgs); } - void TerminalTab::_closePaneClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_closePaneClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ClosePane(); } - void TerminalTab::_exportTextClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_exportTextClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ActionAndArgs actionAndArgs{}; actionAndArgs.Action(ShortcutAction::ExportBuffer); _dispatch.DoAction(*this, actionAndArgs); } - void TerminalTab::_findClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, - const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) + void Tab::_findClicked(const winrt::Windows::Foundation::IInspectable& /* sender */, + const winrt::Windows::UI::Xaml::RoutedEventArgs& /* args */) { ActionAndArgs actionAndArgs{ ShortcutAction::Find, nullptr }; _dispatch.DoAction(*this, actionAndArgs); } - void TerminalTab::_bubbleRestartTerminalRequested(TerminalApp::TerminalPaneContent sender, - const winrt::Windows::Foundation::IInspectable& args) + void Tab::_bubbleRestartTerminalRequested(TerminalApp::TerminalPaneContent sender, + const winrt::Windows::Foundation::IInspectable& args) { RestartTerminalRequested.raise(sender, args); } diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/Tab.h similarity index 67% rename from src/cascadia/TerminalApp/TerminalTab.h rename to src/cascadia/TerminalApp/Tab.h index 29cc3d1b5e..be6180586e 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -4,8 +4,8 @@ #pragma once #include "Pane.h" #include "ColorPickupFlyout.h" -#include "TabBase.h" -#include "TerminalTab.g.h" +#include "Tab.h" +#include "Tab.g.h" // fwdecl unittest classes namespace TerminalAppLocalTests @@ -15,10 +15,10 @@ namespace TerminalAppLocalTests namespace winrt::TerminalApp::implementation { - struct TerminalTab : TerminalTabT + struct Tab : TabT { public: - TerminalTab(std::shared_ptr rootPane); + Tab(std::shared_ptr rootPane); // Called after construction to perform the necessary setup, which relies on weak_ptr void Initialize(); @@ -27,7 +27,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile() const noexcept; winrt::TerminalApp::IPaneContent GetActiveContent() const; - void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override; + void Focus(winrt::Windows::UI::Xaml::FocusState focusState); void Scroll(const int delta); @@ -61,7 +61,7 @@ namespace winrt::TerminalApp::implementation void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); void UpdateTitle(); - void Shutdown() override; + void Shutdown(); void ClosePane(); void SetTabText(winrt::hstring title); @@ -69,7 +69,7 @@ namespace winrt::TerminalApp::implementation void ResetTabText(); void ActivateTabRenamer(); - virtual std::optional GetTabColor() override; + std::optional GetTabColor(); void SetRuntimeTabColor(const winrt::Windows::UI::Color& color); void ResetRuntimeTabColor(); @@ -79,7 +79,7 @@ namespace winrt::TerminalApp::implementation void EnterZoom(); void ExitZoom(); - std::vector BuildStartupActions(BuildStartupKind kind) const override; + std::vector BuildStartupActions(BuildStartupKind kind) const; int GetLeafPaneCount() const noexcept; @@ -98,16 +98,62 @@ namespace winrt::TerminalApp::implementation return _tabStatus; } + void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); + + void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs); + void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap); + + void ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused, + const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& unfocused, + const til::color& tabRowColor); + + Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility CloseButtonVisibility(); + void CloseButtonVisibility(Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility visible); + + til::event> RequestFocusActiveControl; + + til::event> Closed; + til::event> CloseRequested; + til::property_changed_event PropertyChanged; + til::typed_event RestartTerminalRequested; - til::typed_event ActivePaneChanged; + til::typed_event ActivePaneChanged; til::event> TabRaiseVisualBell; til::typed_event TaskbarProgressChanged; + // The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector. + WINRT_PROPERTY(uint32_t, TabViewIndex, 0); + // The TabViewNumTabs is the number of Tab objects in TerminalPage's _tabs vector. + WINRT_PROPERTY(uint32_t, TabViewNumTabs, 0); + + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Title, PropertyChanged.raise); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Icon, PropertyChanged.raise); + WINRT_OBSERVABLE_PROPERTY(bool, ReadOnly, PropertyChanged.raise, false); + WINRT_PROPERTY(winrt::Microsoft::UI::Xaml::Controls::TabViewItem, TabViewItem, nullptr); + + WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::FrameworkElement, Content, PropertyChanged.raise, nullptr); + private: static constexpr double HeaderRenameBoxWidthDefault{ 165 }; static constexpr double HeaderRenameBoxWidthTitleLength{ std::numeric_limits::infinity() }; + winrt::Windows::UI::Xaml::FocusState _focusState{ winrt::Windows::UI::Xaml::FocusState::Unfocused }; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeOtherTabsMenuItem{}; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeTabsAfterMenuItem{}; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveToNewWindowMenuItem{}; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveRightMenuItem{}; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveLeftMenuItem{}; + winrt::TerminalApp::ShortcutActionDispatch _dispatch; + Microsoft::Terminal::Settings::Model::IActionMapView _actionMap{ nullptr }; + winrt::hstring _keyChord{}; + + winrt::Microsoft::Terminal::Settings::Model::ThemeColor _themeColor{ nullptr }; + winrt::Microsoft::Terminal::Settings::Model::ThemeColor _unfocusedThemeColor{ nullptr }; + til::color _tabRowColor; + + Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility _closeButtonVisibility{ Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility::Always }; + std::shared_ptr _rootPane{ nullptr }; std::shared_ptr _activePane{ nullptr }; std::shared_ptr _zoomedPane{ nullptr }; @@ -163,12 +209,10 @@ namespace winrt::TerminalApp::implementation SafeDispatcherTimer _bellIndicatorTimer; void _BellIndicatorTimerTick(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e); - void _MakeTabViewItem() override; - void _UpdateHeaderControlMaxWidth(); - void _CreateContextMenu() override; - virtual winrt::hstring _CreateToolTipTitle() override; + void _CreateContextMenu(); + winrt::hstring _CreateToolTipTitle(); void _DetachEventHandlersFromContent(const uint32_t paneId); void _AttachEventHandlersToContent(const uint32_t paneId, const winrt::TerminalApp::IPaneContent& content); @@ -187,7 +231,23 @@ namespace winrt::TerminalApp::implementation void _DuplicateTab(); - virtual winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush() override; + winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush(); + + void _MakeTabViewItem(); + + void _AppendMoveMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout); + winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem _AppendCloseMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout); + void _EnableMenuItems(); + void _UpdateSwitchToTabKeyChord(); + void _UpdateToolTip(); + + void _RecalculateAndApplyTabColor(); + void _ApplyTabColorOnUIThread(const winrt::Windows::UI::Color& color); + void _ClearTabBackgroundColor(); + void _RefreshVisualState(); + + bool _focused() const noexcept; + void _updateIsClosable(); void _addBroadcastHandlers(const winrt::Microsoft::Terminal::Control::TermControl& control, ContentEventTokens& events); diff --git a/src/cascadia/TerminalApp/TabBase.idl b/src/cascadia/TerminalApp/Tab.idl similarity index 78% rename from src/cascadia/TerminalApp/TabBase.idl rename to src/cascadia/TerminalApp/Tab.idl index 20ee8ffa9d..0f214133a6 100644 --- a/src/cascadia/TerminalApp/TabBase.idl +++ b/src/cascadia/TerminalApp/Tab.idl @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. import "ShortcutActionDispatch.idl"; +import "TerminalTabStatus.idl"; namespace TerminalApp { - unsealed runtimeclass TabBase : Windows.UI.Xaml.Data.INotifyPropertyChanged + runtimeclass Tab : Windows.UI.Xaml.Data.INotifyPropertyChanged { String Title { get; }; String Icon { get; }; @@ -14,6 +15,9 @@ namespace TerminalApp Microsoft.UI.Xaml.Controls.TabViewItem TabViewItem { get; }; Windows.UI.Xaml.FrameworkElement Content { get; }; + // May be Null + TerminalTabStatus TabStatus { get; }; + UInt32 TabViewIndex; UInt32 TabViewNumTabs; diff --git a/src/cascadia/TerminalApp/TabBase.cpp b/src/cascadia/TerminalApp/TabBase.cpp deleted file mode 100644 index 60e2e94bc2..0000000000 --- a/src/cascadia/TerminalApp/TabBase.cpp +++ /dev/null @@ -1,753 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include -#include "TabBase.h" -#include "TabBase.g.cpp" -#include "Utils.h" -#include "ColorHelper.h" - -using namespace winrt; -using namespace winrt::Windows::UI::Xaml; -using namespace winrt::Windows::UI::Core; -using namespace winrt::Microsoft::Terminal::Control; -using namespace winrt::Microsoft::Terminal::Settings::Model; -using namespace winrt::Windows::System; - -namespace winrt -{ - namespace MUX = Microsoft::UI::Xaml; - namespace WUX = Windows::UI::Xaml; -} - -#define ASSERT_UI_THREAD() assert(TabViewItem().Dispatcher().HasThreadAccess()) - -namespace winrt::TerminalApp::implementation -{ - - // Method Description: - // - Prepares this tab for being removed from the UI hierarchy - void TabBase::Shutdown() - { - ASSERT_UI_THREAD(); - - // NOTE: `TerminalPage::_HandleCloseTabRequested` relies on the content being null after this call. - Content(nullptr); - } - - // Method Description: - // - Creates a context menu attached to the tab. - // Currently contains elements allowing the user to close the selected tab - // Arguments: - // - - // Return Value: - // - - void TabBase::_CreateContextMenu() - { - auto weakThis{ get_weak() }; - - // Build the menu - Controls::MenuFlyout contextMenuFlyout; - // GH#5750 - When the context menu is dismissed with ESC, toss the focus - // back to our control. - contextMenuFlyout.Closed([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - tab->RequestFocusActiveControl.raise(); - } - }); - _AppendMoveMenuItems(contextMenuFlyout); - _AppendCloseMenuItems(contextMenuFlyout); - TabViewItem().ContextFlyout(contextMenuFlyout); - } - - void TabBase::_AppendMoveMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout) - { - auto weakThis{ get_weak() }; - - // Move to new window - { - Controls::FontIcon moveTabToNewWindowTabSymbol; - moveTabToNewWindowTabSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); - moveTabToNewWindowTabSymbol.Glyph(L"\xE8A7"); - - _moveToNewWindowMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - MoveTabArgs args{ L"new", MoveTabDirection::Forward }; - ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; - tab->_dispatch.DoAction(*tab, actionAndArgs); - } - }); - _moveToNewWindowMenuItem.Text(RS_(L"MoveTabToNewWindowText")); - _moveToNewWindowMenuItem.Icon(moveTabToNewWindowTabSymbol); - - const auto moveTabToNewWindowToolTip = RS_(L"MoveTabToNewWindowToolTip"); - WUX::Controls::ToolTipService::SetToolTip(_moveToNewWindowMenuItem, box_value(moveTabToNewWindowToolTip)); - Automation::AutomationProperties::SetHelpText(_moveToNewWindowMenuItem, moveTabToNewWindowToolTip); - } - - // Move left - { - _moveLeftMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - MoveTabArgs args{ hstring{}, MoveTabDirection::Backward }; - ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; - tab->_dispatch.DoAction(*tab, actionAndArgs); - } - }); - _moveLeftMenuItem.Text(RS_(L"TabMoveLeft")); - } - - // Move right - { - _moveRightMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - MoveTabArgs args{ hstring{}, MoveTabDirection::Forward }; - ActionAndArgs actionAndArgs{ ShortcutAction::MoveTab, args }; - tab->_dispatch.DoAction(*tab, actionAndArgs); - } - }); - _moveRightMenuItem.Text(RS_(L"TabMoveRight")); - } - - // Create a sub-menu for our extended move tab items. - Controls::MenuFlyoutSubItem moveSubMenu; - moveSubMenu.Text(RS_(L"TabMoveSubMenu")); - moveSubMenu.Items().Append(_moveToNewWindowMenuItem); - moveSubMenu.Items().Append(_moveRightMenuItem); - moveSubMenu.Items().Append(_moveLeftMenuItem); - flyout.Items().Append(moveSubMenu); - } - - // Method Description: - // - Append the close menu items to the context menu flyout - // Arguments: - // - flyout - the menu flyout to which the close items must be appended - // Return Value: - // - the sub-item that we use for all the nested "close" entries. This - // enables subclasses to add their own entries to this menu. - winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem TabBase::_AppendCloseMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout) - { - auto weakThis{ get_weak() }; - - // Close tabs after - _closeTabsAfterMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - CloseTabsAfterArgs args{ tab->_TabViewIndex }; - ActionAndArgs closeTabsAfter{ ShortcutAction::CloseTabsAfter, args }; - tab->_dispatch.DoAction(*tab, closeTabsAfter); - } - }); - _closeTabsAfterMenuItem.Text(RS_(L"TabCloseAfter")); - const auto closeTabsAfterToolTip = RS_(L"TabCloseAfterToolTip"); - - WUX::Controls::ToolTipService::SetToolTip(_closeTabsAfterMenuItem, box_value(closeTabsAfterToolTip)); - Automation::AutomationProperties::SetHelpText(_closeTabsAfterMenuItem, closeTabsAfterToolTip); - - // Close other tabs - _closeOtherTabsMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - CloseOtherTabsArgs args{ tab->_TabViewIndex }; - ActionAndArgs closeOtherTabs{ ShortcutAction::CloseOtherTabs, args }; - tab->_dispatch.DoAction(*tab, closeOtherTabs); - } - }); - _closeOtherTabsMenuItem.Text(RS_(L"TabCloseOther")); - const auto closeOtherTabsToolTip = RS_(L"TabCloseOtherToolTip"); - - WUX::Controls::ToolTipService::SetToolTip(_closeOtherTabsMenuItem, box_value(closeOtherTabsToolTip)); - Automation::AutomationProperties::SetHelpText(_closeOtherTabsMenuItem, closeOtherTabsToolTip); - - // Close - Controls::MenuFlyoutItem closeTabMenuItem; - Controls::FontIcon closeSymbol; - closeSymbol.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); - closeSymbol.Glyph(L"\xE711"); - - closeTabMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - tab->CloseRequested.raise(nullptr, nullptr); - } - }); - closeTabMenuItem.Text(RS_(L"TabClose")); - closeTabMenuItem.Icon(closeSymbol); - const auto closeTabToolTip = RS_(L"TabCloseToolTip"); - - WUX::Controls::ToolTipService::SetToolTip(closeTabMenuItem, box_value(closeTabToolTip)); - Automation::AutomationProperties::SetHelpText(closeTabMenuItem, closeTabToolTip); - - // Create a sub-menu for our extended close items. - Controls::MenuFlyoutSubItem closeSubMenu; - closeSubMenu.Text(RS_(L"TabCloseSubMenu")); - closeSubMenu.Items().Append(_closeTabsAfterMenuItem); - closeSubMenu.Items().Append(_closeOtherTabsMenuItem); - flyout.Items().Append(closeSubMenu); - - flyout.Items().Append(closeTabMenuItem); - - return closeSubMenu; - } - - // Method Description: - // - Enable menu items based on tab index and total number of tabs - // Arguments: - // - - // Return Value: - // - - void TabBase::_EnableMenuItems() - { - const auto tabIndex = TabViewIndex(); - const auto numOfTabs = TabViewNumTabs(); - - // enabled if there are other tabs - _closeOtherTabsMenuItem.IsEnabled(numOfTabs > 1); - - // enabled if there are other tabs on the right - _closeTabsAfterMenuItem.IsEnabled(tabIndex < numOfTabs - 1); - - // enabled if not left-most tab - _moveLeftMenuItem.IsEnabled(tabIndex > 0); - - // enabled if not last tab - _moveRightMenuItem.IsEnabled(tabIndex < numOfTabs - 1); - } - - void TabBase::UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs) - { - ASSERT_UI_THREAD(); - - TabViewIndex(idx); - TabViewNumTabs(numTabs); - _EnableMenuItems(); - _UpdateSwitchToTabKeyChord(); - } - - void TabBase::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch) - { - ASSERT_UI_THREAD(); - - _dispatch = dispatch; - } - - void TabBase::SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap) - { - ASSERT_UI_THREAD(); - - _actionMap = actionMap; - _UpdateSwitchToTabKeyChord(); - } - - // Method Description: - // - Sets the key chord resulting in switch to the current tab. - // Updates tool tip if required - // Arguments: - // - keyChord - string representation of the key chord that switches to the current tab - // Return Value: - // - - void TabBase::_UpdateSwitchToTabKeyChord() - { - const auto id = fmt::format(FMT_COMPILE(L"Terminal.SwitchToTab{}"), _TabViewIndex); - const auto keyChord{ _actionMap.GetKeyBindingForAction(id) }; - const auto keyChordText = keyChord ? KeyChordSerialization::ToString(keyChord) : L""; - - if (_keyChord == keyChordText) - { - return; - } - - _keyChord = keyChordText; - _UpdateToolTip(); - } - - // Method Description: - // - Creates a text for the title run in the tool tip by returning tab title - // Arguments: - // - - // Return Value: - // - The value to populate in the title run of the tool tip - winrt::hstring TabBase::_CreateToolTipTitle() - { - return _Title; - } - - // Method Description: - // - Sets tab tool tip to a concatenation of title and key chord - // Arguments: - // - - // Return Value: - // - - void TabBase::_UpdateToolTip() - { - auto titleRun = WUX::Documents::Run(); - titleRun.Text(_CreateToolTipTitle()); - - auto textBlock = WUX::Controls::TextBlock{}; - textBlock.TextWrapping(WUX::TextWrapping::Wrap); - textBlock.TextAlignment(WUX::TextAlignment::Center); - textBlock.Inlines().Append(titleRun); - - if (!_keyChord.empty()) - { - auto keyChordRun = WUX::Documents::Run(); - keyChordRun.Text(_keyChord); - keyChordRun.FontStyle(winrt::Windows::UI::Text::FontStyle::Italic); - textBlock.Inlines().Append(WUX::Documents::LineBreak{}); - textBlock.Inlines().Append(keyChordRun); - } - - WUX::Controls::ToolTip toolTip{}; - toolTip.Content(textBlock); - WUX::Controls::ToolTipService::SetToolTip(TabViewItem(), toolTip); - } - - // Method Description: - // - Initializes a TabViewItem for this Tab instance. - // Arguments: - // - - // Return Value: - // - - void TabBase::_MakeTabViewItem() - { - TabViewItem(::winrt::MUX::Controls::TabViewItem{}); - - // GH#3609 If the tab was tapped, and no one else was around to handle - // it, then ask our parent to toss focus into the active control. - TabViewItem().Tapped([weakThis{ get_weak() }](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - tab->RequestFocusActiveControl.raise(); - } - }); - - // BODGY: When the tab is drag/dropped, the TabView gets a - // TabDragStarting. However, the way it is implemented[^1], the - // TabViewItem needs either an Item or a Content for the event to - // include the correct TabViewItem. Otherwise, it will just return the - // first TabViewItem in the TabView with the same Content as the dragged - // tab (which, if the Content is null, will be the _first_ tab). - // - // So here, we'll stick an empty border in, just so that every tab has a - // Content which is not equal to the others. - // - // [^1]: microsoft-ui-xaml/blob/92fbfcd55f05c92ac65569f5d284c5b36492091e/dev/TabView/TabView.cpp#L751-L758 - TabViewItem().Content(winrt::WUX::Controls::Border{}); - } - - std::optional TabBase::GetTabColor() - { - ASSERT_UI_THREAD(); - - return std::nullopt; - } - - void TabBase::ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused, - const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& unfocused, - const til::color& tabRowColor) - { - ASSERT_UI_THREAD(); - - _themeColor = focused; - _unfocusedThemeColor = unfocused; - _tabRowColor = tabRowColor; - _RecalculateAndApplyTabColor(); - } - - // Method Description: - // - This function dispatches a function to the UI thread to recalculate - // what this tab's current background color should be. If a color is set, - // it will apply the given color to the tab's background. Otherwise, it - // will clear the tab's background color. - // Arguments: - // - - // Return Value: - // - - void TabBase::_RecalculateAndApplyTabColor() - { - // GetTabColor will return the color set by the color picker, or the - // color specified in the profile. If neither of those were set, - // then look to _themeColor to see if there's a value there. - // Otherwise, clear our color, falling back to the TabView defaults. - const auto currentColor = GetTabColor(); - if (currentColor.has_value()) - { - _ApplyTabColorOnUIThread(currentColor.value()); - } - else if (_themeColor != nullptr) - { - // Safely get the active control's brush. - const Media::Brush terminalBrush{ _BackgroundBrush() }; - - if (const auto themeBrush{ _themeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) }) - { - // ThemeColor.Evaluate will get us a Brush (because the - // TermControl could have an acrylic BG, for example). Take - // that brush, and get the color out of it. We don't really - // want to have the tab items themselves be acrylic. - _ApplyTabColorOnUIThread(til::color{ ThemeColor::ColorFromBrush(themeBrush) }); - } - else - { - _ClearTabBackgroundColor(); - } - } - else - { - _ClearTabBackgroundColor(); - } - } - - // Method Description: - // - Applies the given color to the background of this tab's TabViewItem. - // - Sets the tab foreground color depending on the luminance of - // the background color - // - This method should only be called on the UI thread. - // Arguments: - // - color: the color the user picked for their tab - // Return Value: - // - - void TabBase::_ApplyTabColorOnUIThread(const winrt::Windows::UI::Color& color) - { - Media::SolidColorBrush selectedTabBrush{}; - Media::SolidColorBrush deselectedTabBrush{}; - Media::SolidColorBrush fontBrush{}; - Media::SolidColorBrush deselectedFontBrush{}; - Media::SolidColorBrush secondaryFontBrush{}; - Media::SolidColorBrush hoverTabBrush{}; - Media::SolidColorBrush subtleFillColorSecondaryBrush; - Media::SolidColorBrush subtleFillColorTertiaryBrush; - - // calculate the luminance of the current color and select a font - // color based on that - // see https://www.w3.org/TR/WCAG20/#relativeluminancedef - if (TerminalApp::ColorHelper::IsBrightColor(color)) - { - auto subtleFillColorSecondary = winrt::Windows::UI::Colors::Black(); - subtleFillColorSecondary.A = 0x09; - subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary); - auto subtleFillColorTertiary = winrt::Windows::UI::Colors::Black(); - subtleFillColorTertiary.A = 0x06; - subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary); - } - else - { - auto subtleFillColorSecondary = winrt::Windows::UI::Colors::White(); - subtleFillColorSecondary.A = 0x0F; - subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary); - auto subtleFillColorTertiary = winrt::Windows::UI::Colors::White(); - subtleFillColorTertiary.A = 0x0A; - subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary); - } - - // The tab font should be based on the evaluated appearance of the tab color layered on tab row. - const auto layeredTabColor = til::color{ color }.layer_over(_tabRowColor); - if (TerminalApp::ColorHelper::IsBrightColor(layeredTabColor)) - { - fontBrush.Color(winrt::Windows::UI::Colors::Black()); - auto secondaryFontColor = winrt::Windows::UI::Colors::Black(); - // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L269 - secondaryFontColor.A = 0x9E; - secondaryFontBrush.Color(secondaryFontColor); - } - else - { - fontBrush.Color(winrt::Windows::UI::Colors::White()); - auto secondaryFontColor = winrt::Windows::UI::Colors::White(); - // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L14 - secondaryFontColor.A = 0xC5; - secondaryFontBrush.Color(secondaryFontColor); - } - - selectedTabBrush.Color(color); - - // Start with the current tab color, set to Opacity=.3 - til::color deselectedTabColor{ color }; - deselectedTabColor = deselectedTabColor.with_alpha(77); // 255 * .3 = 77 - - // If we DON'T have a color set from the color picker, or the profile's - // tabColor, but we do have a unfocused color in the theme, use the - // unfocused theme color here instead. - if (!GetTabColor().has_value() && - _unfocusedThemeColor != nullptr) - { - // Safely get the active control's brush. - const Media::Brush terminalBrush{ _BackgroundBrush() }; - - // Get the color of the brush. - if (const auto themeBrush{ _unfocusedThemeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) }) - { - // We did figure out the brush. Get the color out of it. If it - // was "accent" or "terminalBackground", then we're gonna set - // the alpha to .3 manually here. - // (ThemeColor::UnfocusedTabOpacity will do this for us). If the - // user sets both unfocused and focused tab.background to - // terminalBackground, this will allow for some differentiation - // (and is generally just sensible). - deselectedTabColor = til::color{ ThemeColor::ColorFromBrush(themeBrush) }.with_alpha(_unfocusedThemeColor.UnfocusedTabOpacity()); - } - } - - // currently if a tab has a custom color, a deselected state is - // signified by using the same color with a bit of transparency - deselectedTabBrush.Color(deselectedTabColor.with_alpha(255)); - deselectedTabBrush.Opacity(deselectedTabColor.a / 255.f); - - hoverTabBrush.Color(color); - hoverTabBrush.Opacity(0.6); - - // Account for the color of the tab row when setting the color of text - // on inactive tabs. Consider: - // * black active tabs - // * on a white tab row - // * with a transparent inactive tab color - // - // We don't want that to result in white text on a white tab row for - // inactive tabs. - const auto deselectedActualColor = deselectedTabColor.layer_over(_tabRowColor); - if (TerminalApp::ColorHelper::IsBrightColor(deselectedActualColor)) - { - deselectedFontBrush.Color(winrt::Windows::UI::Colors::Black()); - } - else - { - deselectedFontBrush.Color(winrt::Windows::UI::Colors::White()); - } - - // Add the empty theme dictionaries - const auto& tabItemThemeResources{ TabViewItem().Resources().ThemeDictionaries() }; - ResourceDictionary lightThemeDictionary; - ResourceDictionary darkThemeDictionary; - ResourceDictionary highContrastThemeDictionary; - tabItemThemeResources.Insert(winrt::box_value(L"Light"), lightThemeDictionary); - tabItemThemeResources.Insert(winrt::box_value(L"Dark"), darkThemeDictionary); - tabItemThemeResources.Insert(winrt::box_value(L"HighContrast"), highContrastThemeDictionary); - - // Apply the color to the tab - TabViewItem().Background(deselectedTabBrush); - - // Now actually set the resources we want in them. - // Before, we used to put these on the ResourceDictionary directly. - // However, HighContrast mode may require some adjustments. So let's just add - // all three so we can make those adjustments on the HighContrast version. - for (const auto& [k, v] : tabItemThemeResources) - { - const bool isHighContrast = winrt::unbox_value(k) == L"HighContrast"; - const auto& currentDictionary = v.as(); - - // TabViewItem.Background - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), isHighContrast ? fontBrush : hoverTabBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush); - - // TabViewItem.Foreground (aka text) - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForeground"), deselectedFontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), isHighContrast ? selectedTabBrush : fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush); - - // TabViewItem.CloseButton.Foreground (aka X) - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForeground"), deselectedFontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPressed"), isHighContrast ? deselectedFontBrush : secondaryFontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPointerOver"), isHighContrast ? deselectedFontBrush : fontBrush); - - // TabViewItem.CloseButton.Foreground _when_ interacting with the tab - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderPressedCloseButtonForeground"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderPointerOverCloseButtonForeground"), isHighContrast ? selectedTabBrush : fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderSelectedCloseButtonForeground"), fontBrush); - - // TabViewItem.CloseButton.Background (aka X button) - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPressed"), isHighContrast ? selectedTabBrush : subtleFillColorTertiaryBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPointerOver"), isHighContrast ? selectedTabBrush : subtleFillColorSecondaryBrush); - - // A few miscellaneous resources that WinUI said may be removed in the future - currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundPressed"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewButtonForegroundPointerOver"), fontBrush); - - // Add a few extra ones for high contrast mode - // BODGY: contrary to the docs, Insert() seems to throw if the value already exists - // Make sure you don't touch any that already exist here! - if (isHighContrast) - { - // TabViewItem.CloseButton.Border: in HC mode, the border makes the button more clearly visible - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushPressed"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushPointerOver"), fontBrush); - currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBorderBrushSelected"), fontBrush); - } - } - - _RefreshVisualState(); - } - - // Method Description: - // - Clear out any color we've set for the TabViewItem. - // - This method should only be called on the UI thread. - // Arguments: - // - - // Return Value: - // - - void TabBase::_ClearTabBackgroundColor() - { - static const winrt::hstring keys[] = { - // TabViewItem.Background - L"TabViewItemHeaderBackground", - L"TabViewItemHeaderBackgroundSelected", - L"TabViewItemHeaderBackgroundPointerOver", - L"TabViewItemHeaderBackgroundPressed", - - // TabViewItem.Foreground (aka text) - L"TabViewItemHeaderForeground", - L"TabViewItemHeaderForegroundSelected", - L"TabViewItemHeaderForegroundPointerOver", - L"TabViewItemHeaderForegroundPressed", - - // TabViewItem.CloseButton.Foreground (aka X) - L"TabViewItemHeaderCloseButtonForeground", - L"TabViewItemHeaderForegroundSelected", - L"TabViewItemHeaderCloseButtonForegroundPointerOver", - L"TabViewItemHeaderCloseButtonForegroundPressed", - - // TabViewItem.CloseButton.Foreground _when_ interacting with the tab - L"TabViewItemHeaderPressedCloseButtonForeground", - L"TabViewItemHeaderPointerOverCloseButtonForeground", - L"TabViewItemHeaderSelectedCloseButtonForeground", - - // TabViewItem.CloseButton.Background (aka X button) - L"TabViewItemHeaderCloseButtonBackground", - L"TabViewItemHeaderCloseButtonBackgroundPressed", - L"TabViewItemHeaderCloseButtonBackgroundPointerOver", - - // A few miscellaneous resources that WinUI said may be removed in the future - L"TabViewButtonForegroundActiveTab", - L"TabViewButtonForegroundPressed", - L"TabViewButtonForegroundPointerOver", - - // TabViewItem.CloseButton.Border: in HC mode, the border makes the button more clearly visible - L"TabViewItemHeaderCloseButtonBorderBrushPressed", - L"TabViewItemHeaderCloseButtonBorderBrushPointerOver", - L"TabViewItemHeaderCloseButtonBorderBrushSelected" - }; - - const auto& tabItemThemeResources{ TabViewItem().Resources().ThemeDictionaries() }; - - // simply clear any of the colors in the tab's dict - for (const auto& keyString : keys) - { - const auto key = winrt::box_value(keyString); - for (const auto& [_, v] : tabItemThemeResources) - { - const auto& themeDictionary = v.as(); - themeDictionary.Remove(key); - } - } - - // GH#11382 DON'T set the background to null. If you do that, then the - // tab won't be hit testable at all. Transparent, however, is a totally - // valid hit test target. That makes sense. - TabViewItem().Background(WUX::Media::SolidColorBrush{ Windows::UI::Colors::Transparent() }); - - _RefreshVisualState(); - } - - // Method Description: - // BODGY - // - Toggles the requested theme of the tab view item, - // so that changes to the tab color are reflected immediately - // - Prior to MUX 2.8, we only toggled the visual state here, but that seemingly - // doesn't work in 2.8. - // - Just changing the Theme also doesn't seem to work by itself - there - // seems to be a way for the tab to set the deselected foreground onto - // itself as it becomes selected. If the mouse isn't over the tab, that - // can result in mismatched fg/bg's (see GH#15184). So that's right, we - // need to do both. - // Arguments: - // - - // Return Value: - // - - void TabBase::_RefreshVisualState() - { - const auto& item{ TabViewItem() }; - - const auto& reqTheme = TabViewItem().RequestedTheme(); - item.RequestedTheme(ElementTheme::Light); - item.RequestedTheme(ElementTheme::Dark); - item.RequestedTheme(reqTheme); - - if (TabViewItem().IsSelected()) - { - VisualStateManager::GoToState(item, L"Normal", true); - VisualStateManager::GoToState(item, L"Selected", true); - } - else - { - VisualStateManager::GoToState(item, L"Selected", true); - VisualStateManager::GoToState(item, L"Normal", true); - } - } - - TabCloseButtonVisibility TabBase::CloseButtonVisibility() - { - return _closeButtonVisibility; - } - - // Method Description: - // - set our internal state to track if we were requested to have a visible - // tab close button or not. - // - This is called every time the active tab changes. That way, the changes - // in focused tab can be reflected for the "ActiveOnly" state. - void TabBase::CloseButtonVisibility(TabCloseButtonVisibility visibility) - { - _closeButtonVisibility = visibility; - _updateIsClosable(); - } - - // Method Description: - // - Update our close button's visibility, to reflect both the ReadOnly - // state of the tab content, and also if if we were told to have a visible - // close button at all. - // - the tab being read-only takes precedence. That will always suppress - // the close button. - // - Otherwise we'll use the state set in CloseButtonVisibility to control - // the tab's visibility. - void TabBase::_updateIsClosable() - { - bool isClosable = true; - - if (ReadOnly()) - { - isClosable = false; - } - else - { - switch (_closeButtonVisibility) - { - case TabCloseButtonVisibility::Never: - isClosable = false; - break; - case TabCloseButtonVisibility::Hover: - isClosable = true; - break; - case TabCloseButtonVisibility::ActiveOnly: - isClosable = _focused(); - break; - default: - isClosable = true; - break; - } - } - TabViewItem().IsClosable(isClosable); - } - - bool TabBase::_focused() const noexcept - { - return _focusState != FocusState::Unfocused; - } - -} diff --git a/src/cascadia/TerminalApp/TabBase.h b/src/cascadia/TerminalApp/TabBase.h deleted file mode 100644 index aa4928c669..0000000000 --- a/src/cascadia/TerminalApp/TabBase.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once -#include "TabBase.g.h" - -// fwdecl unittest classes -namespace TerminalAppLocalTests -{ - class TabTests; -}; - -namespace winrt::TerminalApp::implementation -{ - struct TabBase : TabBaseT - { - public: - virtual void Focus(winrt::Windows::UI::Xaml::FocusState focusState) = 0; - - virtual void Shutdown(); - void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); - - void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs); - void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap); - virtual std::vector BuildStartupActions(BuildStartupKind kind) const = 0; - - virtual std::optional GetTabColor(); - void ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused, - const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& unfocused, - const til::color& tabRowColor); - - Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility CloseButtonVisibility(); - void CloseButtonVisibility(Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility visible); - - til::event> RequestFocusActiveControl; - - til::event> Closed; - til::event> CloseRequested; - til::property_changed_event PropertyChanged; - - // The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector. - WINRT_PROPERTY(uint32_t, TabViewIndex, 0); - // The TabViewNumTabs is the number of Tab objects in TerminalPage's _tabs vector. - WINRT_PROPERTY(uint32_t, TabViewNumTabs, 0); - - WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Title, PropertyChanged.raise); - WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Icon, PropertyChanged.raise); - WINRT_OBSERVABLE_PROPERTY(bool, ReadOnly, PropertyChanged.raise, false); - WINRT_PROPERTY(winrt::Microsoft::UI::Xaml::Controls::TabViewItem, TabViewItem, nullptr); - - WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::FrameworkElement, Content, PropertyChanged.raise, nullptr); - - protected: - winrt::Windows::UI::Xaml::FocusState _focusState{ winrt::Windows::UI::Xaml::FocusState::Unfocused }; - winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeOtherTabsMenuItem{}; - winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeTabsAfterMenuItem{}; - winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveToNewWindowMenuItem{}; - winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveRightMenuItem{}; - winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _moveLeftMenuItem{}; - winrt::TerminalApp::ShortcutActionDispatch _dispatch; - Microsoft::Terminal::Settings::Model::IActionMapView _actionMap{ nullptr }; - winrt::hstring _keyChord{}; - - winrt::Microsoft::Terminal::Settings::Model::ThemeColor _themeColor{ nullptr }; - winrt::Microsoft::Terminal::Settings::Model::ThemeColor _unfocusedThemeColor{ nullptr }; - til::color _tabRowColor; - - Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility _closeButtonVisibility{ Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility::Always }; - - virtual void _CreateContextMenu(); - virtual winrt::hstring _CreateToolTipTitle(); - - virtual void _MakeTabViewItem(); - - void _AppendMoveMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout); - winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem _AppendCloseMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout); - void _EnableMenuItems(); - void _UpdateSwitchToTabKeyChord(); - void _UpdateToolTip(); - - void _RecalculateAndApplyTabColor(); - void _ApplyTabColorOnUIThread(const winrt::Windows::UI::Color& color); - void _ClearTabBackgroundColor(); - void _RefreshVisualState(); - virtual winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush() = 0; - - bool _focused() const noexcept; - void _updateIsClosable(); - - friend class ::TerminalAppLocalTests::TabTests; - }; -} diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 3b2691df93..b7a3e29e5a 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -99,7 +99,7 @@ namespace winrt::TerminalApp::implementation // Arguments: // - newTabImpl: the uninitialized tab. // - insertPosition: Optional parameter to indicate the position of tab. - void TerminalPage::_InitializeTab(winrt::com_ptr newTabImpl, uint32_t insertPosition) + void TerminalPage::_InitializeTab(winrt::com_ptr newTabImpl, uint32_t insertPosition) { newTabImpl->Initialize(); @@ -206,11 +206,11 @@ namespace winrt::TerminalApp::implementation // Arguments: // - pane: The pane to use as the root. // - insertPosition: Optional parameter to indicate the position of tab. - TerminalApp::TerminalTab TerminalPage::_CreateNewTabFromPane(std::shared_ptr pane, uint32_t insertPosition) + TerminalApp::Tab TerminalPage::_CreateNewTabFromPane(std::shared_ptr pane, uint32_t insertPosition) { if (pane) { - auto newTabImpl = winrt::make_self(pane); + auto newTabImpl = winrt::make_self(pane); _InitializeTab(newTabImpl, insertPosition); return *newTabImpl; } @@ -222,7 +222,7 @@ namespace winrt::TerminalApp::implementation // tab's icon to that icon. // Arguments: // - tab: the Tab to update the title for. - void TerminalPage::_UpdateTabIcon(TerminalTab& tab) + void TerminalPage::_UpdateTabIcon(Tab& tab) { if (const auto content{ tab.GetActiveContent() }) { @@ -272,9 +272,9 @@ namespace winrt::TerminalApp::implementation // - Duplicates the current focused tab void TerminalPage::_DuplicateFocusedTab() { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto activeTab{ _GetFocusedTabImpl() }) { - _DuplicateTab(*terminalTab); + _DuplicateTab(*activeTab); } } @@ -282,7 +282,7 @@ namespace winrt::TerminalApp::implementation // - Duplicates specified tab // Arguments: // - tab: tab to duplicate - void TerminalPage::_DuplicateTab(const TerminalTab& tab) + void TerminalPage::_DuplicateTab(const Tab& tab) { try { @@ -315,7 +315,7 @@ namespace winrt::TerminalApp::implementation // - Exports the content of the Terminal Buffer inside the tab // Arguments: // - tab: tab to export - safe_void_coroutine TerminalPage::_ExportTab(const TerminalTab& tab, winrt::hstring filepath) + safe_void_coroutine TerminalPage::_ExportTab(const Tab& tab, winrt::hstring filepath) { // This will be used to set up the file picker "filter", to select .txt // files by default. @@ -401,7 +401,7 @@ namespace winrt::TerminalApp::implementation // - Removes the tab (both TerminalControl and XAML) after prompting for approval // Arguments: // - tab: the tab to remove - winrt::Windows::Foundation::IAsyncAction TerminalPage::_HandleCloseTabRequested(winrt::TerminalApp::TabBase tab) + winrt::Windows::Foundation::IAsyncAction TerminalPage::_HandleCloseTabRequested(winrt::TerminalApp::Tab tab) { if (tab.ReadOnly()) { @@ -414,7 +414,7 @@ namespace winrt::TerminalApp::implementation } } - auto t = winrt::get_self(tab); + auto t = winrt::get_self(tab); auto actions = t->BuildStartupActions(BuildStartupKind::None); _AddPreviouslyClosedPaneOrTab(std::move(actions)); @@ -425,7 +425,7 @@ namespace winrt::TerminalApp::implementation // - Removes the tab (both TerminalControl and XAML) // Arguments: // - tab: the tab to remove - void TerminalPage::_RemoveTab(const winrt::TerminalApp::TabBase& tab) + void TerminalPage::_RemoveTab(const winrt::TerminalApp::Tab& tab) { uint32_t tabIndex{}; if (!_tabs.IndexOf(tab, tabIndex)) @@ -597,7 +597,7 @@ namespace winrt::TerminalApp::implementation // - tab - tab to select // Return Value: // - - void TerminalPage::_OnSwitchToTabRequested(const IInspectable& /*sender*/, const winrt::TerminalApp::TabBase& tab) + void TerminalPage::_OnSwitchToTabRequested(const IInspectable& /*sender*/, const winrt::TerminalApp::Tab& tab) { uint32_t index{}; if (_tabs.IndexOf(tab, index)) @@ -628,7 +628,7 @@ namespace winrt::TerminalApp::implementation // no tab is currently selected, returns nullopt. // Return Value: // - the index of the currently focused tab if there is one, else nullopt - std::optional TerminalPage::_GetTabIndex(const TerminalApp::TabBase& tab) const noexcept + std::optional TerminalPage::_GetTabIndex(const TerminalApp::Tab& tab) const noexcept { uint32_t i; if (_tabs.IndexOf(tab, i)) @@ -641,7 +641,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - returns the currently focused tab. This might return null, // so make sure to check the result! - winrt::TerminalApp::TabBase TerminalPage::_GetFocusedTab() const noexcept + winrt::TerminalApp::Tab TerminalPage::_GetFocusedTab() const noexcept { if (auto index{ _GetFocusedTabIndex() }) { @@ -653,11 +653,11 @@ namespace winrt::TerminalApp::implementation // Method Description: // - returns a com_ptr to the currently focused tab implementation. This might return null, // so make sure to check the result! - winrt::com_ptr TerminalPage::_GetFocusedTabImpl() const noexcept + winrt::com_ptr TerminalPage::_GetFocusedTabImpl() const noexcept { if (auto tab{ _GetFocusedTab() }) { - return _GetTerminalTabImpl(tab); + return _GetTabImpl(tab); } return nullptr; } @@ -665,7 +665,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - returns a tab corresponding to a view item. This might return null, // so make sure to check the result! - winrt::TerminalApp::TabBase TerminalPage::_GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept + winrt::TerminalApp::Tab TerminalPage::_GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept { uint32_t tabIndexFromControl{}; const auto items{ _tabView.TabItems() }; @@ -687,7 +687,7 @@ namespace winrt::TerminalApp::implementation // - tab: tab to focus. // Return Value: // - - safe_void_coroutine TerminalPage::_SetFocusedTab(const winrt::TerminalApp::TabBase tab) + safe_void_coroutine TerminalPage::_SetFocusedTab(const winrt::TerminalApp::Tab tab) { // GH#1117: This is a workaround because _tabView.SelectedIndex(tabIndex) // sometimes set focus to an incorrect tab after removing some tabs @@ -774,11 +774,11 @@ namespace winrt::TerminalApp::implementation // tab's Closed event. safe_void_coroutine TerminalPage::_CloseFocusedPane() { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto activeTab{ _GetFocusedTabImpl() }) { _UnZoomIfNeeded(); - if (const auto pane{ terminalTab->GetActivePane() }) + if (const auto pane{ activeTab->GetActivePane() }) { if (co_await _PaneConfirmCloseReadOnly(pane)) { @@ -793,7 +793,7 @@ namespace winrt::TerminalApp::implementation // Arguments: // - weakTab: weak reference to the tab that the pane belongs to. // - paneIds: collection of the IDs of the panes that are marked for removal. - void TerminalPage::_ClosePanes(weak_ref weakTab, std::vector paneIds) + void TerminalPage::_ClosePanes(weak_ref weakTab, std::vector paneIds) { if (auto strongTab{ weakTab.get() }) { @@ -838,7 +838,7 @@ namespace winrt::TerminalApp::implementation // - Closes provided tabs one by one // Arguments: // - tabs - tabs to remove - safe_void_coroutine TerminalPage::_RemoveTabs(const std::vector tabs) + safe_void_coroutine TerminalPage::_RemoveTabs(const std::vector tabs) { for (auto& tab : tabs) { @@ -926,7 +926,7 @@ namespace winrt::TerminalApp::implementation } // WinUI asynchronously updates its tab view items, so it may happen that we're given a - // `TabViewItem` that still contains a `TabBase` which has actually already been removed. + // `TabViewItem` that still contains a `Tab` which has actually already been removed. // First we must yield once, to flush out whatever TabView is currently doing. const auto strong = get_strong(); co_await wil::resume_foreground(Dispatcher()); @@ -966,7 +966,7 @@ namespace winrt::TerminalApp::implementation } } - void TerminalPage::_UpdatedSelectedTab(const winrt::TerminalApp::TabBase& tab) + void TerminalPage::_UpdatedSelectedTab(const winrt::TerminalApp::Tab& tab) { // Unfocus all the tabs. for (const auto& tab : _tabs) @@ -1007,10 +1007,10 @@ namespace winrt::TerminalApp::implementation _updateThemeColors(); - auto tab_impl = _GetTerminalTabImpl(tab); - if (tab_impl) + auto tabImpl = _GetTabImpl(tab); + if (tabImpl) { - auto profile = tab_impl->GetFocusedProfile(); + auto profile = tabImpl->GetFocusedProfile(); _UpdateBackground(profile); } } @@ -1057,7 +1057,7 @@ namespace winrt::TerminalApp::implementation for (uint32_t i = 0; i < size; ++i) { auto tab{ _tabs.GetAt(i) }; - auto tabImpl{ winrt::get_self(tab) }; + auto tabImpl{ winrt::get_self(tab) }; tabImpl->UpdateTabViewIndex(i, size); } } @@ -1068,7 +1068,7 @@ namespace winrt::TerminalApp::implementation // - tab: tab to bump. // Return Value: // - - void TerminalPage::_UpdateMRUTab(const winrt::TerminalApp::TabBase& tab) + void TerminalPage::_UpdateMRUTab(const winrt::TerminalApp::Tab& tab) { uint32_t mruIndex; if (_mruTabs.IndexOf(tab, mruIndex)) diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 5012430884..7e5028167b 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -99,15 +99,12 @@ Code - - TabBase.idl + + Tab.idl TaskbarState.idl - - TerminalTab.idl - TerminalPage.xaml Code @@ -206,16 +203,13 @@ PaletteItemTemplateSelector.idl Code - - TabBase.idl + + Tab.idl TaskbarState.idl - - TerminalTab.idl - TerminalPage.xaml Code @@ -333,9 +327,8 @@ MinMaxCloseControl.xaml Code - + - TerminalPage.xaml Code diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters index 52b5113bdb..68c7f8c7a3 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters @@ -81,9 +81,6 @@ settings - - tab - commandPalette @@ -91,7 +88,7 @@ tab - + diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 87ccb19a32..ce8e8762e4 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -59,8 +59,8 @@ namespace winrt namespace winrt::TerminalApp::implementation { TerminalPage::TerminalPage(TerminalApp::WindowProperties properties, const TerminalApp::ContentManager& manager) : - _tabs{ winrt::single_threaded_observable_vector() }, - _mruTabs{ winrt::single_threaded_observable_vector() }, + _tabs{ winrt::single_threaded_observable_vector() }, + _mruTabs{ winrt::single_threaded_observable_vector() }, _manager{ manager }, _hostingHwnd{}, _WindowProperties{ std::move(properties) } @@ -84,9 +84,9 @@ namespace winrt::TerminalApp::implementation // GH#13211 - if we haven't yet set the owning hwnd, reparent all the controls now. for (const auto& tab : _tabs) { - if (auto terminalTab{ _GetTerminalTabImpl(tab) }) + if (auto tabImpl{ _GetTabImpl(tab) }) { - terminalTab->GetRootPane()->WalkTree([&](auto&& pane) { + tabImpl->GetRootPane()->WalkTree([&](auto&& pane) { if (const auto& term{ pane->GetTerminalControl() }) { term.OwningHwnd(reinterpret_cast(hwnd)); @@ -566,9 +566,9 @@ namespace winrt::TerminalApp::implementation // GH#6586: now that we're done processing all startup commands, // focus the active control. This will work as expected for both // commandline invocations and for `wt` action invocations. - if (const auto& terminalTab{ _GetFocusedTabImpl() }) + if (const auto& tabImpl{ _GetFocusedTabImpl() }) { - if (const auto& content{ terminalTab->GetActiveContent() }) + if (const auto& content{ tabImpl->GetActiveContent() }) { content.Focus(FocusState::Programmatic); } @@ -1755,7 +1755,7 @@ namespace winrt::TerminalApp::implementation // TitleChanged event. // Arguments: // - tab: the Tab to update the title for. - void TerminalPage::_UpdateTitle(const TerminalTab& tab) + void TerminalPage::_UpdateTitle(const Tab& tab) { auto newTabTitle = tab.Title(); @@ -1833,13 +1833,13 @@ namespace winrt::TerminalApp::implementation } // Method Description: - // - Connects event handlers to the TerminalTab for events that we want to + // - Connects event handlers to the Tab for events that we want to // handle. This includes: // * the TitleChanged event, for changing the text of the tab // * the Color{Selected,Cleared} events to change the color of a tab. // Arguments: // - hostingTab: The Tab that's hosting this TermControl instance - void TerminalPage::_RegisterTabEvents(TerminalTab& hostingTab) + void TerminalPage::_RegisterTabEvents(Tab& hostingTab) { auto weakTab{ hostingTab.get_weak() }; auto weakThis{ get_weak() }; @@ -1922,9 +1922,9 @@ namespace winrt::TerminalApp::implementation // to the terminal when no other panes are present (GH#6219) bool TerminalPage::_MoveFocus(const FocusDirection& direction) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { - return terminalTab->NavigateFocus(direction); + return tabImpl->NavigateFocus(direction); } return false; } @@ -1938,19 +1938,19 @@ namespace winrt::TerminalApp::implementation // - true if panes were swapped. bool TerminalPage::_SwapPane(const FocusDirection& direction) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { _UnZoomIfNeeded(); - return terminalTab->SwapPane(direction); + return tabImpl->SwapPane(direction); } return false; } TermControl TerminalPage::_GetActiveControl() { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { - return terminalTab->GetActiveTerminalControl(); + return tabImpl->GetActiveTerminalControl(); } return nullptr; } @@ -2057,7 +2057,7 @@ namespace winrt::TerminalApp::implementation for (auto tab : _tabs) { - auto t = winrt::get_self(tab); + auto t = winrt::get_self(tab); auto tabActions = t->BuildStartupActions(serializeBuffer ? BuildStartupKind::PersistAll : BuildStartupKind::PersistLayout); actions.insert(actions.end(), std::make_move_iterator(tabActions.begin()), std::make_move_iterator(tabActions.end())); } @@ -2152,14 +2152,14 @@ namespace winrt::TerminalApp::implementation // - rowsToScroll: a number of lines to move the viewport. If not provided we will use a system default. void TerminalPage::_Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference& rowsToScroll) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { uint32_t realRowsToScroll; if (rowsToScroll == nullptr) { // The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ? - terminalTab->GetActiveTerminalControl().ViewHeight() : + tabImpl->GetActiveTerminalControl().ViewHeight() : _systemRowsToScroll; } else @@ -2168,7 +2168,7 @@ namespace winrt::TerminalApp::implementation realRowsToScroll = rowsToScroll.Value(); } auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll); - terminalTab->Scroll(scrollDelta); + tabImpl->Scroll(scrollDelta); } } @@ -2199,9 +2199,9 @@ namespace winrt::TerminalApp::implementation // specified window instead of moving it in our tab row. if (!windowId.empty()) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { - if (const auto pane{ terminalTab->GetActivePane() }) + if (const auto pane{ tabImpl->GetActivePane() }) { auto startupActions = pane->BuildStartupActions(0, 1, BuildStartupKind::MovePane); _DetachPaneFromWindow(pane); @@ -2240,7 +2240,7 @@ namespace winrt::TerminalApp::implementation // tab before its index changes. if (tabIdx < _tabs.Size()) { - auto targetTab = _GetTerminalTabImpl(_tabs.GetAt(tabIdx)); + auto targetTab = _GetTabImpl(_tabs.GetAt(tabIdx)); // if the selected tab is not a host of terminals (e.g. settings) // don't attempt to add a pane to it. if (!targetTab) @@ -2288,15 +2288,12 @@ namespace winrt::TerminalApp::implementation }); } - void TerminalPage::_DetachTabFromWindow(const winrt::com_ptr& tab) + void TerminalPage::_DetachTabFromWindow(const winrt::com_ptr& tab) { - if (const auto terminalTab = tab.try_as()) + // Detach the root pane, which will act like the whole tab got detached. + if (const auto rootPane = tab->GetRootPane()) { - // Detach the root pane, which will act like the whole tab got detached. - if (const auto rootPane = terminalTab->GetRootPane()) - { - _DetachPaneFromWindow(rootPane); - } + _DetachPaneFromWindow(rootPane); } } @@ -2324,7 +2321,7 @@ namespace winrt::TerminalApp::implementation RequestMoveContent.raise(*this, *request); } - bool TerminalPage::_MoveTab(winrt::com_ptr tab, MoveTabArgs args) + bool TerminalPage::_MoveTab(winrt::com_ptr tab, MoveTabArgs args) { if (!tab) { @@ -2392,10 +2389,10 @@ namespace winrt::TerminalApp::implementation // When the tab's active pane changes, we'll want to lookup a new icon // for it. The Title change will be propagated upwards through the tab's // PropertyChanged event handler. - void TerminalPage::_activePaneChanged(winrt::TerminalApp::TerminalTab sender, + void TerminalPage::_activePaneChanged(winrt::TerminalApp::Tab sender, Windows::Foundation::IInspectable /*args*/) { - if (const auto tab{ _GetTerminalTabImpl(sender) }) + if (const auto tab{ _GetTabImpl(sender) }) { // Possibly update the icon of the tab. _UpdateTabIcon(*tab); @@ -2484,7 +2481,7 @@ namespace winrt::TerminalApp::implementation // - splitDirection: one value from the TerminalApp::SplitDirection enum, indicating how the // new pane should be split from its parent. // - splitSize: the size of the split - void TerminalPage::_SplitPane(const winrt::com_ptr& tab, + void TerminalPage::_SplitPane(const winrt::com_ptr& tab, const SplitDirection splitDirection, const float splitSize, std::shared_ptr newPane) @@ -2565,10 +2562,10 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::_ToggleSplitOrientation() { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { _UnZoomIfNeeded(); - terminalTab->ToggleSplitOrientation(); + tabImpl->ToggleSplitOrientation(); } } @@ -2582,10 +2579,10 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::_ResizePane(const ResizeDirection& direction) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { _UnZoomIfNeeded(); - terminalTab->ResizePane(direction); + tabImpl->ResizePane(direction); } } @@ -2597,23 +2594,23 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_ScrollPage(ScrollDirection scrollDirection) { // Do nothing if for some reason, there's no terminal tab in focus. We don't want to crash. - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { if (const auto& control{ _GetActiveControl() }) { const auto termHeight = control.ViewHeight(); auto scrollDelta = _ComputeScrollDelta(scrollDirection, termHeight); - terminalTab->Scroll(scrollDelta); + tabImpl->Scroll(scrollDelta); } } } void TerminalPage::_ScrollToBufferEdge(ScrollDirection scrollDirection) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { auto scrollDelta = _ComputeScrollDelta(scrollDirection, INT_MAX); - terminalTab->Scroll(scrollDelta); + tabImpl->Scroll(scrollDelta); } } @@ -2725,9 +2722,9 @@ namespace winrt::TerminalApp::implementation { if (_settings && _settings.GlobalSettings().SnapToGridOnResize()) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (const auto tabImpl{ _GetFocusedTabImpl() }) { - return terminalTab->CalcSnappedDimension(widthOrHeight, dimension); + return tabImpl->CalcSnappedDimension(widthOrHeight, dimension); } } return dimension; @@ -3343,7 +3340,7 @@ namespace winrt::TerminalApp::implementation // connection, then we'll return nullptr. Otherwise, we'll return a new // Pane for this connection. std::shared_ptr TerminalPage::_MakeTerminalPane(const NewTerminalArgs& newTerminalArgs, - const winrt::TerminalApp::TabBase& sourceTab, + const winrt::TerminalApp::Tab& sourceTab, TerminalConnection::ITerminalConnection existingConnection) { // First things first - Check for making a pane from content ID. @@ -3361,15 +3358,15 @@ namespace winrt::TerminalApp::implementation TerminalSettingsCreateResult controlSettings{ nullptr }; Profile profile{ nullptr }; - if (const auto& terminalTab{ _GetTerminalTabImpl(sourceTab) }) + if (const auto& tabImpl{ _GetTabImpl(sourceTab) }) { - profile = terminalTab->GetFocusedProfile(); + profile = tabImpl->GetFocusedProfile(); if (profile) { // TODO GH#5047 If we cache the NewTerminalArgs, we no longer need to do this. profile = GetClosestProfileForDuplicationOfProfile(profile); controlSettings = TerminalSettings::CreateWithProfile(_settings, profile, *_bindings); - const auto workingDirectory = terminalTab->GetActiveTerminalControl().WorkingDirectory(); + const auto workingDirectory = tabImpl->GetActiveTerminalControl().WorkingDirectory(); const auto validWorkingDirectory = !workingDirectory.empty(); if (validWorkingDirectory) { @@ -3433,7 +3430,7 @@ namespace winrt::TerminalApp::implementation auto debugContent{ winrt::make(profile, _terminalSettingsCache, newControl) }; auto debugPane = std::make_shared(debugContent); - // Since we're doing this split directly on the pane (instead of going through TerminalTab, + // Since we're doing this split directly on the pane (instead of going through Tab, // we need to handle the panes 'active' states // Set the pane we're splitting to active (otherwise Split will not do anything) @@ -3451,7 +3448,7 @@ namespace winrt::TerminalApp::implementation // NOTE: callers of _MakePane should be able to accept nullptr as a return // value gracefully. std::shared_ptr TerminalPage::_MakePane(const INewContentArgs& contentArgs, - const winrt::TerminalApp::TabBase& sourceTab, + const winrt::TerminalApp::Tab& sourceTab, TerminalConnection::ITerminalConnection existingConnection) { @@ -3485,9 +3482,9 @@ namespace winrt::TerminalApp::implementation // Prevent the user from opening a bunch of snippets panes. // // Look at the focused tab, and if it already has one, then just focus it. - if (const auto& focusedTab{ _GetFocusedTab() }) + if (const auto& focusedTab{ _GetFocusedTabImpl() }) { - const auto rootPane{ focusedTab.try_as()->GetRootPane() }; + const auto rootPane{ focusedTab->GetRootPane() }; const bool found = rootPane == nullptr ? false : rootPane->WalkTree([](const auto& p) -> bool { if (const auto& snippets{ p->GetContent().try_as() }) { @@ -3643,21 +3640,21 @@ namespace winrt::TerminalApp::implementation for (const auto& tab : _tabs) { - if (auto terminalTab{ _GetTerminalTabImpl(tab) }) + if (auto tabImpl{ _GetTabImpl(tab) }) { // Let the tab know that there are new settings. It's up to each content to decide what to do with them. - terminalTab->UpdateSettings(_settings); + tabImpl->UpdateSettings(_settings); // Update the icon of the tab for the currently focused profile in that tab. // Only do this for TerminalTabs. Other types of tabs won't have multiple panes // and profiles so the Title and Icon will be set once and only once on init. - _UpdateTabIcon(*terminalTab); + _UpdateTabIcon(*tabImpl); // Force the TerminalTab to re-grab its currently active control's title. - terminalTab->UpdateTitle(); + tabImpl->UpdateTitle(); } - auto tabImpl{ winrt::get_self(tab) }; + auto tabImpl{ winrt::get_self(tab) }; tabImpl->SetActionMap(_settings.ActionMap()); } @@ -3776,7 +3773,7 @@ namespace winrt::TerminalApp::implementation for (const auto& tab : _tabs) { - if (auto tabImpl{ _GetTerminalTabImpl(tab) }) + if (auto tabImpl{ _GetTabImpl(tab) }) { auto tabState{ tabImpl->GetCombinedTaskbarState() }; // lowest priority wins @@ -3819,11 +3816,11 @@ namespace winrt::TerminalApp::implementation _visible = showOrHide; for (const auto& tab : _tabs) { - if (auto terminalTab{ _GetTerminalTabImpl(tab) }) + if (auto tabImpl{ _GetTabImpl(tab) }) { // Manually enumerate the panes in each tab; this will let us recycle TerminalSettings // objects but only have to iterate one time. - terminalTab->GetRootPane()->WalkTree([&](auto&& pane) { + tabImpl->GetRootPane()->WalkTree([&](auto&& pane) { if (auto control = pane->GetTerminalControl()) { control.WindowVisibilityChanged(showOrHide); @@ -3841,7 +3838,7 @@ namespace winrt::TerminalApp::implementation // - tab: the tab where the search box should be created // Return Value: // - - void TerminalPage::_Find(const TerminalTab& tab) + void TerminalPage::_Find(const Tab& tab) { if (const auto& control{ tab.GetActiveTerminalControl() }) { @@ -3959,7 +3956,7 @@ namespace winrt::TerminalApp::implementation _newTabButton.Background(backgroundBrush); _newTabButton.Foreground(foregroundBrush); - // This is just like what we do in TabBase::_RefreshVisualState. We need + // This is just like what we do in Tab::_RefreshVisualState. We need // to manually toggle the visual state, so the setters in the visual // state group will re-apply, and set our currently selected colors in // the resources. @@ -4210,25 +4207,18 @@ namespace winrt::TerminalApp::implementation } // Method Description: - // - Returns a com_ptr to the implementation type of the given tab if it's a TerminalTab. + // - Returns a com_ptr to the implementation type of the given tab if it's a Tab. // If the tab is not a TerminalTab, returns nullptr. // Arguments: // - tab: the projected type of a Tab // Return Value: // - If the tab is a TerminalTab, a com_ptr to the implementation type. // If the tab is not a TerminalTab, nullptr - winrt::com_ptr TerminalPage::_GetTerminalTabImpl(const TerminalApp::TabBase& tab) + winrt::com_ptr TerminalPage::_GetTabImpl(const TerminalApp::Tab& tab) { - if (auto terminalTab = tab.try_as()) - { - winrt::com_ptr tabImpl; - tabImpl.copy_from(winrt::get_self(terminalTab)); - return tabImpl; - } - else - { - return nullptr; - } + winrt::com_ptr tabImpl; + tabImpl.copy_from(winrt::get_self(tab)); + return tabImpl; } // Method Description: @@ -4715,10 +4705,10 @@ namespace winrt::TerminalApp::implementation for (const auto& tab : _tabs) { - if (auto terminalTab{ _GetTerminalTabImpl(tab) }) + if (auto tabImpl{ _GetTabImpl(tab) }) { // The root pane will propagate the theme change to all its children. - if (const auto& rootPane{ terminalTab->GetRootPane() }) + if (const auto& rootPane{ tabImpl->GetRootPane() }) { rootPane->UpdateResources(_paneResources); } @@ -4779,7 +4769,7 @@ namespace winrt::TerminalApp::implementation } // Second: Update the colors of our individual TabViewItems. This - // applies tab.background to the tabs via TerminalTab::ThemeColor. + // applies tab.background to the tabs via Tab::ThemeColor. // // Do this second, so that we already know the bgColor of the titlebar. { @@ -4787,8 +4777,8 @@ namespace winrt::TerminalApp::implementation const auto tabUnfocusedBackground = theme.Tab() ? theme.Tab().UnfocusedBackground() : nullptr; for (const auto& tab : _tabs) { - winrt::com_ptr tabImpl; - tabImpl.copy_from(winrt::get_self(tab)); + winrt::com_ptr tabImpl; + tabImpl.copy_from(winrt::get_self(tab)); tabImpl->ThemeColor(tabBackground, tabUnfocusedBackground, bgColor); } } @@ -5257,8 +5247,8 @@ namespace winrt::TerminalApp::implementation // Get the tab impl from this event. const auto eventTab = e.Tab(); const auto tabBase = _GetTabByTabViewItem(eventTab); - winrt::com_ptr tabImpl; - tabImpl.copy_from(winrt::get_self(tabBase)); + winrt::com_ptr tabImpl; + tabImpl.copy_from(winrt::get_self(tabBase)); if (tabImpl) { // First: stash the tab we started dragging. diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 4879f65fc6..e4bf6efa1e 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -4,7 +4,7 @@ #pragma once #include "TerminalPage.g.h" -#include "TerminalTab.h" +#include "Tab.h" #include "AppKeyBindings.h" #include "AppCommandlineArgs.h" #include "RenameWindowRequestedArgs.g.h" @@ -219,13 +219,13 @@ namespace winrt::TerminalApp::implementation Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; - Windows::Foundation::Collections::IObservableVector _tabs; - Windows::Foundation::Collections::IObservableVector _mruTabs; - static winrt::com_ptr _GetTerminalTabImpl(const TerminalApp::TabBase& tab); + Windows::Foundation::Collections::IObservableVector _tabs; + Windows::Foundation::Collections::IObservableVector _mruTabs; + static winrt::com_ptr _GetTabImpl(const TerminalApp::Tab& tab); void _UpdateTabIndices(); - TerminalApp::TerminalTab _settingsTab{ nullptr }; + TerminalApp::Tab _settingsTab{ nullptr }; bool _isInFocusMode{ false }; bool _isFullscreen{ false }; @@ -277,7 +277,7 @@ namespace winrt::TerminalApp::implementation struct StashedDragData { - winrt::com_ptr draggedTab{ nullptr }; + winrt::com_ptr draggedTab{ nullptr }; winrt::Windows::Foundation::Point dragOffset{ 0, 0 }; } _stashed; @@ -305,7 +305,7 @@ namespace winrt::TerminalApp::implementation void _OpenNewTabDropdown(); HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs); - TerminalApp::TerminalTab _CreateNewTabFromPane(std::shared_ptr pane, uint32_t insertPosition = -1); + TerminalApp::Tab _CreateNewTabFromPane(std::shared_ptr pane, uint32_t insertPosition = -1); std::wstring _evaluatePathForCwd(std::wstring_view path); @@ -328,25 +328,25 @@ namespace winrt::TerminalApp::implementation void _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap) noexcept; void _RegisterActionCallbacks(); - void _UpdateTitle(const TerminalTab& tab); - void _UpdateTabIcon(TerminalTab& tab); + void _UpdateTitle(const Tab& tab); + void _UpdateTabIcon(Tab& tab); void _UpdateTabView(); void _UpdateTabWidthMode(); void _SetBackgroundImage(const winrt::Microsoft::Terminal::Settings::Model::IAppearanceConfig& newAppearance); void _DuplicateFocusedTab(); - void _DuplicateTab(const TerminalTab& tab); + void _DuplicateTab(const Tab& tab); - safe_void_coroutine _ExportTab(const TerminalTab& tab, winrt::hstring filepath); + safe_void_coroutine _ExportTab(const Tab& tab, winrt::hstring filepath); - winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::TabBase tab); + winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::Tab tab); void _CloseTabAtIndex(uint32_t index); - void _RemoveTab(const winrt::TerminalApp::TabBase& tab); - safe_void_coroutine _RemoveTabs(const std::vector tabs); + void _RemoveTab(const winrt::TerminalApp::Tab& tab); + safe_void_coroutine _RemoveTabs(const std::vector tabs); - void _InitializeTab(winrt::com_ptr newTabImpl, uint32_t insertPosition = -1); + void _InitializeTab(winrt::com_ptr newTabImpl, uint32_t insertPosition = -1); void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term); - void _RegisterTabEvents(TerminalTab& hostingTab); + void _RegisterTabEvents(Tab& hostingTab); void _DismissTabContextMenus(); void _FocusCurrentTab(const bool focusAlways); @@ -357,7 +357,7 @@ namespace winrt::TerminalApp::implementation bool _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction); bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction); bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args); - bool _MoveTab(winrt::com_ptr tab, const Microsoft::Terminal::Settings::Model::MoveTabArgs args); + bool _MoveTab(winrt::com_ptr tab, const Microsoft::Terminal::Settings::Model::MoveTabArgs args); template bool _ApplyToActiveControls(F f) @@ -381,21 +381,21 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl(); std::optional _GetFocusedTabIndex() const noexcept; - std::optional _GetTabIndex(const TerminalApp::TabBase& tab) const noexcept; - TerminalApp::TabBase _GetFocusedTab() const noexcept; - winrt::com_ptr _GetFocusedTabImpl() const noexcept; - TerminalApp::TabBase _GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept; + std::optional _GetTabIndex(const TerminalApp::Tab& tab) const noexcept; + TerminalApp::Tab _GetFocusedTab() const noexcept; + winrt::com_ptr _GetFocusedTabImpl() const noexcept; + TerminalApp::Tab _GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept; void _HandleClosePaneRequested(std::shared_ptr pane); - safe_void_coroutine _SetFocusedTab(const winrt::TerminalApp::TabBase tab); + safe_void_coroutine _SetFocusedTab(const winrt::TerminalApp::Tab tab); safe_void_coroutine _CloseFocusedPane(); - void _ClosePanes(weak_ref weakTab, std::vector paneIds); + void _ClosePanes(weak_ref weakTab, std::vector paneIds); winrt::Windows::Foundation::IAsyncOperation _PaneConfirmCloseReadOnly(std::shared_ptr pane); void _AddPreviouslyClosedPaneOrTab(std::vector&& args); void _Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference& rowsToScroll); - void _SplitPane(const winrt::com_ptr& tab, + void _SplitPane(const winrt::com_ptr& tab, const Microsoft::Terminal::Settings::Model::SplitDirection splitType, const float splitSize, std::shared_ptr newPane); @@ -439,14 +439,14 @@ namespace winrt::TerminalApp::implementation void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs); void _OnTabCloseRequested(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabCloseRequestedEventArgs& eventArgs); void _OnFirstLayout(const IInspectable& sender, const IInspectable& eventArgs); - void _UpdatedSelectedTab(const winrt::TerminalApp::TabBase& tab); + void _UpdatedSelectedTab(const winrt::TerminalApp::Tab& tab); void _UpdateBackground(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile); void _OnDispatchCommandRequested(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::Command& command); void _OnCommandLineExecutionRequested(const IInspectable& sender, const winrt::hstring& commandLine); - void _OnSwitchToTabRequested(const IInspectable& sender, const winrt::TerminalApp::TabBase& tab); + void _OnSwitchToTabRequested(const IInspectable& sender, const winrt::TerminalApp::Tab& tab); - void _Find(const TerminalTab& tab); + void _Find(const Tab& tab); winrt::Microsoft::Terminal::Control::TermControl _CreateNewControlAndContent(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection); @@ -455,10 +455,10 @@ namespace winrt::TerminalApp::implementation TerminalApp::IPaneContent _makeSettingsContent(); std::shared_ptr _MakeTerminalPane(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr, - const winrt::TerminalApp::TabBase& sourceTab = nullptr, + const winrt::TerminalApp::Tab& sourceTab = nullptr, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); std::shared_ptr _MakePane(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs = nullptr, - const winrt::TerminalApp::TabBase& sourceTab = nullptr, + const winrt::TerminalApp::Tab& sourceTab = nullptr, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); void _RefreshUIForSettingsReload(); @@ -475,7 +475,7 @@ namespace winrt::TerminalApp::implementation static int _ComputeScrollDelta(ScrollDirection scrollDirection, const uint32_t rowsToScroll); static uint32_t _ReadSystemRowsToScroll(); - void _UpdateMRUTab(const winrt::TerminalApp::TabBase& tab); + void _UpdateMRUTab(const winrt::TerminalApp::Tab& tab); void _TryMoveTab(const uint32_t currentTabIndex, const int32_t suggestedNewTabIndex); @@ -534,7 +534,7 @@ namespace winrt::TerminalApp::implementation void _onTabDroppedOutside(winrt::Windows::Foundation::IInspectable sender, winrt::Microsoft::UI::Xaml::Controls::TabViewTabDroppedOutsideEventArgs e); void _DetachPaneFromWindow(std::shared_ptr pane); - void _DetachTabFromWindow(const winrt::com_ptr& terminalTab); + void _DetachTabFromWindow(const winrt::com_ptr& tabImpl); void _MoveContent(std::vector&& actions, const winrt::hstring& windowName, const uint32_t tabIndex, @@ -546,9 +546,9 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Controls::MenuFlyout _CreateRunAsAdminFlyout(int profileIndex); winrt::Microsoft::Terminal::Control::TermControl _senderOrActiveControl(const winrt::Windows::Foundation::IInspectable& sender); - winrt::com_ptr _senderOrFocusedTab(const IInspectable& sender); + winrt::com_ptr _senderOrFocusedTab(const IInspectable& sender); - void _activePaneChanged(winrt::TerminalApp::TerminalTab tab, Windows::Foundation::IInspectable args); + void _activePaneChanged(winrt::TerminalApp::Tab tab, Windows::Foundation::IInspectable args); safe_void_coroutine _doHandleSuggestions(Microsoft::Terminal::Settings::Model::SuggestionsArgs realArgs); #pragma region ActionHandlers diff --git a/src/cascadia/TerminalApp/TerminalTab.idl b/src/cascadia/TerminalApp/TerminalTab.idl deleted file mode 100644 index 5dbd845d49..0000000000 --- a/src/cascadia/TerminalApp/TerminalTab.idl +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import "TabBase.idl"; -import "TerminalTabStatus.idl"; - -namespace TerminalApp -{ - [default_interface] runtimeclass TerminalTab : TabBase - { - TerminalTabStatus TabStatus { get; }; - } -} diff --git a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj index 6a20f0ac9b..282f68a589 100644 --- a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj @@ -36,7 +36,6 @@ - From 48b796f102420dea6dbf605011ff6cd5a1a666cf Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Wed, 23 Jul 2025 19:07:11 +0200 Subject: [PATCH 50/74] [UX] Settings UI refinements (#19001) This PR is a (first) design pass on the Terminal settings UX to bring consistency and alignment with the rest of the OS and other settings surfaces. High-level changes include: - Using WinUI brushes vs. UWP/OS brushes. [See brushes overview in the WinUI 3 Gallery:](winui3gallery://item/Color). - Updating pixel values for margins, fontsizes, paddings etc. using units of 4. See [the design guidelines for more info](https://learn.microsoft.com/en-us/windows/apps/design/basics/content-basics). - Adding rounded corners on various elements to be consistent with the Windows 11 look and feel (using the ControlCornerRadius or OverlayCornerRadius WinUI resources as much as possible). - Decreasing the page header titles / breadcrumb so it feels a bit less cramped. - Fixing a bug where the title of the page was not aligned with the content when resizing the window (note to self: should fix this in PowerToys too): - Ensuring the subheader texts for settings categories are inline with the rest of the OS (== decreasing the fontsize). --------- Co-authored-by: Carlos Zamora --- .../TerminalSettingsEditor/Actions.xaml | 28 +++++---- .../TerminalSettingsEditor/ColorSchemes.xaml | 26 ++++----- .../CommonResources.xaml | 57 ++++++++++--------- .../EditColorScheme.xaml | 17 +++--- .../TerminalSettingsEditor/Extensions.xaml | 21 ++++--- .../TerminalSettingsEditor/Launch.xaml | 6 +- .../TerminalSettingsEditor/MainPage.xaml | 57 ++++++++++--------- .../TerminalSettingsEditor/NewTabMenu.xaml | 13 ++--- .../NullableColorPicker.xaml | 6 +- .../Profiles_Advanced.xaml | 10 ++-- .../Profiles_Appearance.xaml | 27 ++++----- .../TerminalSettingsEditor/Profiles_Base.xaml | 17 +++--- .../Profiles_Base_Orphaned.xaml | 2 +- .../Profiles_Terminal.xaml | 2 - .../SettingContainerStyle.xaml | 26 ++++----- 15 files changed, 160 insertions(+), 155 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index 5eb09daf42..b712170a1c 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -56,13 +56,14 @@ @@ -97,13 +98,15 @@ @@ -138,7 +141,7 @@ 32 - 15 + 14 - -