Compare commits

..

21 Commits

Author SHA1 Message Date
Dustin L. Howett
49b80b48d6 Port the nightly build pipeline to OneBranch as well (#16108)
This pull request also removes the original release and nightly
pipelines, but it does not remove the release pipeline _template_.

I had to demote the Azure job from being a _deployment_ to being a plain
old job, unfortunately. Alas! Review with whitespace disabled (or `git
diff -w`).

(cherry picked from commit 775e7ebe1f)
Service-Card-Id: 92465420
Service-Version: 1.19
2024-04-30 15:26:08 -05:00
James Holderness
244549acdc Fix the DECTCEM reset position in the conpty stream (#17148)
## Summary of the Pull Request

When the conpty renderer determines that it needs to hide the cursor,
it does so by inserting a `DECTCEM` reset sequence at the start of the
output buffer, assuming that is the start of the frame. But when the
`_noFlushOnEnd` flag is set, you can have multiple frames pending in the
buffer, and the `DECTCEM` sequence will then end up in the wrong place.

This PR fixes the issue by saving the buffer size at the start of the
frame, and using that saved offset as the insert position for the
`DECTCEM` sequence.

## Validation Steps Performed

I have a game that was frequently affected by this issue (the cursor
would be visible when it was meant to be hidden). With this PR applied,
it now works perfectly.

## PR Checklist
- [x] Closes #15449

(cherry picked from commit ef318a1450)
Service-Card-Id: 92457089
Service-Version: 1.19
2024-04-30 14:16:10 -05:00
Dustin L. Howett
d97f49bbdb Revert "Properly fix ConPTY buffer corking (#16793)"
This reverts commit aaecea7c1c.
2024-04-30 14:15:09 -05:00
Dustin L. Howett
2660585490 Update Cascadia Code to 2404.23 (#17137)
This update adds support for:

- Unicode 16 Large Type Pieces (they are really cool, you *have* to see
them)
- Unicode 13 Sextants (U+1FB00 - U+1FB3B)
- Octants, sedecimants, eights, miscellanrous blocks, separated
quadrants and sextants, and diagonals
- Segmented digits (think LED numbers)
- Checkerboards

It also fixes the coordinate system used in all of the blocks,
half-blocks, quadrants and eights for consistency.

This update does **not** include the new "Nerd Fonts" variant of
Cascadia Code or Cascadia Mono.

With big thanks to @PhMajerus for contributing all of the new symbols
for legacy computing.

See microsoft/cascadia-code#723, microsoft/cascadia-code#708 and
microsoft/cascadia-code#727 for more details.

(cherry picked from commit 41bb28c46d)
Service-Card-Id: 92434844
Service-Version: 1.19
2024-04-26 11:32:36 -05:00
Dustin L. Howett
5257e89f1b build: disable CheckCFlags for now, as it is blowing up the build (#17116)
OneBranch no likey. A test build is running now.

(cherry picked from commit 19f43f70bd)
Service-Card-Id: 92421263
Service-Version: 1.19
2024-04-26 11:32:34 -05:00
PankajBhojwani
bf0bc91789 Update Azure Cloud Shell API to the newer version (#17115)
Updates the `api-version` to `2023-02-01-preview` when requesting for
CloudShell settings and shell

## Validation Steps Performed
Can still use Azure Cloud Shell through Windows Terminal

(cherry picked from commit ce4e0df7b0)
Service-Card-Id: 92411551
Service-Version: 1.19
2024-04-26 11:32:13 -05:00
Leonard Hecker
42639f4bb6 Add more TraceLogging to ApiDispatchers (#17085)
More TraceLogging = More better?
I made this change as I noticed that most calls are not being logged.
Even after this change some crucial information won't be logged
(for instance arrays of `INPUT_RECORD`), because I couldn't come up
with a clever way to do so, but I think this is better than nothing.

(cherry picked from commit f49cf44b79)
Service-Card-Id: 92374414
Service-Version: 1.19
2024-04-22 17:35:56 -05:00
Tushar Singh
5e4f34330d Fix window style under minimized state (#17058)
Closes: #13961

This PR changes the window styling we use under the minimized state. We
now retain the active window styling so the content's height doesn't
change when switching between minimized and maximized states.

## Validation Steps Performed
- Open Terminal and go into Maximized mode.
- Click on the Minimize button.
- No `SizeChanged` event in `ControlCore`.
- Click on the WT icon in the taskbar to restore it.
- No `SizeChanged` event in `ControlCore`.

(cherry picked from commit 11c4aa459d)
Service-Card-Id: 92350318
Service-Version: 1.19
2024-04-22 17:35:56 -05:00
Windows Console Service Bot
39f90921fe Localization Updates - main - associated with #16886 (#17035)
(cherry picked from commit f4d8a74082)
Service-Card-Id: 92350382
Service-Version: 1.19
2024-04-16 10:46:41 -05:00
Harsh Narayan Jha
3a9024b19b Fix: UI style errors: Menu items capitalization and … (ellipses) mark misuse (#16886)
I changed the improper capitalization and misuse of ellipses mark (...)
in the context menu and various other places.

Fixes issues #16819 and #16846

## PR Checklist
- [ ] Closes #16846
- [x] Tests added/passed - NA
- [x] Documentation updated - NA
- If checked, please file a pull request on [our docs
repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
- [x] Schema updated (if necessary) - NA

(cherry picked from commit 8bd9578b3c)
Service-Card-Id: 92350386
Service-Version: 1.19
2024-04-16 10:46:37 -05:00
Windows Console Service Bot
d5094c1b50 Localization Updates - main - 04/06/2024 03:04:16 (#17025)
(cherry picked from commit b90eb93d26)
Service-Card-Id: 92279782
Service-Version: 1.19
2024-04-16 10:46:36 -05:00
Dustin L. Howett
c6c0b41438 ci/rel: publish symbols using the internal symbol request API instead (#16991)
Work is ongoing to remove individually-authenticated service accounts
from some pipelines. This moves us closer to that goal.

Tested in Nightly 2403.28002.

(cherry picked from commit 2bcbe6b492)
Service-Card-Id: 92239367
Service-Version: 1.19
2024-04-16 10:46:35 -05:00
Leonard Hecker
aaecea7c1c Properly fix ConPTY buffer corking (#16793)
I've found that #16079 was never properly addressed (it still randomly
occurred after even after PR #16349), which later led to the issues
described in #16769 (nushell flickering due to too many flushes).

The crux of the fix is that this brings back the `_noFlushOnEnd` flag
that was removed in PR #15991. This is then combined with a change to
the cork API: An `uncork` on `VtEngine` now only flushes if `_Flush`
got called while it was corked in the first place.

`_noFlushOnEnd` prevents us from flushing in between two "unknown"
VT sequences (like soft fonts or FTCS) which prevents them from being
corrupted. The corking prevents the remaining cases of flushing too
often. Long-term, a proper fix would be to pass through VT unmodified.

Closes #16769

(cherry picked from commit 1ede023331)
Service-Card-Id: 91965216
Service-Version: 1.19
2024-04-16 10:46:00 -05:00
Leonard Hecker
724bfa77cd Make ploc translations predictable (#16924)
(cherry picked from commit 77d5e23ef2)
Service-Card-Id: 92350444
Service-Version: 1.19
2024-04-16 10:45:54 -05:00
Isaac Blanco
280ff09762 Update CommandPalette.cpp to ignore _filterTextChanged on TabSwitchMode (#16858)
As mentioned in #11146, when the "Next/Prev" command is executed from
the command line with a string in the search bar, this is setting always
the first tab.

When using the command "Next/Previous Tab" from the command line, we are
creating another tab (as if we are using the keyboard shortcut), and
this triggers the `_filterTextChanged` that resets the index to the
first item in because the current mode that it has.

This could be cause because, It seems that it detects as if we are
deleting the entered letter or creating an empty string, causing the
execution of the mentioned method and resetting its index to 0.

To avoid this, we are making sure that when this action is triggerd and
we are in the `TabSwitchMode`, we should ignore the following execution
of the method.

## Validation Steps Performed
I tested out the following scenarios:
1. Performing the action with the keyboard shorcut
2. Perfoming the action with an empty string
3. Performing the action with a string in the search bar.

Also validated with the current tests.

Closes #11146

(cherry picked from commit 806d5e2d05)
Service-Card-Id: 92139799
Service-Version: 1.19
2024-03-21 15:43:05 -05:00
Dustin L. Howett
24cb2df500 build: roll back to a build container with the 14.38 compiler (#16907)
The 14.39 compiler seems pretty busted.

Refs #16794

(cherry picked from commit ff47e0c257)
Service-Card-Id: 92139754
Service-Version: 1.19
2024-03-20 15:58:11 -05:00
Leonard Hecker
b05ea7c7ce Fix a ReadConsoleOutputCharacter regression (#16898)
The `nLength` parameter of `ReadConsoleOutputCharacterW` indicates
the number of columns that should be read. For single-column (narrow)
surrogate pairs this previously clipped a trailing character of the
returned string. In the major Unicode support update in #13626
surrogate pairs truly got stored as atomic units for the first time.
This now meant that a 120 column read with such codepoints resulted
in 121 characters. Other parts of conhost still assume UCS2 however,
and so this results in the entire read failing.

This fixes the issue by turning surrogate pairs into U+FFFD
which makes it UCS2 compatible.

Closes #16892

* Write U+F15C0 and read it back with `ReadConsoleOutputCharacterW`
* Read succeeds with a single U+FFFD 

(cherry picked from commit 373faf00c9)
Service-Card-Id: 92129542
Service-Version: 1.19
2024-03-20 15:58:10 -05:00
Windows Console Service Bot
17e2d0df18 Localization Updates - main - 03/08/2024 20:28:42 (#16847)
(cherry picked from commit c238416ae1)
Service-Card-Id: 92019655
Service-Version: 1.19
2024-03-08 15:08:17 -06:00
Dustin L. Howett
29099c6324 Check the localizations into the project nightly (#16835)
Right now, the localization submission pipeline runs every night and
sends our localizable resources over to Touchdown. Later, release builds
pick up the localizations directly from Touchdown, move them into place,
and consume them.

This allowed us to avoid having localized content in the repository, but
it came with too many downsides:

- Users could not contribute additional localizations very easily
- We use the same release pipeline and Touchdown configuration for every
  branch, so strings needed to either slightly match or _entirely match_
  across an entire set of active release branches
- Building from day to day can pull in different strings, making the
  product not reproduceable
- Calling TDBuild during release builds requires network access from the
  build machine (so does restoring NuGet packages, but that's neither
  here nor there)
- Local developers and users could not test out other languages

This pull request moves all localization processing into the nightly
build phase and introduces support for checking loc in and submitting a
pull request. The pull request will not be created anew if one already
exists which has not been merged.

Anything we needed to do on release is now done overnight. This includes
moving loc files into the right places and merging the Cascadia
resources with the Context Menu resources (so that we can work around a
relatively lower amount of translations being chosen for the app versus
the context menu; see #12491 for more info.)

There are some smaller downsides to this approach and its
implementation:

- The first commit is going to be huge
- Right now, it only manages a single branch and uses a force push; if a
  PR is not reviewed timely, it will be force-pushed and you cannot see
  the day-to-day changes in the strings. Hopefully there won't be any.

I've taken great care to ensure that repeated runs of this new pipeline
will not result in unnecessary whitespace changes. This required
changing how we merge ContextMenu.resw into CascadiaPackage to always
use the .NET XmlWriter with specific flags.

NOTE that this does not allow users to _contribute_ translation fixes
for the 10 languages which we are importing. We will still need to pull
changes out of those files and submit them as bugs to the localization
team separately, and hope they come back around in another nightly
build. However, there is no reason users cannot contribute
_non-Touchdown_ languages.

(cherry picked from commit ab4b140aa4)
Service-Card-Id: 92019664
Service-Version: 1.19
2024-03-08 15:08:15 -06:00
James Holderness
35cda311a6 Fix conpty rendering of control characters in the buffer (#16825)
When using the legacy console APIs, it's possible to write arbitrary
codepoints into the buffer. If any of those codepoints are in the C0 or
C1 range, and the buffer contents are forwarded over conpty, they can
end up mistakenly interpreted as controls by the connected terminal.

This PR fixes that issue by converting any C0 and C1 codepoints in the
buffer into printable glyphs before forwarding them over conpty. I've
used the C0 glyphs from the DOS 437 codepage and just a `?` for the C1
codepoints, since that's what you would typically have seen in the v1
console with a raster font.

Although this doesn't address the main problem in #16410, it should at
least fix the rendering issues they're seeing when running their app in
Windows Terminal.

I've confirmed that the test case in #4363 now looks the same in Windows
Terminal as it does in conhost, and I've tested the Windows version of
the terminal game [Gorched], and confirmed that it now works correctly
in Window Terminal.

[Gorched]: https://github.com/zladovan/gorched

Closes #4363
Closes #6265

(cherry picked from commit 563b7312b6)
Service-Card-Id: 92001661
Service-Version: 1.19
2024-03-08 15:07:37 -06:00
Leonard Hecker
9acb15b626 Fix bugs in CharToColumnMapper (#16787)
Aside from overall simplifying `CharToColumnMapper` this fixes 2 bugs:
* The backward search loop may have iterated 1 column too far,
  because it didn't stop at `*current <= *target`, but rather at
  `*(current - 1) <= *target`. This issue was only apparent when
  surrogate pairs were being used in a row.
* When the target offset is that of a trailing surrogate pair
  the forward search loop may have iterated 1 column too far.
  It's somewhat unlikely for this to happen since this code is
  only used through ICU, but you never know.

This is a continuation of PR #16775.

(cherry picked from commit 043d5cd484)
Service-Card-Id: 91955617
Service-Version: 1.19
2024-02-29 16:53:29 -06:00
524 changed files with 22514 additions and 20932 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"XamlStyler.Console": {
"version": "3.2311.2",
"version": "3.2206.4",
"commands": [
"xstyler"
]

View File

@@ -6,6 +6,8 @@
By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.
:warning: The command is written for posix shells. If it doesn't work for you, you can manually _add_ (one word per line) / _remove_ items to `expect.txt` and the `excludes.txt` files.
If the listed items are:
* ... **misspelled**, then please *correct* them instead of using the command.
@@ -34,9 +36,7 @@ https://www.regexplanet.com/advanced/perl/) yours before committing to verify it
* well-formed pattern.
If you can write a [pattern](
https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
) that would match it,
If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
try adding it to the `patterns.txt` file.
Patterns are Perl 5 Regular Expressions - you can [test](

View File

@@ -18,8 +18,6 @@ consvc
copyable
Counterintuitively
CtrlDToClose
CVS
CUI
cybersecurity
dalet
Dcs
@@ -58,7 +56,6 @@ hyperlinks
iconify
img
inlined
issuetitle
It'd
kje
libfuzzer
@@ -83,32 +80,25 @@ noreply
ogonek
ok'd
overlined
perlw
pipeline
postmodern
Powerline
powerline
ptys
pwshw
qof
qps
Remappings
Retargets
rclt
reimplementation
reserialization
reserialize
reserializes
rlig
rubyw
runtimes
servicebus
shcha
similaritytolerance
slnt
Sos
ssh
sustainability
stakeholders
sxn
timeline
@@ -121,7 +111,6 @@ toolset
truthiness
tshe
ubuntu
UEFI
uiatextrange
UIs
und
@@ -133,7 +122,6 @@ walkthroughs
We'd
westus
wildcards
workarounds
XBox
YBox
yeru

View File

@@ -1,12 +1,9 @@
aalt
abvm
ACCEPTFILES
ACCESSDENIED
acl
aclapi
alignas
alignof
allocconsolewithoptions
APPLYTOSUBMENUS
appxrecipe
bitfield
@@ -24,9 +21,7 @@ COLORPROPERTY
colspan
COMDLG
commandlinetoargv
commoncontrols
comparand
COPYFROMRESOURCE
cstdint
CXICON
CYICON
@@ -38,7 +33,6 @@ delayimp
DERR
dlldata
DNE
dnom
DONTADDTORECENT
DWMSBT
DWMWA
@@ -47,12 +41,10 @@ endfor
ENDSESSION
enumset
environstrings
EXACTSIZEONLY
EXPCMDFLAGS
EXPCMDSTATE
filetime
FILTERSPEC
fina
FORCEFILESYSTEM
FORCEMINIMIZE
frac
@@ -66,7 +58,6 @@ Hashtable
HIGHCONTRASTON
HIGHCONTRASTW
hinternet
HIGHQUALITYSCALE
HINTERNET
hotkeys
href
@@ -83,7 +74,6 @@ IBox
IClass
IComparable
IComparer
ICONINFO
IConnection
ICustom
IDialog
@@ -93,7 +83,6 @@ IExplorer
IFACEMETHOD
IFile
IGraphics
IImage
IInheritable
IMap
IMonarch
@@ -124,7 +113,6 @@ LSHIFT
LTGRAY
MAINWINDOW
MAXIMIZEBOX
medi
memchr
memicmp
MENUCOMMAND
@@ -155,7 +143,6 @@ NOTIFYBYPOS
NOTIFYICON
NOTIFYICONDATA
ntprivapi
numr
oaidl
ocidl
ODR
@@ -169,7 +156,6 @@ OUTLINETEXTMETRICW
overridable
PACL
PAGESCROLL
PALLOC
PATINVERT
PEXPLICIT
PICKFOLDERS
@@ -181,11 +167,9 @@ REGCLS
RETURNCMD
rfind
RLO
rnrn
ROOTOWNER
roundf
RSHIFT
rvrn
SACL
schandle
SEH
@@ -226,7 +210,6 @@ tlg
TME
tmp
tmpdir
tokeninfo
tolower
toupper
TRACKMOUSEEVENT

View File

@@ -14,13 +14,13 @@ autoexec
backplating
bitmaps
BOMs
checkcflags
COMPUTERNAME
CPLs
cpptools
cppvsdbg
CPRs
cryptbase
cscript
DACL
DACLs
defaultlib
@@ -32,6 +32,7 @@ DWINRT
enablewttlogging
HOMESHARE
Intelli
issecret
IVisual
libucrt
libucrtd
@@ -46,7 +47,6 @@ MSAA
msixbundle
MSVC
MSVCP
mtu
muxc
netcore
Onefuzz
@@ -74,6 +74,7 @@ sid
Skype
SRW
sxs
symbolrequestprod
Sysinternals
sysnative
systemroot
@@ -90,12 +91,11 @@ Virtualization
visualstudio
vscode
VSTHRD
WINBASEAPI
winsdkver
wlk
wscript
wslpath
wtl
wtt
wttlog
Xamarin
xfgcheck

View File

@@ -6,7 +6,6 @@ bhoj
Bhojwani
Bluloco
carlos
craigloewen
dhowett
Diviness
dsafa

View File

@@ -1,37 +1,23 @@
# marker to ignore all code on line
^.*/\* #no-spell-check-line \*/.*$
# marker to ignore all code on line
^.*\bno-spell-check(?:-line|)(?:\s.*|)$
# https://cspell.org/configuration/document-settings/
# cspell inline
^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b
# marker for ignoring a comment to the end of the line
// #no-spell-check.*$
# patch hunk comments
^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .*
# git index header
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 url wrappings
\burl\([^)]+\)
index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
# cid urls
(['"])cid:.*?\g{-1}
# data url in parens
#\(data:(?:[^) ][^)]*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
# data url in quotes
([`'"])data:(?:[^ `'"].*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
([`'"])data:.*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
# data url
data:[-a-zA-Z=;:/0-9+]*,\S*
# 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,}
@@ -49,9 +35,6 @@ magnet:[?=:\w]+
# asciinema
\basciinema\.org/a/[0-9a-zA-Z]+
# asciinema v2
^\[\d+\.\d+, "[io]", ".*"\]$
# apple
\bdeveloper\.apple\.com/[-\w?=/]+
# Apple music
@@ -106,7 +89,7 @@ vpc-\w+
# Google Drive
\bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]*
# Google Groups
\bgroups\.google\.com(?:/[a-z]+/(?:#!|)[^/\s"]+)*
\bgroups\.google\.com/(?:(?:forum/#!|d/)(?:msg|topics?|searchin)|a)/[^/\s"]+/[-a-zA-Z0-9$]+(?:/[-a-zA-Z0-9]+)*
# Google Maps
\bmaps\.google\.com/maps\?[\w&;=]*
# Google themes
@@ -134,8 +117,6 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
# GitHub SHAs
\bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b
# GitHub SHA refs
\[([0-9a-f]+)\]\(https://(?:www\.|)github.com/[-\w]+/[-\w]+/commit/\g{-1}[0-9a-f]*
# GitHub wiki
\bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b
# githubusercontent
@@ -147,9 +128,9 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
# git.io
\bgit\.io/[0-9a-zA-Z]+
# GitHub JSON
"node_id": "[-a-zA-Z=;:/0-9+_]*"
"node_id": "[-a-zA-Z=;:/0-9+]*"
# Contributor
\[[^\]]+\]\(https://github\.com/[^/\s"]+/?\)
\[[^\]]+\]\(https://github\.com/[^/\s"]+\)
# GHSA
GHSA(?:-[0-9a-z]{4}){3}
@@ -162,8 +143,8 @@ GHSA(?:-[0-9a-z]{4}){3}
# GitLab commits
\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b
# binance
accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
# binanace
accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
# bitbucket diff
\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+
@@ -299,9 +280,9 @@ slack://[a-zA-Z0-9?&=]+
\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+
# ipfs protocol
ipfs://[0-9a-zA-Z]{3,}
ipfs://[0-9a-z]*
# ipfs url
/ipfs/[0-9a-zA-Z]{3,}
/ipfs/[0-9a-z]*
# w3
\bw3\.org/[-0-9a-zA-Z/#.]+
@@ -378,33 +359,22 @@ ipfs://[0-9a-zA-Z]{3,}
# tinyurl
\btinyurl\.com/\w+
# codepen
\bcodepen\.io/[\w/]+
# registry.npmjs.org
\bregistry\.npmjs\.org/(?:@[^/"']+/|)[^/"']+/-/[-\w@.]+
# getopts
\bgetopts\s+(?:"[^"]+"|'[^']+')
# ANSI color codes
(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m
(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m
# URL escaped characters
\%[0-9A-F][A-F](?=[A-Za-z])
# lower URL escaped characters
\%[0-9a-f][a-f](?=[a-z]{2,})
\%[0-9A-F][A-F]
# IPv6
#\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
# c99 hex digits (not the full format, just one I've seen)
0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP]
# Punycode
\bxn--[-0-9a-z]+
# sha
sha\d+:[0-9]*[a-f]{3,}[0-9a-f]*
# sha-... -- uses a fancy capture
(\\?['"]|&quot;)[0-9a-f]{40,}\g{-1}
(['"]|&quot;)[0-9a-f]{40,}\g{-1}
# hex runs
\b[0-9a-fA-F]{16,}\b
# hex in url queries
@@ -419,21 +389,18 @@ sha\d+:[0-9]*[a-f]{3,}[0-9a-f]*
# Well known gpg keys
.well-known/openpgpkey/[\w./]+
# pki
-----BEGIN.*-----END
# 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)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b
# integrity
integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1}
integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}"
# https://www.gnu.org/software/groff/manual/groff.html
# man troff content
\\f[BCIPR]
# '/"
\\\([ad]q
# '
\\\(aq
# .desktop mime types
^MimeTypes?=.*$
@@ -442,33 +409,21 @@ integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1}
# Localized .desktop content
Name\[[^\]]+\]=.*
# IServiceProvider / isAThing
\b(?:I|isA)(?=(?:[A-Z][a-z]{2,})+\b)
# IServiceProvider
\bI(?=(?:[A-Z][a-z]{2,})+\b)
# crypt
(['"])\$2[ayb]\$.{56}\g{-1}
"\$2[ayb]\$.{56}"
# scrypt / argon
\$(?:scrypt|argon\d+[di]*)\$\S+
# go.sum
\bh1:\S+
# scala modules
("[^"]+"\s*%%?\s*){2,3}"[^"]+"
# Input to GitHub JSON
content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}
content: "[-a-zA-Z=;:/0-9+]*="
# 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
#%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA]|p)(?=[a-zA-Z]{2,}))(?=[_a-zA-Z]+\b)(?!%)(?=.*?['"])
# Python string prefix / binary prefix
# Python stringprefix / binaryprefix
# 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
(?<!')\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'(?=[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
(?<!')\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
# Regular expressions for (P|p)assword
\([A-Z]\|[a-z]\)[a-z]+
@@ -484,35 +439,16 @@ content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}
^\s*/\\[b].*/[gim]*\s*(?:\)(?:;|$)|,$)
# javascript replace regex
\.replace\(/[^/\s"]*/[gim]*\s*,
# assign regex
= /[^*]*?(?:[a-z]{3,}|[A-Z]{3,}|[A-Z][a-z]{2,}).*/
# perl regex test
[!=]~ (?:/.*/|m\{.*?\}|m<.*?>|m([|!/@#,;']).*?\g{-1})
# perl qr regex
(?<!\$)\bqr(?:\{.*?\}|<.*?>|\(.*?\)|([|!/@#,;']).*?\g{-1})
# Go regular expressions
regexp?\.MustCompile\(`[^`]*`\)
# regex choice
\(\?:[^)]+\|[^)]+\)
# proto
^\s*(\w+)\s\g{-1} =
# sed regular expressions
sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2}
# node packages
(["'])\@[^/'" ]+/[^/'" ]+\g{-1}
# go install
go install(?:\s+[a-z]+\.[-@\w/.]+)+
# jetbrains schema https://youtrack.jetbrains.com/issue/RSRP-489571
urn:shemas-jetbrains-com
# kubernetes pod status lists
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
@@ -524,47 +460,19 @@ urn:shemas-jetbrains-com
-[0-9a-f]{10}-\w{5}\s
# posthog secrets
([`'"])phc_[^"',]+\g{-1}
posthog\.init\((['"])phc_[^"',]+\g{-1},
# xcode
# xcodeproject scenes
(?:Controller|destination|ID|id)="\w{3}-\w{2}-\w{3}"
(?:Controller|ID|id)="\w{3}-\w{2}-\w{3}"
# xcode api botches
customObjectInstantitationMethod
# configure flags
.* \| --\w{2,}.*?(?=\w+\s\w+)
# font awesome classes
\.fa-[-a-z0-9]+
# bearer auth
(['"])Bear[e][r] .*?\g{-1}
# basic auth
(['"])Basic [-a-zA-Z=;:/0-9+]{3,}\g{-1}
# base64 encoded content
#([`'"])[-a-zA-Z=;:/0-9+]+=\g{-1}
# base64 encoded content in xml/sgml
>[-a-zA-Z=;:/0-9+]+=</
# base64 encoded content, possibly wrapped in mime
#(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
# encoded-word
=\?[-a-zA-Z0-9"*%]+\?[BQ]\?[^?]{0,75}\?=
# Time Zones
\b(?:Africa|Atlantic|America|Antarctica|Asia|Australia|Europe|Indian|Pacific)(?:/\w+)+
# linux kernel info
^(?:bugs|flags|Features)\s+:.*
# systemd mode
systemd.*?running in system mode \([-+].*\)$
# 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:
@@ -575,62 +483,32 @@ systemd.*?running in system mode \([-+].*\)$
(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])*
# Non-English
[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
# highlighted letters
\[[A-Z]\][a-z]+
[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
# French
# This corpus only had capital letters, but you probably want lowercase ones as well.
\b[LN]'+[a-z]{2,}\b
# latex (check-spelling <= 0.0.21)
#\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+)
# latex (check-spelling >= 0.0.22)
\\\w{2,}\{
# eslint
"varsIgnorePattern": ".+"
# Windows short paths
[/\\][^/\\]{5,6}~\d{1,2}[/\\]
# in check-spelling@v0.0.22+, printf markers aren't automatically consumed
# printf markers
#(?<!\\)\\[nrt](?=[a-z]{2,})
# alternate markers if you run into latex and friends
#(?<!\\)\\[nrt](?=[a-z]{2,})(?=.*['"`])
# apache
a2(?:en|dis)
# weak e-tag
W/"[^"]+"
# latex
\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+)
# the negative lookahead here is to allow catching 'templatesz' as a misspelling
# but to otherwise recognize a Windows path with \templates\foo.template or similar:
#\\(?:necessary|r(?:eport|esolve[dr]?|esult)|t(?:arget|emplates?))(?![a-z])
\\(?:necessary|r(?:eport|esolve[dr]?|esult)|t(?:arget|emplates?))(?![a-z])
# ignore long runs of a single character:
\b([A-Za-z])\g{-1}{3,}\b
# Note that the next example is no longer necessary if you are using
# to match a string starting with a `#`, use a character-class:
[#]backwards
# version suffix <word>v#
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
# 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,})
# 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,}))
# Compiler flags (Scala)
(?:^|[\t ,>"'`=(])-J-[DPWXY](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
# Compiler flags
#(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
# Compiler flags (linker)
,-B
# curl arguments
\b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
# set arguments

View File

@@ -1,24 +1,21 @@
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes
(?:(?i)\.png$)
(?:^|/)(?i)COPYRIGHT
(?:^|/)(?i)LICEN[CS]E
(?:^|/)3rdparty/
(?:^|/)dirs$
(?:^|/)go\.mod$
(?:^|/)go\.sum$
(?:^|/)package(?:-lock|)\.json$
(?:^|/)Pipfile$
(?:^|/)pyproject.toml
(?:^|/)requirements(?:-dev|-doc|-test|)\.txt$
(?:^|/)sources(?:|\.dep)$
(?:^|/)vendor/
\.a$
\.ai$
\.all-contributorsrc$
\.avi$
\.bmp$
\.bz2$
\.cer$
\.class$
\.coveragerc$
\.crl$
\.crt$
\.csr$
@@ -30,15 +27,11 @@
\.eps$
\.exe$
\.gif$
\.git-blame-ignore-revs$
\.gitattributes$
\.gitignore$
\.gitkeep$
\.graffle$
\.gz$
\.icns$
\.ico$
\.ipynb$
\.jar$
\.jks$
\.jpeg$
@@ -48,62 +41,61 @@
\.lock$
\.map$
\.min\..
\.mo$
\.mod$
\.mp3$
\.mp4$
\.o$
\.ocf$
\.otf$
\.p12$
\.parquet$
\.pbxproj$
\.pdf$
\.pem$
\.pfx$
\.png$
\.psd$
\.pyc$
\.pylintrc$
\.qm$
\.runsettings$
\.s$
\.sig$
\.so$
\.svg$
\.svgz$
\.sys$
\.svgz?$
\.tar$
\.tgz$
\.tiff?$
\.ttf$
\.vcxproj\.filters$
\.vsdx$
\.wav$
\.webm$
\.webp$
\.woff
\.woff2?$
\.xcf$
\.xls
\.xlsx?$
\.xpm$
\.xz$
\.yml$
\.zip$
^\.github/actions/spelling/
^\.github/fabricbot.json$
^\.gitignore$
^\Q.git-blame-ignore-revs\E$
^\Q.github/workflows/spelling.yml\E$
^\Qbuild/config/release.gdnbaselines\E$
^\Qdoc/reference/windows-terminal-logo.ans\E$
^\Qsamples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj.filters\E$
^\Qsrc/host/exe/Host.EXE.vcxproj.filters\E$
^\Qsrc/host/ft_host/chafa.txt\E$
^\Qsrc/host/ft_uia/run.bat\E$
^\Qsrc/host/runft.bat\E$
^\Qsrc/tools/lnkd/lnkd.bat\E$
^\Qsrc/tools/pixels/pixels.bat\E$
^\Qsrc/tools/closetest/CloseTest.vcxproj.filters\E$
^\XamlStyler.json$
^build/config/
^consolegit2gitfilters\.json$
^dep/
^doc/reference/master-sequence-list\.csv$
^doc/reference/master-sequence-list.csv$
^doc/reference/UTF8-torture-test\.txt$
^doc/reference/windows-terminal-logo\.ans$
^oss/
^samples/PixelShaders/Screenshots/
^src/host/ft_uia/run\.bat$
^src/host/runft\.bat$
^src/host/runut\.bat$
^src/interactivity/onecore/BgfxEngine\.
^src/renderer/atlas/
^src/renderer/wddmcon/WddmConRenderer\.
@@ -115,15 +107,14 @@
^src/terminal/parser/ut_parser/Base64Test.cpp$
^src/terminal/parser/ut_parser/run\.bat$
^src/tools/benchcat
^src/tools/ConsoleBench
^src/tools/integrity/dirs$
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
^src/tools/RenderingTests/main\.cpp$
^src/tools/lnkd/lnkd\.bat$
^src/tools/pixels/pixels\.bat$
^src/tools/RenderingTests/main.cpp$
^src/tools/texttests/fira\.txt$
^src/tools/U8U16Test/(?!en)..\.
^src/types/ColorFix\.cpp$
^src/types/ut_types/UtilsTests\.cpp$
^tools/ReleaseEngineering/ServicingPipeline\.ps1$
^XamlStyler\.json$
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
^src/types/ColorFix.cpp
^src/types/ut_types/UtilsTests.cpp$
^tools/ReleaseEngineering/ServicingPipeline.ps1$
ignore$
Resources/(?!en)
SUMS$

View File

@@ -1,5 +0,0 @@
EOB
swrapped
wordi
wordiswrapped
wrappe

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
WCAG
winui
appshellintegration
mdtauk

View File

@@ -1,6 +1,4 @@
# reject `m_data` as VxWorks defined it and that breaks things if it's used elsewhere
# see [fprime](https://github.com/nasa/fprime/commit/d589f0a25c59ea9a800d851ea84c2f5df02fb529)
# and [Qt](https://github.com/qtproject/qt-solutions/blame/fb7bc42bfcc578ff3fa3b9ca21a41e96eb37c1c7/qtscriptclassic/src/qscriptbuffer_p.h#L46)
# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere
# \bm_data\b
# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test,
@@ -8,72 +6,40 @@
# to use this:
#\bfit\(
# s.b. anymore
\bany more[,.]
# s.b. GitHub
(?<![&*.]|// |\btype )\bGithub\b(?![{)])
\bGithub\b
# s.b. GitLab
(?<![&*.]|// |\btype )\bGitlab\b(?![{)])
\bGitlab\b
# s.b. JavaScript
\bJavascript\b
# s.b. macOS or Mac OS X or ...
\bMacOS\b
# s.b. Microsoft
\bMicroSoft\b
# s.b. TypeScript
\bTypescript\b
# s.b. another
\ban[- ]other\b
# s.b. deprecation warning
\b[Dd]epreciation [Ww]arnings?\b
# s.b. greater than
\bgreater then\b
# s.b. in front of
\bin from of\b
# s.b. into
# when not phrasal and when `in order to` would be wrong:
# https://thewritepractice.com/into-vs-in-to/
#\sin to\s(?!if\b)
# s.b. is obsolete
\bis obsolescent\b
# s.b. it's or its
\bits[']
#\sin to\s
# s.b. opt-in
#(?<!\sfor)\sopt in\s
\sopt in\s
# s.b. less than
\bless then\b
# s.b. one of
\bon of\b
# s.b. otherwise
\bother[- ]wise\b
# s.b. or (more|less)
\bore (?:more|less)\b
# s.b. nonexistent
\bnon existing\b
\b[Nn]o[nt][- ]existent\b
# s.b. brief / details/ param / return / retval
(?:^\s*|(?:\*|//|/*)\s+`)[\\@](?:breif|(?:detail|detials)|(?:params(?!\.)|prama?)|ret(?:uns?)|retvl)\b
# s.b. preexisting
[Pp]re[- ]existing
@@ -83,37 +49,14 @@
# s.b. preemptively
[Pp]re[- ]emptively
# s.b. recently changed or recent changes
[Rr]ecent changed
# s.b. reentrancy
[Rr]e[- ]entrancy
# s.b. reentrant
[Rr]e[- ]entrant
# s.b. understand
\bunder stand\b
# s.b. workaround(s)
#\bwork[- ]arounds?\b
# s.b. workarounds
#\bwork[- ]arounds\b
# s.b. workaround
(?:(?:[Aa]|[Tt]he|ugly)\swork[- ]around\b|\swork[- ]around\s+for)
# s.b. (coarse|fine)-grained
\b(?:coarse|fine) grained\b
# s.b. neither/nor -- or reword
#\bnot\b[^.?!"/(]+\bnor\b
# probably a double negative
# s.b. neither/nor (plus rewording the beginning)
\bnot\b[^.?!"/]*\bneither\b[^.?!"/(]*\bnor\b
# In English, it is generally wrong to have the same word twice in a row without punctuation.
# Duplicated words are generally mistakes.
# There are a few exceptions where it is acceptable (e.g. "that that").
# If the highlighted doubled word pair is in a code snippet, you can write a pattern to mask it.
# If the highlighted doubled word pair is in prose, have someone read the English before you dismiss this error.
# Reject duplicate words
\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s

View File

@@ -0,0 +1,2 @@
\\native(?![a-z])
\\nihilist(?![a-z])

View File

@@ -0,0 +1,8 @@
\\registry(?![a-z])
\\release(?![a-z])
\\resources?(?![a-z])
\\result(?![a-z])
\\resultmacros(?![a-z])
\\rules(?![a-z])
\\renderer(?![a-z])
\\rectread(?![a-z])

View File

@@ -0,0 +1,13 @@
\\telemetry(?![a-z])
\\templates(?![a-z])
\\term(?![a-z])
\\terminal(?![a-z])
\\terminalcore(?![a-z])
\\terminalinput(?![a-z])
\\testlist(?![a-z])
\\testmd(?![a-z])
\\testpasses(?![a-z])
\\tests(?![a-z])
\\thread(?![a-z])
\\tools(?![a-z])
\\types?(?![a-z])

View File

@@ -7,6 +7,10 @@ Note: order of the contents of these files can matter.
Lines from an individual file are handled in file order.
Files are selected in alphabetical order.
* [n](0_n.txt), [r](0_r.txt), and [t](0_t.txt) are specifically to work around
a quirk in the spell checker:
it often sees C strings of the form "Hello\nwerld". And would prefer to
spot the typo of `werld`.
* [patterns](patterns.txt) is the main list -- there is nothing
particularly special about the file name (beyond the extension which is
important).

View File

@@ -3,7 +3,7 @@
https?://\S+
[Pp]ublicKeyToken="?[0-9a-fA-F]{16}"?
(?:[{"]|UniqueIdentifier>)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|</UniqueIdentifier)
(?:0[Xx]|\\x|U\+|#)[a-f0-9A-FGgRr]{2,}(?!\[)[Uu]?[Ll]{0,2}\b
(?:0[Xx]|\\x|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]{0,2}\b
microsoft/cascadia-code\@[0-9a-fA-F]{40}
\d+x\d+Logo
Scro\&ll
@@ -12,6 +12,7 @@ Scro\&ll
TestUtils::VerifyExpectedString\(tb, L"[^"]+"
(?:hostSm|mach)\.ProcessString\(L"[^"]+"
\b([A-Za-z])\g{-1}{3,}\b
0x[0-9A-Za-z]+
Base64::s_(?:En|De)code\(L"[^"]+"
VERIFY_ARE_EQUAL\(L"[^"]+"
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+/"
@@ -23,129 +24,70 @@ ROY\sG\.\sBIV
!(?:(?i)ESC)!\[
!(?:(?i)CSI)!(?:\d+(?:;\d+|)m|[ABCDF])
# Python stringprefix / binaryprefix
\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'
# SSE intrinsics like "_mm_subs_epu16"
\b_mm(?:|256|512)_\w+\b
# ARM NEON intrinsics like "vsubq_u16"
\bv\w+_[fsu](?:8|16|32|64)\b
# color floating numbers
0x[0-9a-f](?:\.[0-9a-f]*p)[-+]\d+f
# AppX package
_\d[0-9a-z]{12}['\.]
# string test
equals_insensitive_ascii\("\w+", "\w+"
# Automatically suggested patterns
# hit-count: 3788 file-count: 599
# IServiceProvider / isAThing
\b(?:I|isA)(?=(?:[A-Z][a-z]{2,})+\b)
# hit-count: 3831 file-count: 582
# IServiceProvider
\bI(?=(?:[A-Z][a-z]{2,})+\b)
# hit-count: 314 file-count: 21
# hex runs
\b[0-9a-fA-F]{16,}\b
# hit-count: 71 file-count: 35
# Compiler flags
(?:^|[\t ,"'`=(])-[D](?=[A-Z]{2,}|[A-Z][a-z])
(?:^|[\t ,"'`=(])-[X](?!aml)(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
# hit-count: 47 file-count: 11
# special cased printf markers
\\r\\n(?=[a-z])|(?<!\\)\\[nrt](?=[a-z]{2,})(?=.*(?:<.*['"`]|"(?:[;,]|\);)$|\) \+$))
# ConsoleArgumentsTests
--headless\\.*?"
# hit-count: 109 file-count: 62
# Compiler flags (Unix, Java/Scala)
# Use if you have things like `-Pdocker` and want to treat them as `docker`
(?:^|[\t ,>"'`=(])-(?:D(?=[A-Z])|[WX]|f(?=[ms]))(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
# hit-count: 60 file-count: 35
# hit-count: 41 file-count: 28
# version suffix <word>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: 20 file-count: 9
# hex runs
\b[0-9a-fA-F]{16,}\b
# hit-count: 16 file-count: 10
# hit-count: 10 file-count: 7
# 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: 4 file-count: 1
# ANSI color codes
(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m
# 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
(?<!')\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'(?=[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
# latex
\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+)
# hit-count: 1 file-count: 1
# Punycode
\bxn--[-0-9a-z]+
# 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}|u\d+)\b
# hit-count: 1 file-count: 1
# latex (check-spelling >= 0.0.22)
\\\w{2,}\{
# Non-English
[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
# 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
# French
# This corpus only had capital letters, but you probably want lowercase ones as well.
\b[LN]'+[a-z]{2,}\b
# acceptable duplicates
# ls directory listings
[-bcdlpsw](?:[-r][-w][-Ssx]){3}\s+\d+\s+\S+\s+\S+\s+\d+\s+
# mount
\bmount\s+-t\s+(\w+)\s+\g{-1}\b
# C types and repeated CSS values
\s(auto|center|div|Guid|inherit|long|LONG|none|normal|solid|that|thin|transparent|very)(?: \g{-1})+\s
# C struct
\bstruct\s+(\w+)\s+\g{-1}\b
# go templates
\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml):
# doxygen / javadoc / .net
(?:[\\@](?:brief|groupname|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+static|\s+override|\s+readonly)*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s
[-bcdlpsw](?:[-r][-w][-sx]){3}\s+\d+\s+(\S+)\s+\g{-1}\s+\d+\s+
# C/idl types + English ...
\s(Guid|long|LONG|that) \g{-1}\s
# javadoc / .net
(?:[\\@](?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s
# Commit message -- Signed-off-by and friends
^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$

View File

@@ -1,7 +1,6 @@
^attache$
^attacher$
^attachers$
^bellow$
benefitting
occurences?
^dependan.*

View File

@@ -7,13 +7,13 @@ on:
- labeled
- unlabeled
permissions: {}
permissions: {}
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.5.0
- uses: actions/add-to-project@v0.3.0
with:
project-url: https://github.com/orgs/microsoft/projects/159
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}

View File

@@ -1,32 +0,0 @@
name: GitGudSimilarIssues comments
on:
issues:
types: [opened]
jobs:
getSimilarIssues:
runs-on: ubuntu-latest
outputs:
message: ${{ steps.getBody.outputs.message }}
steps:
- id: getBody
uses: craigloewen-msft/GitGudSimilarIssues@main
with:
issuetitle: ${{ github.event.issue.title }}
repo: ${{ github.repository }}
similaritytolerance: "0.75"
add-comment:
needs: getSimilarIssues
runs-on: ubuntu-latest
permissions:
issues: write
if: needs.getSimilarIssues.outputs.message != ''
steps:
- name: Add comment
run: gh issue comment "$NUMBER" --repo "$REPO" --body "$BODY"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NUMBER: ${{ github.event.issue.number }}
REPO: ${{ github.repository }}
BODY: ${{ needs.getSimilarIssues.outputs.message }}

View File

@@ -5,7 +5,7 @@ name: Spell checking
# https://github.com/check-spelling/check-spelling/wiki/Feature%3A-Restricted-Permissions
#
# `jobs.comment-push` runs when a push is made to a repository and the `jobs.spelling` job needs to make a comment
# (in odd cases, it might actually run just to collapse a comment, but that's fairly rare)
# (in odd cases, it might actually run just to collapse a commment, but that's fairly rare)
# it needs `contents: write` in order to add a comment.
#
# `jobs.comment-pr` runs when a pull_request is made to a repository and the `jobs.spelling` job needs to make a comment
@@ -34,29 +34,6 @@ name: Spell checking
#
# For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key
# Sarif reporting
#
# Access to Sarif reports is generally restricted (by GitHub) to members of the repository.
#
# Requires enabling `security-events: write`
# and configuring the action with `use_sarif: 1`
#
# For information on the feature, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Sarif-output
# Minimal workflow structure:
#
# on:
# push:
# ...
# pull_request_target:
# ...
# jobs:
# # you only want the spelling job, all others should be omitted
# spelling:
# # remove `security-events: write` and `use_sarif: 1`
# # remove `experimental_apply_changes_via_bot: 1`
# ... otherwise adjust the `with:` as you wish
on:
push:
branches:
@@ -66,6 +43,8 @@ on:
pull_request_target:
branches:
- "**"
tags-ignore:
- "**"
types:
- 'opened'
- 'reopened'
@@ -81,11 +60,10 @@ jobs:
contents: read
pull-requests: read
actions: read
security-events: write
outputs:
followup: ${{ steps.spelling.outputs.followup }}
runs-on: ubuntu-latest
if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }}
if: "contains(github.event_name, 'pull_request') || github.event_name == 'push'"
concurrency:
group: spelling-${{ github.event.pull_request.number || github.ref }}
# note: If you use only_check_changed_files, you do not want cancel-in-progress
@@ -93,50 +71,35 @@ jobs:
steps:
- name: check-spelling
id: spelling
uses: check-spelling/check-spelling@v0.0.22
uses: check-spelling/check-spelling@v0.0.21
with:
suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }}
suppress_push_for_open_pull_request: 1
checkout: true
check_file_names: 1
spell_check_this: microsoft/terminal@main
spell_check_this: check-spelling/spell-check-this@prerelease
post_comment: 0
use_magic_file: 1
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
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 }}
extra_dictionary_limit: 20
extra_dictionary_limit: 10
extra_dictionaries:
cspell:software-terms/dict/softwareTerms.txt
cspell:cpp/src/stdlib-cpp.txt
cspell:lorem-ipsum/dictionary.txt
cspell:cpp/src/stdlib-c.txt
cspell:php/dict/php.txt
cspell:filetypes/filetypes.txt
cspell:java/src/java.txt
cspell:python/src/common/extra.txt
cspell:node/dict/node.txt
cspell:java/src/java-terms.txt
cspell:aws/aws.txt
cspell:typescript/dict/typescript.txt
cspell:dotnet/dict/dotnet.txt
cspell:golang/dict/go.txt
cspell:fullstack/dict/fullstack.txt
cspell:cpp/src/compiler-msvc.txt
cspell:software-terms/src/software-terms.txt
cspell:python/src/python/python-lib.txt
cspell:mnemonics/src/mnemonics.txt
cspell:cpp/src/stdlib-cmath.txt
cspell:css/dict/css.txt
cspell:node/node.txt
cspell:cpp/src/stdlib-c.txt
cspell:cpp/src/stdlib-cpp.txt
cspell:fullstack/fullstack.txt
cspell:filetypes/filetypes.txt
cspell:html/html.txt
cspell:cpp/src/compiler-msvc.txt
cspell:python/src/common/extra.txt
cspell:powershell/powershell.txt
cspell:aws/aws.txt
cspell:cpp/src/lang-keywords.txt
cspell:django/dict/django.txt
cspell:npm/npm.txt
cspell:dotnet/dotnet.txt
cspell:python/src/python/python.txt
cspell:html/dict/html.txt
cspell:cpp/src/ecosystem.txt
cspell:cpp/src/compiler-clang-attributes.txt
cspell:npm/dict/npm.txt
cspell:r/src/r.txt
cspell:powershell/dict/powershell.txt
cspell:csharp/csharp.txt
cspell:css/css.txt
cspell:cpp/src/stdlib-cmath.txt
check_extra_dictionaries: ''
comment-push:
name: Report (Push)
@@ -148,10 +111,10 @@ jobs:
if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push'
steps:
- name: comment
uses: check-spelling/check-spelling@v0.0.22
uses: check-spelling/check-spelling@v0.0.21
with:
checkout: true
spell_check_this: microsoft/terminal@main
spell_check_this: check-spelling/spell-check-this@prerelease
task: ${{ needs.spelling.outputs.followup }}
comment-pr:
@@ -160,38 +123,12 @@ jobs:
runs-on: ubuntu-latest
needs: spelling
permissions:
contents: read
pull-requests: write
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
steps:
- name: comment
uses: check-spelling/check-spelling@v0.0.22
uses: check-spelling/check-spelling@v0.0.21
with:
checkout: true
spell_check_this: microsoft/terminal@main
spell_check_this: check-spelling/spell-check-this@prerelease
task: ${{ needs.spelling.outputs.followup }}
experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }}
update:
name: Update PR
permissions:
contents: write
pull-requests: write
actions: read
runs-on: ubuntu-latest
if: ${{
github.repository_owner != 'microsoft' &&
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '@check-spelling-bot apply')
}}
concurrency:
group: spelling-update-${{ github.event.issue.number }}
cancel-in-progress: false
steps:
- name: apply spelling updates
uses: check-spelling/check-spelling@v0.0.22
with:
experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }}
checkout: true
ssh_key: "${{ secrets.CHECK_SPELLING }}"

View File

@@ -14,7 +14,7 @@ The point of doing all this work in public is to ensure that we are holding ours
The team triages new issues several times a week. During triage, the team uses labels to categorize, manage, and drive the project workflow.
We employ [a bot engine](./doc/bot.md) to help us automate common processes within our workflow.
We employ [a bot engine](https://github.com/microsoft/terminal/blob/main/doc/bot.md) to help us automate common processes within our workflow.
We drive the bot by tagging issues with specific labels which cause the bot engine to close issues, merge branches, etc. This bot engine helps us keep the repo clean by automating the process of notifying appropriate parties if/when information/follow-up is needed, and closing stale issues/PRs after reminders have remained unanswered for several days.

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
![terminal-logos](https://github.com/microsoft/terminal/assets/91625426/333ddc76-8ab2-4eb4-a8c0-4d7b953b1179)
![terminal-logos](https://user-images.githubusercontent.com/48369326/115790869-4c852b00-a37c-11eb-97f1-f61972c7800c.png)
# Welcome to the Windows Terminal, Console and Command-Line repo
@@ -8,8 +8,8 @@ This repository contains the source code for:
* [Windows Terminal Preview](https://aka.ms/terminal-preview)
* The Windows console host (`conhost.exe`)
* Components shared between the two projects
* [ColorTool](./src/tools/ColorTool)
* [Sample projects](./samples)
* [ColorTool](https://github.com/microsoft/terminal/tree/main/src/tools/ColorTool)
* [Sample projects](https://github.com/microsoft/terminal/tree/main/samples)
that show how to consume the Windows Console APIs
Related repositories include:
@@ -21,7 +21,7 @@ Related repositories include:
## Installing and running Windows Terminal
> [!NOTE]
> **Note**\
> Windows Terminal requires Windows 10 2004 (build 19041) or later
### Microsoft Store [Recommended]
@@ -53,7 +53,7 @@ fails for any reason, you can try the following command at a PowerShell prompt:
Add-AppxPackage Microsoft.WindowsTerminal_<versionNumber>.msixbundle
```
> [!NOTE]
> **Note**\
> If you install Terminal manually:
>
> * You may need to install the [VC++ v14 Desktop Framework Package](https://docs.microsoft.com/troubleshoot/cpp/c-runtime-packages-desktop-bridge#how-to-install-and-update-desktop-framework-packages).
@@ -72,8 +72,8 @@ package:
winget install --id Microsoft.WindowsTerminal -e
```
> [!NOTE]
> Dependency support is available in WinGet version [1.6.2631 or later](https://github.com/microsoft/winget-cli/releases). To install the Terminal stable release 1.18 or later, please make sure you have the updated version of the WinGet client.
> **Note**\
> Due to [a dependency issue](https://github.com/microsoft/terminal/issues/15663), Terminal's current versions cannot be installed via the Windows Package Manager CLI. To install the stable release 1.17 or later, or the Preview release 1.18 or later, please use an alternative installation method.
#### Via Chocolatey (unofficial)
@@ -118,28 +118,6 @@ repository.
---
## Installing Windows Terminal Canary
Windows Terminal Canary is a nightly build of Windows Terminal. This build has the latest code from our `main` branch, giving you an opportunity to try features before they make it to Windows Terminal Preview.
Windows Terminal Canary is our least stable offering, so you may discover bugs before we have had a chance to find them.
Windows Terminal Canary is available as an App Installer distribution and a Portable ZIP distribution.
The App Installer distribution supports automatic updates. Due to platform limitations, this installer only works on Windows 11.
The Portable ZIP distribution is a portable application. It will not automatically update and will not automatically check for updates. This portable ZIP distribution works on Windows 10 (19041+) and Windows 11.
| Distribution | Architecture | Link |
|---------------|:---------------:|------------------------------------------------------|
| App Installer | x64, arm64, x86 | [download](https://aka.ms/terminal-canary-installer) |
| Portable ZIP | x64 | [download](https://aka.ms/terminal-canary-zip-x64) |
| Portable ZIP | ARM64 | [download](https://aka.ms/terminal-canary-zip-arm64) |
| Portable ZIP | x86 | [download](https://aka.ms/terminal-canary-zip-x86) |
_Learn more about the [types of Windows Terminal distributions](https://learn.microsoft.com/windows/terminal/distributions)._
---
## Windows Terminal Roadmap
The plan for the Windows Terminal [is described here](/doc/roadmap-2023.md) and
@@ -262,7 +240,7 @@ Cause: You're launching the incorrect solution in Visual Studio.
Solution: Make sure you're building & deploying the `CascadiaPackage` project in
Visual Studio.
> [!NOTE]
> **Note**\
> `OpenConsole.exe` is just a locally-built `conhost.exe`, the classic
> Windows Console that hosts Windows' command-line infrastructure. OpenConsole
> is used by Windows Terminal to connect to and communicate with command-line
@@ -286,7 +264,7 @@ enhance Windows Terminal\!
***BEFORE you start work on a feature/fix***, please read & follow our
[Contributor's
Guide](./CONTRIBUTING.md) to
Guide](https://github.com/microsoft/terminal/blob/main/CONTRIBUTING.md) to
help avoid any wasted or duplicate effort.
## Communicating with the Team
@@ -387,10 +365,10 @@ Please review these brief docs below about our coding practices.
This is a work in progress as we learn what we'll need to provide people in
order to be effective contributors to our project.
* [Coding Style](./doc/STYLE.md)
* [Code Organization](./doc/ORGANIZATION.md)
* [Exceptions in our legacy codebase](./doc/EXCEPTIONS.md)
* [Helpful smart pointers and macros for interfacing with Windows in WIL](./doc/WIL.md)
* [Coding Style](https://github.com/microsoft/terminal/blob/main/doc/STYLE.md)
* [Code Organization](https://github.com/microsoft/terminal/blob/main/doc/ORGANIZATION.md)
* [Exceptions in our legacy codebase](https://github.com/microsoft/terminal/blob/main/doc/EXCEPTIONS.md)
* [Helpful smart pointers and macros for interfacing with Windows in WIL](https://github.com/microsoft/terminal/blob/main/doc/WIL.md)
---

View File

@@ -14,4 +14,4 @@ Support for Windows Terminal is limited to the resources listed above.
[gh-bug]: https://github.com/microsoft/terminal/issues/new?assignees=&labels=Issue-Bug&template=bug_report.md&title=
[gh-feature]: https://github.com/microsoft/terminal/issues/new?assignees=&labels=Issue-Feature&template=Feature_Request.md&title=
[docs]: https://docs.microsoft.com/windows/terminal
[contributor]: ./CONTRIBUTING.md
[contributor]: https://github.com/microsoft/terminal/blob/main/CONTRIBUTING.md

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<!-- Optional, defaults to main. Name of the branch which will be used for calculating branch point. -->
<PGOBranch>main</PGOBranch>
<PGOBranch>release-1.19</PGOBranch>
<!-- Mandatory. Name of the NuGet package which will contain PGO databases for consumption by build system. -->
<PGOPackageName>Microsoft.Internal.Windows.Terminal.PGODatabase</PGOPackageName>

View File

@@ -102,7 +102,7 @@ stages:
- ${{ if ne(variables['Build.Reason'], 'PullRequest') }}:
- stage: CodeIndexer
displayName: GitHub CodeNav Indexer
displayName: Github CodeNav Indexer
dependsOn: []
jobs:
- template: ./templates-v2/job-index-github-codenav.yml

View File

@@ -75,6 +75,7 @@ steps:
rm LocOutputMunged.tar
rm -r -fo LocOutput
& ./build/scripts/Copy-ContextMenuResourcesToCascadiaPackage.ps1
& ./build/scripts/Generate-PseudoLocalizations.ps1
displayName: Move Loc files to the right places
- pwsh: |-

View File

@@ -33,6 +33,8 @@ extends:
publishSymbolsToPublic: true
publishVpackToWindows: false
symbolExpiryTime: 15
symbolPublishingSubscription: $(SymbolPublishingServiceConnection)
symbolPublishingProject: $(SymbolPublishingProject)
${{ if eq(true, parameters.publishToAzure) }}:
extraPublishJobs:
- template: build/pipelines/templates-v2/job-deploy-to-azure-storage.yml@self

View File

@@ -81,3 +81,5 @@ extends:
terminalInternalPackageVersion: ${{ parameters.terminalInternalPackageVersion }}
publishSymbolsToPublic: ${{ parameters.publishSymbolsToPublic }}
publishVpackToWindows: ${{ parameters.publishVpackToWindows }}
symbolPublishingSubscription: $(SymbolPublishingServiceConnection)
symbolPublishingProject: $(SymbolPublishingProject)

View File

@@ -92,11 +92,6 @@ jobs:
# Yup.
BuildTargetParameter: ' '
SelectedSigningFragments: ' '
# When building the unpackaged distribution, build it in portable mode if it's Canary-branded
${{ if eq(parameters.branding, 'Canary') }}:
UnpackagedBuildArguments: -PortableMode
${{ else }}:
UnpackagedBuildArguments: ' '
JobOutputDirectory: $(Terminal.BinDir)
JobOutputArtifactName: build-$(BuildPlatform)-$(BuildConfiguration)${{ parameters.artifactStem }}
${{ insert }}: ${{ parameters.variables }}
@@ -274,7 +269,7 @@ jobs:
- pwsh: |-
$XamlAppxPath = (Get-Item "src\cascadia\CascadiaPackage\AppPackages\*\Dependencies\$(BuildPlatform)\Microsoft.UI.Xaml*.appx").FullName
$outDir = New-Item -Type Directory "$(Terminal.BinDir)/_unpackaged" -ErrorAction:Ignore
& .\build\scripts\New-UnpackagedTerminalDistribution.ps1 $(UnpackagedBuildArguments) -TerminalAppX $(WindowsTerminalPackagePath) -XamlAppX $XamlAppxPath -Destination $outDir.FullName
& .\build\scripts\New-UnpackagedTerminalDistribution.ps1 -TerminalAppX $(WindowsTerminalPackagePath) -XamlAppX $XamlAppxPath -Destination $outDir.FullName
displayName: Build Unpackaged Distribution (from MSIX)
condition: and(succeeded(), ne(variables.WindowsTerminalPackagePath, ''))

View File

@@ -1,92 +0,0 @@
parameters:
- name: buildConfiguration
type: string
- name: buildPlatforms
type: object
- name: pool
type: object
default: []
- name: dependsOn
type: object
default: null
- name: artifactStem
type: string
default: ''
- name: variables
type: object
default: {}
- name: environment
type: string
- name: storagePublicRootURL
type: string
- name: subscription
type: string
- name: storageAccount
type: string
- name: storageContainer
type: string
jobs:
- job: DeployAzure
${{ if ne(length(parameters.pool), 0) }}:
pool: ${{ parameters.pool }}
displayName: Publish to Azure Storage (Prod)
dependsOn: ${{ parameters.dependsOn }}
variables:
${{ insert }}: ${{ parameters.variables }}
steps:
- download: none
- checkout: self
clean: true
fetchDepth: 1
fetchTags: false # Tags still result in depth > 1 fetch; we don't need them here
submodules: true
persistCredentials: True
- task: DownloadPipelineArtifact@2
displayName: Download MSIX Bundle Artifact
inputs:
artifactName: appxbundle-${{ parameters.buildConfiguration }}${{ parameters.artifactStem }}
downloadPath: '$(Build.SourcesDirectory)/_out'
itemPattern: '**/*.msixbundle'
- ${{ each platform in parameters.buildPlatforms }}:
- task: DownloadPipelineArtifact@2
displayName: Download unpackaged build for ${{ platform }} ${{ parameters.buildConfiguration }}
inputs:
artifactName: build-${{ platform }}-${{ parameters.buildConfiguration }}${{ parameters.artifactStem }}
downloadPath: '$(Build.SourcesDirectory)/_unpackaged'
itemPattern: '**/_unpackaged/*.zip'
- pwsh: |-
$b = Get-Item _out/*.msixbundle
./build/scripts/New-AppInstallerFromTemplateAndBundle.ps1 -BundlePath $b.FullName -AppInstallerTemplatePath ./build/config/template.appinstaller -AppInstallerRoot "${{ parameters.storagePublicRootURL }}" -OutputPath _out/Microsoft.WindowsTerminalCanary.appinstaller
displayName: "Produce AppInstaller for MSIX bundle"
- pwsh: |-
$zips = Get-ChildItem -Recurse -Filter *.zip _unpackaged
$zips | ForEach-Object {
$name = $_.Name
$parts = $name.Split('_')
$parts[1] = "latest"
$name = [String]::Join('_', $parts)
$_ | Move-Item -Destination (Join-Path "_out" $name)
}
displayName: "Wrangle Unpackaged builds into place, rename"
- powershell: |-
Get-PackageProvider -Name NuGet -ForceBootstrap
Install-Module -Verbose -AllowClobber -Force Az.Accounts, Az.Storage, Az.Network, Az.Resources, Az.Compute
displayName: Install Azure Module Dependencies
- task: AzureFileCopy@5
displayName: Publish to Storage Account
inputs:
sourcePath: _out/*
Destination: AzureBlob
azureSubscription: ${{ parameters.subscription }}
storage: ${{ parameters.storageAccount }}
ContainerName: ${{ parameters.storageContainer }}
AdditionalArgumentsForBlobCopy: "--content-type application/octet-stream"

View File

@@ -1,6 +1,6 @@
jobs:
- job: CodeNavIndexer
displayName: Run GitHub CodeNav Indexer
displayName: Run Github CodeNav Indexer
pool: { vmImage: windows-2022 }
steps:

View File

@@ -0,0 +1,117 @@
parameters:
- name: includePublicSymbolServer
type: boolean
default: false
- name: pool
type: object
default: []
- name: dependsOn
type: object
default: null
- name: artifactStem
type: string
default: ''
- name: jobName
type: string
default: PublishSymbols
- name: symbolExpiryTime
type: string
default: 36530 # This is the default from PublishSymbols@2
- name: variables
type: object
default: {}
- name: subscription
type: string
- name: symbolProject
type: string
jobs:
- job: ${{ parameters.jobName }}
${{ if ne(length(parameters.pool), 0) }}:
pool: ${{ parameters.pool }}
${{ if eq(parameters.includePublicSymbolServer, true) }}:
displayName: Publish Symbols to Internal and MSDL
${{ else }}:
displayName: Publish Symbols Internally
dependsOn: ${{ parameters.dependsOn }}
variables:
${{ insert }}: ${{ parameters.variables }}
steps:
- checkout: self
clean: true
fetchDepth: 1
fetchTags: false # Tags still result in depth > 1 fetch; we don't need them here
submodules: true
persistCredentials: True
- task: PkgESSetupBuild@12
displayName: Package ES - Setup Build
inputs:
disableOutputRedirect: true
- task: DownloadPipelineArtifact@2
displayName: Download all PDBs from all prior build phases
inputs:
itemPattern: '**/*.pdb'
targetPath: '$(Build.SourcesDirectory)/bin'
- powershell: |-
Get-PackageProvider -Name NuGet -ForceBootstrap
Install-Module -Verbose -AllowClobber -Force Az.Accounts, Az.Storage, Az.Network, Az.Resources, Az.Compute
displayName: Install Azure Module Dependencies
# Transit the Azure token from the Service Connection into a secret variable for the rest of the pipeline to use.
- task: AzurePowerShell@5
displayName: Generate an Azure Token
inputs:
azureSubscription: ${{ parameters.subscription }}
azurePowerShellVersion: LatestVersion
pwsh: true
ScriptType: InlineScript
Inline: |-
$AzToken = (Get-AzAccessToken -ResourceUrl api://30471ccf-0966-45b9-a979-065dbedb24c1).Token
Write-Host "##vso[task.setvariable variable=SymbolAccessToken;issecret=true]$AzToken"
- task: PublishSymbols@2
displayName: Publish Symbols (to current Azure DevOps tenant)
continueOnError: True
inputs:
SymbolsFolder: '$(Build.SourcesDirectory)/bin'
SearchPattern: '**/*.pdb'
IndexSources: false
DetailedLog: true
SymbolsMaximumWaitTime: 30
SymbolServerType: 'TeamServices'
SymbolsProduct: 'Windows Terminal Converged Symbols'
SymbolsVersion: '$(XES_APPXMANIFESTVERSION)'
SymbolsArtifactName: 'WindowsTerminal_$(XES_APPXMANIFESTVERSION)'
SymbolExpirationInDays: ${{ parameters.symbolExpiryTime }}
env:
LIB: $(Build.SourcesDirectory)
- pwsh: |-
# Prepare the defaults for IRM
$PSDefaultParameterValues['Invoke-RestMethod:Headers'] = @{ Authorization = "Bearer $(SymbolAccessToken)" }
$PSDefaultParameterValues['Invoke-RestMethod:ContentType'] = "application/json"
$PSDefaultParameterValues['Invoke-RestMethod:Method'] = "POST"
$BaseUri = "https://symbolrequestprod.trafficmanager.net/projects/${{ parameters.symbolProject }}/requests"
# Prepare the request
$expiration = (Get-Date).Add([TimeSpan]::FromDays(${{ parameters.symbolExpiryTime }}))
$createRequestBody = @{
requestName = "WindowsTerminal_$(XES_APPXMANIFESTVERSION)";
expirationTime = $expiration.ToString();
}
Write-Host "##[debug]Starting request $($createRequestBody.requestName) with expiration date of $($createRequestBody.expirationTime)"
Invoke-RestMethod -Uri "$BaseUri" -Body ($createRequestBody | ConvertTo-Json -Compress) -Verbose
# Request symbol publication
$publishRequestBody = @{
publishToInternalServer = $true;
publishToPublicServer = $${{ parameters.includePublicSymbolServer }};
}
Write-Host "##[debug]Submitting request $($createRequestBody.requestName) ($($publishRequestBody | ConvertTo-Json -Compress))"
Invoke-RestMethod -Uri "$BaseUri/$($createRequestBody.requestName)" -Body ($publishRequestBody | ConvertTo-Json -Compress) -Verbose
displayName: Publish Symbols using internal REST API

View File

@@ -56,7 +56,6 @@ jobs:
- task: PowerShell@2
displayName: 'Run PGO Tests'
inputs:
pwsh: true
targetType: filePath
filePath: build\scripts\Run-Tests.ps1
arguments: >-

View File

@@ -44,7 +44,6 @@ jobs:
- task: PowerShell@2
displayName: 'Run Unit Tests'
inputs:
pwsh: true
targetType: filePath
filePath: build\scripts\Run-Tests.ps1
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(OutputBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}' -Root "$(Terminal.BinDir)"
@@ -53,7 +52,6 @@ jobs:
- task: PowerShell@2
displayName: 'Run Feature Tests'
inputs:
pwsh: true
targetType: filePath
filePath: build\scripts\Run-Tests.ps1
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(OutputBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}' -Root "$(Terminal.BinDir)"

View File

@@ -52,6 +52,10 @@ parameters:
- name: publishVpackToWindows
type: boolean
default: false
- name: symbolPublishingSubscription
type: string
- name: symbolPublishingProject
type: string
- name: extraPublishJobs
type: object
@@ -78,6 +82,7 @@ extends:
cloudvault: # https://aka.ms/obpipelines/cloudvault
enabled: false
globalSdl: # https://aka.ms/obpipelines/sdl
enableCheckCFlags: false # CheckCFlags is broken and exploding our builds; to remove, :g/BAD-FLAGS/d
asyncSdl:
enabled: true
tsaOptionsFile: 'build/config/tsa.json'
@@ -103,6 +108,8 @@ extends:
parameters:
pool: { type: windows }
variables:
ob_sdl_checkcflags_enabled: false # BAD-FLAGS
ob_sdl_xfgcheck_enabled: false # BAD-FLAGS
ob_git_checkout: false # This job checks itself out
ob_git_skip_checkout_none: true
ob_outputDirectory: $(JobOutputDirectory)
@@ -137,6 +144,8 @@ extends:
parameters:
pool: { type: windows }
variables:
ob_sdl_checkcflags_enabled: false # BAD-FLAGS
ob_sdl_xfgcheck_enabled: false # BAD-FLAGS
ob_git_checkout: false # This job checks itself out
ob_git_skip_checkout_none: true
ob_outputDirectory: $(JobOutputDirectory)
@@ -168,6 +177,8 @@ extends:
parameters:
pool: { type: windows }
variables:
ob_sdl_checkcflags_enabled: false # BAD-FLAGS
ob_sdl_xfgcheck_enabled: false # BAD-FLAGS
ob_git_checkout: false # This job checks itself out
ob_git_skip_checkout_none: true
ob_outputDirectory: $(JobOutputDirectory)
@@ -219,6 +230,8 @@ extends:
parameters:
pool: { type: windows }
variables:
ob_sdl_checkcflags_enabled: false # BAD-FLAGS
ob_sdl_xfgcheck_enabled: false # BAD-FLAGS
ob_git_checkout: false # This job checks itself out
ob_git_skip_checkout_none: true
ob_outputDirectory: $(JobOutputDirectory)
@@ -234,6 +247,8 @@ extends:
parameters:
pool: { type: windows }
variables:
ob_sdl_checkcflags_enabled: false # BAD-FLAGS
ob_sdl_xfgcheck_enabled: false # BAD-FLAGS
ob_git_checkout: false # This job checks itself out
ob_git_skip_checkout_none: true
ob_outputDirectory: $(JobOutputDirectory)
@@ -248,13 +263,16 @@ extends:
displayName: Publish
dependsOn: [Build]
jobs:
- template: ./build/pipelines/templates-v2/job-publish-symbols.yml@self
- template: ./build/pipelines/templates-v2/job-publish-symbols-using-symbolrequestprod-api.yml@self
parameters:
pool: { type: windows }
includePublicSymbolServer: ${{ parameters.publishSymbolsToPublic }}
symbolPatGoesInTaskInputs: true # onebranch tries to muck with the PAT variable, so we need to change how it get the PAT
symbolExpiryTime: ${{ parameters.symbolExpiryTime }}
subscription: ${{ parameters.symbolPublishingSubscription }}
symbolProject: ${{ parameters.symbolPublishingProject }}
variables:
ob_sdl_checkcflags_enabled: false # BAD-FLAGS
ob_sdl_xfgcheck_enabled: false # BAD-FLAGS
ob_git_checkout: false # This job checks itself out
ob_git_skip_checkout_none: true
ob_outputDirectory: $(Build.ArtifactStagingDirectory)

View File

@@ -1,2 +1,2 @@
variables:
WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest'
WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:1.0.02566.28'

View File

@@ -1,49 +0,0 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OpenConsoleGetUtilityProjectWinMDReferencesDependsOn></OpenConsoleGetUtilityProjectWinMDReferencesDependsOn>
<OpenConsoleGetUtilityProjectWinMDReferencesDependsOn Condition="'$(TerminalCppWinRT)'=='true'">
GetCppWinRTProjectWinMDReferences;
$(OpenConsoleGetUtilityProjectWinMDReferencesDependsOn)
</OpenConsoleGetUtilityProjectWinMDReferencesDependsOn>
<OpenConsoleGetUtilityProjectWinMDReferencesDependsOn Condition="'$(TerminalMidlRT)'=='true'">
GetMidlRTProjectWinMDReferences;
$(OpenConsoleGetUtilityProjectWinMDReferencesDependsOn)
</OpenConsoleGetUtilityProjectWinMDReferencesDependsOn>
<CppWinRTResolveReferencesDependsOn>$(CppWinRTResolveReferencesDependsOn);OpenConsoleSwitchUpMergeInputs;</CppWinRTResolveReferencesDependsOn>
<MidlRTResolveReferencesDependsOn>$(MidlRTResolveReferencesDependsOn);OpenConsoleSwitchUpMergeInputs;</MidlRTResolveReferencesDependsOn>
</PropertyGroup>
<Target Name="OpenConsoleGetUtilityProjectWinMDReferences"
DependsOnTargets="ResolveProjectReferences;$(OpenConsoleGetUtilityProjectWinMDReferencesDependsOn)"
Returns="@(OpenConsoleUtilityProjectWinMDReferences)">
<ItemGroup>
<_OpenConsoleUtilityProjectReferences Remove="@(_OpenConsoleUtilityProjectReferences)"/>
<_OpenConsoleUtilityProjectReferences Include="@(_ResolvedProjectReferencePaths)"
Condition= "'%(_ResolvedProjectReferencePaths.ProjectType)'=='Utility' AND
'%(_ResolvedProjectReferencePaths.WinMDFile)' == 'true' AND
'%(_ResolvedProjectReferencePaths.OpenConsoleImplementInterface)' == 'true'"/>
</ItemGroup>
<ItemGroup>
<OpenConsoleUtilityProjectWinMDReferences Remove="@(OpenConsoleUtilityProjectWinMDReferences)" />
<OpenConsoleUtilityProjectWinMDReferences Include="@(_OpenConsoleUtilityProjectReferences)">
<WinMDPath>%(FullPath)</WinMDPath>
</OpenConsoleUtilityProjectWinMDReferences>
</ItemGroup>
</Target>
<!-- Calculates the input files and metadata directories to be passed to MdMerge -->
<Target Name="OpenConsoleSwitchUpMergeInputs"
DependsOnTargets="OpenConsoleGetUtilityProjectWinMDReferences">
<ItemGroup>
<!-- Utility projects should be consumed statically, not as dynamic references. -->
<CppWinRTDynamicProjectWinMDReferences Remove="@(OpenConsoleUtilityProjectWinMDReferences)" />
<CppWinRTStaticProjectWinMDReferences Include="@(OpenConsoleUtilityProjectWinMDReferences)" />
<MidlRTDynamicProjectWinMDReferences Remove="@(OpenConsoleUtilityProjectWinMDReferences)" />
<MidlRTStaticProjectWinMDReferences Include="@(OpenConsoleUtilityProjectWinMDReferences)" />
</ItemGroup>
</Target>
</Project>

View File

@@ -30,7 +30,6 @@
<IntermediateOutputPath>$(SolutionDir)obj\$(Configuration)\GenerateFeatureFlags\</IntermediateOutputPath>
<OpenConsoleCommonOutDir>$(SolutionDir)bin\$(Configuration)\</OpenConsoleCommonOutDir>
<_WTBrandingName Condition="'$(WindowsTerminalBranding)'=='Canary'">Canary</_WTBrandingName>
<_WTBrandingName Condition="'$(WindowsTerminalBranding)'=='Preview'">Preview</_WTBrandingName>
<_WTBrandingName Condition="'$(WindowsTerminalBranding)'=='Release'">Release</_WTBrandingName>
<_WTBrandingName Condition="'$(_WTBrandingName)'==''">Dev</_WTBrandingName>

View File

@@ -0,0 +1,16 @@
Get-ChildItem -Recurse -Filter *.resw
| Where-Object { $_.Directory.Name.StartsWith("qps-ploc") }
| ForEach-Object {
$source = Join-Path $_.Directory "../en-US/$($_.Name)"
$target = $_
$ploc = ./tools/ConvertTo-PseudoLocalization.ps1 -Path $source
$writerSettings = [System.Xml.XmlWriterSettings]::new()
$writerSettings.NewLineChars = "`r`n"
$writerSettings.Indent = $true
$writer = [System.Xml.XmlWriter]::Create($target, $writerSettings)
$ploc.Save($writer)
$writer.Flush()
$writer.Close()
}

View File

@@ -25,12 +25,7 @@ Param(
[Parameter(HelpMessage="Path to makeappx.exe", ParameterSetName='Layout')]
[ValidateScript({Test-Path $_ -Type Leaf})]
[string]
$MakeAppxPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakeAppx.exe",
[Parameter(HelpMessage="Include the portable mode marker file by default", ParameterSetName='AppX')]
[Parameter(HelpMessage="Include the portable mode marker file by default", ParameterSetName='Layout')]
[switch]
$PortableMode = $PSCmdlet.ParameterSetName -eq 'Layout'
$MakeAppxPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakeAppx.exe"
)
$filesToRemove = @("*.xml", "*.winmd", "Appx*", "Images/*Tile*", "Images/*Logo*") # Remove from Terminal
@@ -133,11 +128,6 @@ $finalTerminalPriFile = Join-Path $terminalAppPath "resources.pri"
# Packaging
########
$portableModeMarkerFile = Join-Path $terminalAppPath ".portable"
If ($PortableMode) {
"" | Out-File $portableModeMarkerFile
}
If ($PSCmdlet.ParameterSetName -Eq "AppX") {
# We only produce a ZIP when we're combining two AppX directories.
New-Item -ItemType Directory -Path $Destination -ErrorAction:SilentlyContinue | Out-Null

View File

@@ -16,48 +16,22 @@ Param(
# Find test DLLs based on the provided root, match pattern, and recursion
$testDlls = Get-ChildItem -Path $Root -Recurse -Filter $MatchPattern
$teArgs = @()
$args = @()
# Check if the LogPath parameter is provided and enable WTT logging
if ($LogPath) {
$teArgs += '/enablewttlogging'
$teArgs += '/appendwttlogging'
$teArgs += "/logFile:$LogPath"
$args += '/enablewttlogging'
$args += '/appendwttlogging'
$args += "/logFile:$LogPath"
Write-Host "WTT Logging Enabled"
}
$rootTe = "$Root\te.exe"
# Invoke the te.exe executable with arguments and test DLLs
& "$Root\te.exe" $args $testDlls.FullName $AdditionalTaefArguments
# Some of our test fixtures depend on resources.pri in the same folder as the .exe hosting them.
# Unfortunately, that means that we need to run the te.exe *next to* each test DLL we discover.
# This code establishes a mapping from te.exe to test DLL (or DLLs)
$testDllTaefGroups = $testDlls | % {
$localTe = Get-Item (Join-Path (Split-Path $_ -Parent) "te.exe") -EA:Ignore
If ($null -eq $localTe) {
$finalTePath = $rootTe
} Else {
$finalTePath = $localTe.FullName
}
[PSCustomObject]@{
TePath = $finalTePath;
TestDll = $_;
}
}
# Invoke the te.exe executables with arguments and test DLLs
$anyFailed = $false
$testDllTaefGroups | Group-Object TePath | % {
$te = $_.Group[0].TePath
$dlls = $_.Group.TestDll
Write-Verbose "Running $te (for $($dlls.Name))"
& $te $teArgs $dlls.FullName $AdditionalTaefArguments
if ($LASTEXITCODE -ne 0) {
$anyFailed = $true
}
}
if ($anyFailed) {
Exit 1
# Check the exit code of the te.exe process and exit accordingly
if ($LASTEXITCODE -ne 0) {
Exit $LASTEXITCODE
}
Exit 0

View File

@@ -3,9 +3,9 @@
<!-- This file is read by XES, which we use in our Release builds. -->
<PropertyGroup Label="Version">
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2024</XesBaseYearForStoreVersion>
<XesBaseYearForStoreVersion>2023</XesBaseYearForStoreVersion>
<VersionMajor>1</VersionMajor>
<VersionMinor>21</VersionMinor>
<VersionMinor>19</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

@@ -10,7 +10,6 @@
<package id="Microsoft.UI.Xaml" version="2.8.4" targetFramework="native" />
<package id="Microsoft.Web.WebView2" version="1.0.1661.34" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.240122.1" targetFramework="native" developmentDependency="true" />
<package id="Microsoft.Windows.MidlRT" version="2.0.200924.1" targetFramework="native" />
<!-- Managed packages -->
<package id="Appium.WebDriver" version="3.0.0.2" targetFramework="net45" />

View File

@@ -5,10 +5,10 @@
`.../console/published/wincon.w` in the OS repo when you submit the PR.
The branch won't build without it.
* For now, you can update winconp.h with your consumable changes.
* Define registry name (ex: `CONSOLE_REGISTRY_CURSORCOLOR`)
* Add the setting to `CONSOLE_STATE_INFO`.
* Define registry name (ex `CONSOLE_REGISTRY_CURSORCOLOR`)
* Add the setting to `CONSOLE_STATE_INFO`
* Define the property key ID and the property key itself.
- Yes, the large majority of the `DEFINE_PROPERTYKEY` defs are the same, it's only the last byte of the guid that changes.
- Yes, the large majority of the `DEFINE_PROPERTYKEY` defs are the same, it's only the last byte of the guid that changes
2. Add matching fields to Settings.hpp
- Add getters, setters, the whole drill.
@@ -17,9 +17,9 @@
- We need to add it to *reading and writing* the registry from the propsheet, and *reading* the link from the propsheet. Yes, that's weird, but the propsheet is smart enough to re-use ShortcutSerialization::s_SetLinkValues, but not smart enough to do the same with RegistrySerialization.
- `src/propsheet/registry.cpp`
- `propsheet/registry.cpp@InitRegistryValues` should initialize the default value for the property.
- `propsheet/registry.cpp@GetRegistryValues` should make sure to read the property from the registry.
- `propsheet/registry.cpp@GetRegistryValues` should make sure to read the property from the registry
4. Add the field to the propslib registry map.
4. Add the field to the propslib registry map
5. Add the value to `ShortcutSerialization.cpp`
- Read the value in `ShortcutSerialization::s_PopulateV2Properties`
@@ -30,11 +30,11 @@ Now, your new setting should be stored just like all the other properties.
7. Update the feature test properties to get add the setting as well
- `ft_uia/Common/NativeMethods.cs@WinConP`:
- `Wtypes.PROPERTYKEY PKEY_Console_`.
- `NT_CONSOLE_PROPS`.
- `Wtypes.PROPERTYKEY PKEY_Console_`
- `NT_CONSOLE_PROPS`
8. Add the default value for the setting to `win32k-settings.man`
- If the setting shouldn't default to 0 or `nullptr`, then you'll need to set the default value of the setting in `win32k-settings.man`.
9. Update `Settings::InitFromStateInfo` and `Settings::CreateConsoleStateInfo` to get/set the value in a CONSOLE_STATE_INFO appropriately.
9. Update `Settings::InitFromStateInfo` and `Settings::CreateConsoleStateInfo` to get/set the value in a CONSOLE_STATE_INFO appropriately

View File

@@ -51,7 +51,7 @@ Will this UI enhancement come to other apps on Windows? Almost certainly not. Th
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.
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....

View File

@@ -125,6 +125,8 @@
* Private calls into the Windows Window Manager to perform privileged actions related to the console process (working to eliminate) or for High DPI stuff (also working to eliminate)
* `Userprivapi.cpp`
* `Windowdpiapi.cpp`
* New UTF8 state machine in progress to improve Bash (and other apps) support for UTF-8 in console
* `Utf8ToWideCharParser.cpp`
* Window resizing/layout/management/window messaging loops and all that other stuff that has us interact with Windows to create a visual display surface and control the user interaction entry point
* `Window.cpp`
* `Windowproc.cpp`

View File

@@ -86,19 +86,12 @@
]
},
"BuiltinSuggestionSource": {
"type": "string",
"anyOf": [
{
"type": "string"
},
{
"enum": [
"commandHistory",
"tasks",
"all"
]
}
]
"enum": [
"commandHistory",
"tasks",
"all"
],
"type": "string"
},
"SuggestionSource": {
"default": "all",
@@ -106,17 +99,15 @@
"$comment": "`tasks` and `local` are sources that would be added by the Tasks feature, as a follow-up",
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/$defs/BuiltinSuggestionSource"
"type": [ "string", "null", "BuiltinSuggestionSource" ]
},
{
"type": "array",
"items": {
"$ref": "#/$defs/BuiltinSuggestionSource"
},
"uniqueItems": true
"items": { "type": "BuiltinSuggestionSource" }
},
{
"type": "array",
"items": { "type": "string" }
}
]
},
@@ -210,6 +201,10 @@
"desktopWallpaper"
]
}
],
"type": [
"string",
"null"
]
},
"backgroundImageOpacity": {
@@ -275,7 +270,7 @@
"description": "Use to set a path to a pixel shader to use with the Terminal when unfocused. Overrides `experimental.retroTerminalEffect`. This is an experimental feature, and its continued existence is not guaranteed.",
"type": "string"
},
"useAcrylic": {
"useAcrylic":{
"description": "When set to true, the window will have an acrylic material background when unfocused. When set to false, the window will have a plain, untextured background when unfocused.",
"type": "boolean"
},
@@ -657,7 +652,6 @@
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
@@ -700,7 +694,6 @@
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
@@ -717,7 +710,6 @@
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
@@ -739,7 +731,6 @@
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
@@ -756,7 +747,6 @@
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
@@ -797,7 +787,6 @@
]
},
"ShortcutAction": {
"type": "object",
"properties": {
"action": {
"description": "The action to execute",
@@ -806,7 +795,8 @@
},
"required": [
"action"
]
],
"type": "object"
},
"AdjustFontSizeAction": {
"description": "Arguments corresponding to an Adjust Font Size Action",
@@ -815,7 +805,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -840,7 +829,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -882,7 +870,6 @@
"$ref": "#/$defs/NewTerminalArgs"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -899,7 +886,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -924,7 +910,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -949,7 +934,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -974,7 +958,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -999,7 +982,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1024,7 +1006,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1049,7 +1030,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1077,7 +1057,6 @@
"$ref": "#/$defs/NewTerminalArgs"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1110,7 +1089,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1148,7 +1126,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1176,7 +1153,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1198,7 +1174,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1220,7 +1195,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1245,7 +1219,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1266,7 +1239,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1287,7 +1259,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1308,7 +1279,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1333,7 +1303,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1362,7 +1331,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1391,7 +1359,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1420,7 +1387,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1445,7 +1411,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1470,7 +1435,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1494,7 +1458,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1520,7 +1483,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1542,7 +1504,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1570,7 +1531,6 @@
"$ref": "#/$defs/NewTerminalArgs"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1587,7 +1547,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1609,7 +1568,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1631,7 +1589,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1653,7 +1610,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1675,7 +1631,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1698,7 +1653,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1720,7 +1674,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1742,7 +1695,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1794,7 +1746,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1811,7 +1762,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1835,7 +1785,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1864,7 +1813,6 @@
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
@@ -1951,11 +1899,7 @@
"properties": {
"applicationTheme": {
"description": "Which UI theme the Terminal should use for controls",
"enum": [
"light",
"dark",
"system"
],
"enum": [ "light", "dark", "system" ],
"type": "string"
},
"useMica": {
@@ -1986,11 +1930,7 @@
"type": "string",
"description": "The name of the theme. This will be displayed in the settings UI.",
"not": {
"enum": [
"light",
"dark",
"system"
]
"enum": [ "light", "dark", "system" ]
}
},
"tab": {
@@ -2007,7 +1947,6 @@
"ThemePair": {
"additionalProperties": false,
"description": "A pair of Theme names, to allow the Terminal to switch theme based on the OS theme",
"type": "object",
"properties": {
"light": {
"type": "string",
@@ -2189,16 +2128,16 @@
},
"name": {
"description": "The name that will appear in the command palette. If one isn't provided, the terminal will attempt to automatically generate a name.\nIf name is a string, it will be the name of the command.\nIf name is a object, the key property of the object will be used to lookup a localized string resource for the command",
"type": [
"string",
"object",
"null"
],
"properties": {
"key": {
"type": "string"
}
}
},
"type": [
"string",
"object",
"null"
]
},
"iterateOn": {
"type": "string",
@@ -2235,7 +2174,6 @@
"Globals": {
"additionalProperties": true,
"description": "Properties that affect the entire window, regardless of the profile settings.",
"type": "object",
"properties": {
"alwaysOnTop": {
"default": false,
@@ -2247,7 +2185,7 @@
"description": "When set to true, tabs are always displayed. When set to false and \"showTabsInTitlebar\" is set to false, tabs only appear after opening a new tab.",
"type": "boolean"
},
"compatibility.enableUnfocusedAcrylic": {
"compatibility.enableUnfocusedAcrylic":{
"default": true,
"description": "When set to true, unfocused windows can have acrylic instead of opaque.",
"type": "boolean"
@@ -2450,17 +2388,10 @@
"theme": {
"default": "dark",
"description": "Sets the theme of the application. This value should be the name of one of the themes defined in `themes`. The Terminal also includes the themes `dark`, `light`, and `system`.",
"anyOf": [
"oneOf": [
{
"type": "string"
},
{
"enum": [
"dark",
"light",
"system"
]
},
{
"$ref": "#/$defs/ThemePair"
}
@@ -2570,12 +2501,12 @@
},
"required": [
"defaultProfile"
]
],
"type": "object"
},
"Profile": {
"description": "Properties specific to a unique profile.",
"additionalProperties": false,
"type": "object",
"properties": {
"acrylicOpacity": {
"default": 0.5,
@@ -2627,7 +2558,7 @@
},
"backgroundImage": {
"description": "Sets the file location of the image to draw over the window background.",
"anyOf": [
"oneOf": [
{
"type": [
"string",
@@ -2639,6 +2570,10 @@
"desktopWallpaper"
]
}
],
"type": [
"string",
"null"
]
},
"backgroundImageAlignment": {
@@ -2782,15 +2717,15 @@
"description": "When set to true, marks added to the buffer via the addMark action will appear on the scrollbar.",
"type": "boolean"
},
"experimental.repositionCursorWithMouse": {
"default": false,
"description": "When set to true, you can move the text cursor by clicking with the mouse on the current commandline. This is an experimental feature - there are lots of edge cases where this will not work as expected.",
"type": "boolean"
},
"experimental.pixelShaderPath": {
"description": "Use to set a path to a pixel shader to use with the Terminal. Overrides `experimental.retroTerminalEffect`. This is an experimental feature, and its continued existence is not guaranteed.",
"type": "string"
},
"useAtlasEngine": {
"description": "Windows Terminal 1.16 and later ship with a new, performant text renderer. Set this to false to revert back to the old text renderer.",
"type": "boolean",
"default": true
},
"fontFace": {
"default": "Cascadia Mono",
"description": "[deprecated] Define 'face' within the 'font' object instead.",
@@ -2975,7 +2910,8 @@
"description": "When set to true, the window will have an acrylic material background. When set to false, the window will have a plain, untextured background.",
"type": "boolean"
}
}
},
"type": "object"
},
"ProfileList": {
"description": "A list of profiles and the properties specific to each.",
@@ -2990,7 +2926,6 @@
},
"ProfilesObject": {
"description": "A list of profiles and default settings that apply to all of them",
"type": "object",
"properties": {
"list": {
"$ref": "#/$defs/ProfileList"
@@ -2999,12 +2934,12 @@
"description": "The default settings that apply to every profile.",
"$ref": "#/$defs/Profile"
}
}
},
"type": "object"
},
"SchemeList": {
"description": "Properties are specific to each color scheme. ColorTool is a great tool you can use to create and explore new color schemes. All colors use hex color format.",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
@@ -3093,7 +3028,8 @@
"$ref": "#/$defs/Color",
"description": "Sets the color used as ANSI yellow."
}
}
},
"type": "object"
},
"type": "array"
}
@@ -3103,7 +3039,6 @@
"$ref": "#/$defs/Globals"
},
{
"type": "object",
"additionalItems": true,
"properties": {
"profiles": {

View File

@@ -115,7 +115,7 @@ Incoming issues/asks/etc. are triaged several times a week, labeled appropriatel
[Up Next]: https://github.com/microsoft/terminal/milestone/37
[Backlog]: https://github.com/microsoft/terminal/milestone/45
[Terminal v2 Roadmap]: ./terminal-v2-roadmap.md
[Terminal v2 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/terminal-v2-roadmap.md
[Windows Terminal Preview 1.2 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-2-release/
[Windows Terminal Preview 1.3 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-3-release/
@@ -131,4 +131,4 @@ Incoming issues/asks/etc. are triaged several times a week, labeled appropriatel
[Windows Terminal Preview 1.13 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-13-release/
[Windows Terminal Preview 1.14 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-14-release/
[Terminal 2023 Roadmap]: ./roadmap-2023.md
[Terminal 2023 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/roadmap-2023.md

View File

@@ -54,9 +54,9 @@ _informative, not normative_
For a more fluid take on what each of the team's personal goals are, head on over to [Core team North Stars]. This has a list of more long-term goals that each team member is working towards, but not things that are necessarily committed work.
[^1]: A conclusive list of these features can be found at [../src/features.xml](../src/features.xml). Note that this is a raw XML doc used to light up specific parts of the codebase, and not something authored for human consumption.
[^1]: A conclusive list of these features can be found at https://github.com/microsoft/terminal/blob/main/src/features.xml. Note that this is a raw XML doc used to light up specific parts of the codebase, and not something authored for human consumption.
[2022 Roadmap]: ./roadmap-2022.md
[2022 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/roadmap-2022.md
[Terminal 1.17]: https://github.com/microsoft/terminal/releases/tag/v1.17.1023
[Terminal 1.18]: https://github.com/microsoft/terminal/releases/tag/v1.18.1462.0

View File

@@ -435,7 +435,7 @@ ultimately deemed it to be out of scope for the initial spec review.
<!-- Footnotes -->
[#2046]: https://github.com/microsoft/terminal/issues/2046
[Command Palette, Addendum 1]: ../%232046%20-%20Unified%20keybindings%20and%20commands%2C%20and%20synthesized%20action%20names.md
[Command Palette, Addendum 1]: https://github.com/microsoft/terminal/blob/main/doc/specs/%232046%20-%20Unified%20keybindings%20and%20commands%2C%20and%20synthesized%20action%20names.md
[#3337]: https://github.com/microsoft/terminal/issues/3337
[#6899]: https://github.com/microsoft/terminal/issues/6899

Binary file not shown.

Before

Width:  |  Height:  |  Size: 585 KiB

View File

@@ -1,744 +0,0 @@
---
author: Mike Griese
created on: 2022-08-22
last updated: 2023-08-03
issue id: 1595
---
# Windows Terminal - Suggestions UI
## Abstract
Multiple related scenarios have come up where it would be beneficial to display
actionable UI to the user within the context of the active terminal itself. This
UI would be akin to the Intellisense UI in Visual Studio. It appears right where
the user is typing, and can help provide immediate content for the user, based
on some context. The "Suggestions UI" is this new ephemeral UI within the
Windows Terminal that can display different types of actions, from different
sources.
## Background
The Suggestions UI is the singular UI by which the Terminal can display a
variety of suggestions to the user. These include:
* Recent commands the user has executed in this terminal, powered by shell integration.
* Recent directories, similarly powered by shell integration
* Completions from the shell itself (like the shell completions in PowerShell)
* Tasks, which are `sendInput` actions from the user's settings
* Buffer Completions, which is a dumb type of autocomplete based on words in the buffer
* and more (as provided via extensions)
All of these scenarios are places where it makes sense to present the user a
menu at the point of text insertion in the terminal control itself.
### Inspiration
Primarily, the inspiration is any Intellisense-like experience, in any app.
Visual Studio, VsCode, PowerShell, vim, Sublime any JetBrains IDE - there's more
than enough examples in the wild.
Ultimately, the inspiration for the Suggestions UI came from a bunch of places
all at once. In the course of a few months though, it became clear that we'd
need a unified UI for displaying a variety of suggestion-like experiences in the
Terminal. Our work with the PowerShell and VsCode teams helped refine these
requests all into the unified design below.
### User Stories
Size | Description
-----------|--
🐣 Crawl | The user can bring up the Suggestions UI with recent commands, powered by shell integration
🐣 Crawl | [#12863] The user can bring up the Suggestions UI with recent directories, powered by shell integration
🚶 Walk | The user can bring up the Suggestions UI with tasks from their settings
🚶 Walk | CLI apps can invoke the Suggestions UI with a new VT sequence
🚶 Walk | The Suggestions UI can be opened using the current typed commandline as a filter
🚶 Walk | Recent commands and directories are stored in `state.json`, across sessions
🏃‍♂️ Run | Suggestions can have descriptions presented in / alongside the UI
🏃‍♂️ Run | The Suggestions UI can be opened without any nesting
🏃‍♂️ Run | The Suggestions UI can be opened, nested by `source` of the suggestion
🚀 Sprint | Extensions can provide suggestion sources for the Suggestions UI
🚀 Sprint | The Suggestions UI can be opened in "inline" mode, only showing the text of the first suggestion
### Elevator Pitch
The Suggestions UI is a UI element displayed in the Terminal for providing
different types of text suggestions to the user - anything from recently run
commands, to saved commands, to tab-completion suggestions from the shell
itself.
## Business Justification
It will delight developers.
Furthermore, our partners on the Visual Studio team have been requesting similar
functionality for some time now. The way autocompletion menus in PowerShell
currently interact with UIA clients leaves much to be desired. They'd like a way
to provide richer context to screen readers. Something to enable the terminal to
more specifically describe the context of what's being presented to the user.
## Scenario Details
### UI/UX Design
#### Prototypes
The following gif was a VsCode prototype of [shell-driven autocompletion]. This
is the point of reference we're starting from when talking about what the
suggestions UI might look like.
![](vscode-shell-suggestions.gif)
These suggestions are populated by logic within PowerShell itself, and
communicated to the Terminal. The Terminal can then display them in the
Suggestions UI.
The following demonstrate a prototype of what that might look like for the
Terminal. These are meant to be informative, not normative, representations of
what the UI would look like.
![](shell-autocomplete-july-2022-000.gif)
A prototype of the recent commands UI, powered by shell integration:
![](command-history-suggestions.gif)
A prototype of the tasks UI, powered by the user's settings:
![](tasks-suggestions.gif)
(admittedly, the `TeachingTip` in that gif is a prototype and was later replaced
with a better version.)
In general, the Suggestions UI will present a list of elements to select from,
near the text cursor. This control might be contain a text box for filtering
these items (a "**palette**"), or it might not (a "**menu**").
![An example of the menu mode](3121-suggestion-menu-2023-000.gif)
#### Palette vs Menu
Depending on how the suggestions UI is invoked, we may or may not want to
display a text box for filtering these suggestions. Consider the Intellisense
menu in Visual Studio. That's a UI that only allows for up/down for navigation
(and enter/tab for selecting the suggestion).
For suggestions driven by the Terminal, we'll display a filtering text box in
the Suggestions UI. This is similar to the command palette's search - a fuzzy
search to filter the contents. This is the "**palette**" style of the
suggestions dialog.
For completions driven by the shell, we should probably not display the
filtering text box. This is the "**menu**" style of the suggestion dialog. The
user is primarily interacting with the shell here, not the Terminal.
> **Warning**
> TODO! For discussion, possibly with a real UX designer.
How should we handle completions here? Tab? Enter? Right-Arrow? Should we have
an element selected when we open the menu, or should tab/enter only work once
the user has used the arrows at least once? Sublime allows for <kbd>tab</kbd> to
complete the suggestion immediately.
Consider also that these suggestions might be provided by the shell, as the user
is typing at a commandline shell. For something like PowerShell, the user might
want to start typing a command and have it tab-complete based off the shell's
tab expansion rules. PowerShell's inline suggestions use right-arrow to
differentiate "use this suggestion" vs tab for "tab expand what I'm typing at
the prompt". We should probably preserve this behavior.
We probably don't want to provide different experiences for the **menu** version
of the Suggestions UI vs. the **palette** version. In the palette version, the
user won't be pressing tab to tab-complete at the shell - the focus is out of
the of terminal and in the Suggestions UI. With the menu version, the focus is
still "in the terminal", and users would expect tab to tab-complete.
We will want to make sure that there's some semblance of consistency across our
implementation for the Suggestions UI, our own Command Palette, VsCode's
intellisense and their own implementation of shell-completions in the Terminal.
> **Note**
> In my prototype, for the "Menu" mode, I accepted ALL of right-arrow, tab, and
> enter as "accept completion", and any other key dismissed the UI. This _felt_
> right for that mode. I'm not sure we could make the same call for "palette"
> mode, where we'd need tab for navigating focus.
### Implementation Details
#### Fork the Command Palette
We're largely going to start with the Command Palette to build the Suggestions
UI[[1](#footnote-1)]. The Command Palette is already a control we've built for displaying a
transient list of commands and dispatching them to the rest of the app.
Currently, the Command Palette is a single static control, at the top-center of
the Terminal window, and occupying a decent portion of the screen. For the
Suggestions UI, we'll instead want to make sure that the control appears
relative to the current cursor position.
We'll start by taking the command palette, and copying it over to a new control.
This will allow us to remove large chunks of code dealing with different modes
(i.e. the tab switcher), and code dealing with prefix characters to switch
modes.
We'll need to make some small modifications to enable the Suggestions UI to
* work as a text cursor-relative control
* exist as a Flyout outside the bounds of the Terminal window
* If the Suggestions UI is too close to the bottom of the screen, we'll need it to open
"upwards", with the search box at the _bottom_ and the list extending above it
* prevent it from switching to command-line mode
* display tooltips / `TeachingTip`s / some secondary flyout with a description
of the suggestion (if provided)
#### Completion sources
The Suggestions UI will support suggestions from a variety of different
"sources". As an example, consider the following actions:
```json
{ "command": { "action":"suggestions", "source": "commandHistory" } },
{ "command": { "action":"suggestions", "source": "directoryHistory" } },
{ "command": { "action":"suggestions", "source": "tasks" } },
{ "command": { "action":"suggestions", "source": "local" } },
{ "command": { "action":"suggestions", "source": ["local", "tasks", "commandHistory"] } },
{ "command": { "action":"suggestions", "source": "Microsoft.Terminal.Extensions.BufferComplete" } },
```
Each of these `suggestions` actions would open the Suggestions UI with a
different set of actions.
* `commandHistory`: Use commands from this session, as identified via shell
integration. This won't be able to return any suggestions if the user has not
configured their shell to support shell integration sequences yet.
* `directoryHistory`: Populate the list with a series of `cd {path}` commands,
where the paths are populated via shell integration. Paths are in MRU order.
* `tasks`: Populate the list with all `sendInput` actions in the user's settings
file. The command structure should remain unchanged. For example, if they have
`sendInput` actions nested under a "git" command, then the "git" entry will
remain in this tasks view with their `sendInput` actions nested inside it. For
more details, see the [Tasks] spec.
* `local`: Populate the list with tasks that are located in the CWD, in a file
named `.wt.json`. For more details, see the [Tasks] spec.
* `Microsoft.Terminal.Extensions.BufferComplete`: As an example, this
demonstrates how an action might be authored to reference a suggestion source
from an extension[[2](#footnote-2)].
Each of these different sources will build a different set of `Command`s,
primarily populated with `sendInput` actions. We'll load those `Command`s into
the Suggestions UI control, and open it at the text cursor.
To drill in on a single example - the `commandHistory` source. In that
particular case, the TerminalPage will query the active TermControl for a list
of its recent commands. If it knows these (via shell integration), then the
TerminalPage will use that list of commands to build a list of `sendInput`
actions. Those will then get fed to the suggestions UI.
Not listed above is [shell-driven autocompletion]. These aren't something that
the Terminal can invoke all on its own - these are something the shell would
need to invoke themselves.
#### Pre-populate the current commandline context
Consider the following scenario. A user has typed `git c` in their shell, and
has [shell integration] enabled for their shell. They want to open the
Suggestions UI filtered to their recent history, but starting with what they've
already typed. To support this scenario, we'll add an additional property:
* `"useCommandline"`: `bool` (**default**: `true`)
* `true`: the current commandline the user has typed will pre-populate the
filter of the Suggestions UI. This requires that the user has enabled shell
integration in their shell's config.
* `false`: the filter will start empty, regardless of what the user has typed.
With that setting, the user can achieve their desired UX with the following action:
```json
{ "command": { "action":"suggestions", "source": "commandHistory", "useCommandline": true } },
```
Now, when they type `git c` and invoke the Suggestions UI, they can immediately
start searching for recent commands that started with `git c`.
The primary use case for `useCommandline: false` was for `"nesting": "source"`.
When filtering a list of ["Tasks...", "Recent commands...", "Recent
directories...", "Docker...", "Git..."], then there's minimal value to start by
filtering to "git c".
#### Default actions
I propose adding the following actions to the Terminal by default:
```json
{ "command": { "action":"suggestions", "source": "commandHistory", "useCommandline": true } },
{ "command": { "action":"suggestions", "source": "directoryHistory" } },
{ "command": { "action":"suggestions", "source": ["local", "tasks", "commandHistory"], "useCommandline": true, "nesting": "disabled" } },
{ "command": { "action":"suggestions", "source": ["all"], "useCommandline": false, "nesting": "source" } },
```
These actions are colloquially:
* Give me suggestions from my recent commands, using what I've typed
* Give me suggestions of directories I've recently been in
* _(After [Tasks] are implemented)_ Give me suggestions from recent commands,
commands I've saved, and commands for this project. Don't nest any, so they're
all in the top-level menu. Use what I've typed already to start filtering.
* Just open the Suggestions UI with all suggestions sources, and group them by
the source of the suggestions.
This should cover most of the basic use cases for suggestions.
#### Who owns this menu?
There was some discussion of who should own the suggestions menu. The control
itself? Or the app hosting the control?
A main argument for hosting this UI in the control itself is that any consumer
of the `TermControl` should be able to display the [shell-driven autocompletion]
menu. And they should get the UI from us "for free". Consumers shouldn't need to
reimplement it themselves. This probably could be done without many changes:
* Instead of operating on `Command`s and actions from the terminal settings,
the control could just know that all the entries in the menu are "send
input" "actions".
* The control could offer a method to manually invoke the Suggestions UI for a
list of {suggestion, name, description} objects.
* The app layer could easily translate between sendInput actions and these
pseudo-actions.
A big argument in favor of having the app layer host the control: Consider an
app like Visual Studio. When they embed the control, they'll want to style the
shell-completions UI in their own way. They already have their own intellisense
menu, and their own UI paradigm.
For now, we'll leave this as something that's owned by the app layer. When we
get around to finalizing the [shell-driven autocompletion] design, we can
iterate on ideas for supporting both consumers that want to use a pre-built
suggestions control, or consumers who want to bring their own.
## Tenets
<table>
<tr><td><strong>Compatibility</strong></td><td>
This shouldn't break any existing flows. This is a general purpose UI element,
to be extended in a variety of ways. Those customizations will all be opt-in by
the user, so I'm not expecting any breaking compatibility changes here.
</td></tr>
<tr><td><strong>Accessibility</strong></td><td>
The Suggestions UI was designed with the goal of making commandline shell
suggestions _more_ accessible. As Carlos previously wrote:
> Screen readers struggle with this because the entire menu is redrawn every time, making it harder to understand what exactly is "selected" (as the concept of selection in this instance is a shell-side concept represented by visual manipulation).
>
> ...
>
> _\[Shell driven suggestions\]_ can then be leveraged by Windows Terminal to create UI elements. Doing so leverages WinUI's accessible design.
This will allow the Terminal to provide more context-relevant information to
screen readers.
</td></tr>
<tr><td><strong>Sustainability</strong></td><td>
No sustainability changes expected.
</td></tr>
<tr><td><strong>Localization</strong></td><td>
The localization needs of the Suggestions UI will be effectively the same as the
needs of the Command Palette.
The Terminal will have no way to localize suggestions that are provided via
[shell-driven autocompletion]. These are just verbatim strings that the shell
told us to use. We don't consider this to be something to worry about, however.
This is no different than the fact that Terminal cannot localize the `Get-Help`
(or any other) output of PowerShell.
</td></tr>
</table>
## Implementation Plan
This is more of an informative outline, rather than a normative one. Many of the
things from Crawl, Walk, and Run are all already in PRs as of the time of this
spec's review.
### 🐣 Crawl
* [ ] Fork the Command palette to a new UI element, the `SuggestionsControl`
* [ ] Enable previewing `sendInput` actions in the Command Palette and `SuggestionsControl`
* [ ] Enable the `SuggestionsControl` to open top-down (aligned to the bottom of the cursor row) or bottom-up (aligned to the top of the cursor row).
* [ ] Disable sorting on the `SuggestionsControl` - elements should presumably be pre-sorted by the source.
* [ ] Expose the recent commands as a accessor on `TermControl`
* [ ] Add a `suggestions` action which accepts a single option `recentCommands`. These should be fed in MRU order to the `SuggestionsControl`.
* [ ] Expose the recent directories as an accessor on `TermControl`, and add a `recentDirectories` source.
### 🚶 Walk
* [ ] Add a `tasks` source to `suggestions` which opens the Suggestions UI with
a tree of all `sendInput` commands
* [ ] Enable the `SuggestionsControl` to open with or without a search box
* [ ] Plumb support for shell-driven completions through the core up to the app
* [ ] Expose the _current_ commandline from the `TermControl`
* [ ] Add a `useCommandline` property to `suggestions`, to pre-populate the search with the current commandline.
* [ ] Persist recent commands / directories accordingly
### 🏃‍♂️ Run
* [ ] Add a `description` field to `Command`
* [ ] Add a `TeachingTip` (or similar) to the Suggestions UI to display
descriptions (when available)
* [ ] Use the `ToolTip` property of shell-driven suggestions as the description
* [ ] Add a boolean `nesting` property which can be used to disable nesting on the `tasks` source.
* [ ] Add the ability for `nesting` to accept `enabled`/`disabled` as `true`/`false` equivalents
* [ ] Add the ability for `nesting` to accept `source`, which instead groups all
commands to the Suggestions UI by the source of that suggestion.
### 🚀 Sprint
The two "sprint" tasks here are much more ambitious than the other listed
scenarios, so breaking them down to atomic tasks sees less reasonable. We'd have
to spend a considerable amount more time figuring out _how_ to do each of these
first.
For example - extensions. We have yet to fully realize what extensions _are_.
Determining how extensions will provide suggestions is left as something we'll
need to do as a part of the Extensions spec.
## Conclusion
Here's a sample json schema for the settings discussed here.
```json
"OpenSuggestionsAction": {
"description": "Arguments corresponding to a Open Suggestions Action",
"allOf": [
{
"$ref": "#/$defs/ShortcutAction"
},
{
"properties": {
"action": {
"type": "string",
"const": "suggestions"
},
"source": {
"$ref": "#/$defs/SuggestionSource",
"description": "Which suggestion sources to filter."
},
"useCommandline": {
"default": false,
"description": "When set to `true`, the current commandline the user has typed will pre-populate the filter of the Suggestions UI. This requires that the user has enabled shell integration in their shell's config. When set to false, the filter will start empty."
},
"nesting": {
"default": true,
"description": "When set to `true`, suggestions will follow the provided nesting structure. For Tasks, these will follow the structure of the Command Palette. When set to `false`, no nesting will be used (and all suggestions will be in the top-level menu.",
"$comment": "This setting is a possible follow-up setting, not required for v1. "
}
}
}
]
},
"BuiltinSuggestionSource": {
"enum": [
"commandHistory",
"directoryHistory",
"tasks",
"local",
"all"
],
"type": "string"
},
"SuggestionSource": {
"default": "all",
"description": "Either a single suggestion source, or an array of sources to concatenate. Built-in sources include `commandHistory`, `directoryHistory`, `tasks`, and `local`. Extensions may provide additional values. The special value `all` indicates all suggestion sources should be included",
"$comment": "`tasks` and `local` are sources that would be added by the Tasks feature, as a follow-up"
"oneOf": [
{
"type": [ "string", "null", "BuiltinSuggestionSource" ]
},
{
"type": "array",
"items": { "type": "BuiltinSuggestionSource" }
},
{
"type": "array",
"items": { "type": "string" }
}
]
},
```
### Future Considerations
* Another extension idea: `WithFig.FigCompletions`. Imagine an extension that
could parse existing [Fig] completion specs, and provide those as suggestions
in this way.
* This might be a good example of an async suggestion source. The current
commandline is used as the starting filter, and the suggestions would be
populated by some `fig` process / thread / async operation that returns the
suggestions.
* If the user hasn't enabled shell completion, we could add text to the
`commandHistory` or `directoryHistory` menus to inform the user how they could
go enable shell integration. We already have a docs page dedicated to this, so
we could start by linking to that page. More notes on this in [Automatic shell
integration](#Automatic-shell-integration).
* Maybe there could be a per-profile setting for automatic suggestions after
some timeout. Like, as you type, a menu version of the Suggestions UI appears.
So you could just start typing `git c`, and it would automatically give you a
menu with suggestions, implicitly using the typed command as the "filter".
* Maybe we could do this as an `implicit` property on the `suggestions` action
#### Description Tooltips
> **Note**: _This is left as a future consideration for the initial draft of
> this spec. I'd like to flesh out [shell-driven autocompletion] more before
> committing any plans here._
It would be beneficial for the Suggestions UI to display additional context to
the user. Consider a extension that provides some commands for the user, like a
hypothetical "Docker" extension. The extension author might be able to give the
commands simplified names, but also want to expose a more detailed description
of the commands to the user.
Or consider the Suggestions UI when invoked by [shell-driven autocompletion].
The shell might want to provide help text to the user with each of the
suggestions. This would allow a user to browse through the suggestions that they
might not know about, and learn how they work before committing to one.
Only the help text for the currently hovered command should be presented to the
user. To support this kind of UX, we'll add an optional flyout of some sort to
display with the Suggestions UI. This flyout will only appear if there's more
information provided to the Terminal.
This might be in the form of a `TeachingTip`, as in this example:
![TeachingTip with description](https://user-images.githubusercontent.com/18356694/222244568-243a6482-92d9-4c3c-bffc-54ad97f01f69.gif)
Actions in the settings could also accept an optional `description` property, to
specify the string that would be presented in that flyout.
#### Automatic shell integration
A large portion of these features all rely on shell integration being enabled by
the user. However, this is not a trivial thing for the Terminal to do on behalf
of the user. Shell integration relies on changes to the user's shell config. If
the Terminal were to try and configure those itself, we may accidentally destroy
configuration that the user has already set up. Hence why the Terminal can't
just have a "Light up all the bells and whistles" toggle in the Settings UI.
This is a non-trivial problem to solve, so it is being left as a future
consideration, for a later spec. It deserves its own spec to sort out how we
should expose this to users and safely implement it.
#### Pre-filtering the UI & filter by source
> **Note**: _This is a brainstorm I considered while writing this spec. I would
> not include it in the v1 of this spec. Rather, I'd like to leave it for
> where we might go with this UX in the future._
Do want to support different _types_ of nesting? So instead of just the default,
there could be something like `nesting: "source"`, to create a menu structured
like:
```
Suggestions UI
├─ Recent Commands...
│ ├─ git checkout main
│ ├─ git fetch
│ └─ git pull
├─ Recent Directories...
│ ├─ d:\dev
│ ├─ d:\dev\public
│ └─ d:\dev\public\terminal
├─ Saved tasks...
│ ├─ Git...
│ │ └─ git commit -m "
│ │ └─ git log...
│ └─ bx & runut
└─ Docker
├─ docker build --platform linux/amd64 <path>
└─ docker logs -f --tail <lines_count> <container_name>
```
> **Note**
> I'm using `Docker` as an example fragment extension that provides
> some `docker` commands. When grouping by `"source"`, we could pull those into
> a separate top-level entry. When not grouping by `"source"`, those would still
> show up with the rest of `tasks`. )
#### Store recent commands across sessions
> **Note**
> _I'm not sure we really want to put this in this spec or not, hence
> why it is in the "Future considerations" section. I think it is worth
> mentioning. This might be better served in the [shell integration] doc._
We'll probably want a way for recent commands to be saved across sessions. That way, your `cmd.exe` command history could persist across sessions. We'd need:
* A setting to enable this behavior
* A setting to control the context of these saved commandlines.
* Do we want them saved per-profile, or globally?
* If they're saved per-profile, maybe a profile can opt-in to loading all the commands?
* How does defterm play with this? Do we "layer" by concatenating per-profile commands with `profiles.defaults` ones?
* A button in the Settings UI for clearing these commands
* Should fragments be able to pre-populate "recent commands"?
* I'm just gonna say _no_. That would be a better idea for Tasks (aka just a `sendInput` Action that we load from the fragment normally as a Task), or a specific suggestion source for the fragment extension.
#### Inline mode
> **Note**
> _This is a half-baked idea with some potential. However, I don't
> think it needs to be a part of the v1 of the Suggestions UI, so I'm leaving it
> under future considerations for a future revision._
Do we want to have a suggestions UI "mode", that's just **one** inline
suggestion, "no" UI? Some UX ala the `PsReadline` recent command suggestion
feature. Imagine, we just display the IME ghost text thing for the first result,
given the current prompt?
Take the following action as an example:
```json
{ "command": { "action":"suggestions", "source": "commandHistory", "useCommandline": true, "inline": true } },
```
Type the start of some command at the prompt, and press that key. Presto, we do
the `pwsh` thing. Ghost text appears for the first match in the `commandHistory`
for what the user has typed. If they press another key, ~they've typed into the
"hidden" Suggestions UI, which filters the (hidden) list more, and updates the
one inline suggestion.~
Or, instead, typed keys go to the shell, and then we re-query the commandline,
and update the filter accordingly. That would allow tab-completion to still
work. We'd use <kbd>right arrow</kbd> to accept the suggestion (and dismiss the
ghost text preview).
This would seemingly SUPER conflict with PowerShell's own handler. Probably not
something someone should enable for PowerShell 7 profiles if they're using that
feature.
### Rejected ideas
These are musings from earlier versions of the spec.
* **Asynchronous prompting**: This was rejected because it was so fundamentally
different from the rest of the UX of the Suggestions UI, it didn't make sense
to try and also do that behavior.
* ...
#### REJECTED: Asynchronous prompting
Certain suggestion sources might want to provide results asynchronously.
Consider a source that might want to make a web request to populate what strings
to suggest. That source might want to prompt the user for input first, then
dispatch the request, then populate the UI. Or something like a `fig`-like
suggestion source, which would need to parse some files from the disk to
generate the list of suggestions.
The easiest way to do this would be to provide a secondary UI element for
prompting the user for input, doing the request in the background, then opening
the UI later. However, that feels a little disjointed. Could we instead provide
a more continuous experience?
The following is a proposal for using the Suggestions UI itself as the control
to prompt the user for input.
```c++
TerminalPage::SetUpSuggestionsUI()
{
const auto& asyncSource{ AsyncSuggestions() };
suggestionsUI.OnInputChanged({ asyncSource, AsyncSuggestions::InputChangedHandler});
// In this example, we don't want the UI to filter item based on the input
// string - the source has already determined the list of relevant matches.
suggestionsUI.FilterByInput(false);
asyncSource.SuggestionsChanged([](const auto& newCommands){
suggestionsUI.Loading(false);
suggestionsUI.Commands(newCommands);
})
}
void AsyncSuggestions::InputChangedHandler(FilterChangedArgs args)
{
// kick off a trailing ThrottledFunc to do a new query
_loadNewResults->Run(args.NewInputText());
// If we get another request, we might want to cancel the pending throttled
// func entirely, and start the timeout fresh. Just so that we only make a
// query for the final string they type.
args.RequestLoading(true); // pass a boolean back up in the args, so that
// the Suggestions UI can clear out the current commands, and start displaying an
// indeterminate progress wheel.
}
```
That would basically _have_ to be special cased for this source, at least for
now. We could refactor that later to better deal with extensions.
Let's make sure this would work for something `fig`-like, where the "prompt" is
literally the prompt, what the user has already typed at the commandline.
After some discussion:
* How do we differentiate the prompting version of the Suggestions UI from the
filtering version?
* The prompting version _doesn't_ filter results
* Async modes wouldn't work with sync ones at all. E.g. if you did `source:
["tasks", "myAsyncSource"]`. It doesn't make sense to start with a list of
`tasks`, then type, find no tasks, but then oh! the UI fills in some other
suggestions too. That's weird.
## Resources
These are some other work streams that have a lot of tie-in to the Suggestions
UI. These are all being spec'd at roughly the same time, so links may not be
fully up to date.
* [Shell integration]
* [Shell-driven autocompletion]
* [Tasks]
### Footnotes
<a name="footnote-1"><a>[1]: We've had discussion in the past ([#7285]) about
possibly creating a more abstract "Live filtering list view" to replace the
Command Palette. We could most certainly use that here too. We've decided to
initially go with a fork for now.
<a name="footnote-2"><a>[2]: Obviously, we're not having a real discussion about
extensions in this doc. This example is solely to show that there's room for
extensions to work with the "source" property in this design. What the final
shape of extensions will be is very much still to be determined.
[Fig]: https://github.com/withfig/autocomplete
[Warp]: https://www.warp.dev/
[workflows]: https://docs.warp.dev/features/workflows
[also working on workflows]: https://fig.io/user-manual/workflows
[winget script]: https://github.com/microsoft/PowerToys/blob/main/.github/workflows/package-submissions.yml
[#1595]: https://github.com/microsoft/terminal/issues/1595
[#7039]: https://github.com/microsoft/terminal/issues/7039
[#3121]: https://github.com/microsoft/terminal/issues/3121
[#10436]: https://github.com/microsoft/terminal/issues/10436
[#12927]: https://github.com/microsoft/terminal/issues/12927
[#12863]: https://github.com/microsoft/terminal/issues/12863
[#7285]: https://github.com/microsoft/terminal/issues/7285
[#14939]: https://github.com/microsoft/terminal/issues/7285
[#keep]: https://github.com/zadjii/keep
[VsCode Tasks]: ../../../.vscode/tasks.json
<!-- Note: This is its own spec in progress, but for the time being #12862 will do -->
[Tasks]: https://github.com/microsoft/terminal/issues/12862
<!-- Note: This is just a link to the PR that introduced the shell integration spec -->
[shell integration]: https://github.com/microsoft/terminal/pull/14792
<!-- Note: If I ever write a spec for this, go ahead and replace this link -->
[shell-driven autocompletion]: https://github.com/microsoft/terminal/issues/3121

Binary file not shown.

Before

Width:  |  Height:  |  Size: 965 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 KiB

View File

@@ -605,4 +605,4 @@ as well as 3 schemes: "Scheme 1", "Scheme 2", and "Scheme 3".
<!-- Footnotes -->
[Command Palette Spec]: ./%232046%20-%20Command%20Palette.md
[Command Palette Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%232046%20-%20Command%20Palette.md

View File

@@ -612,8 +612,8 @@ You could have a profile that layers on an existing profile, with elevated-speci
[#8514]: https://github.com/microsoft/terminal/issues/8514
[#10276]: https://github.com/microsoft/terminal/issues/10276
[Process Model 2.0 Spec]: ../%235000%20-%20Process%20Model%202.0.md
[Configuration object for profiles]: ../%233062%20-%20Appearance configuration object for profiles.md
[Session Management Spec]: ./%234472%20-%20Windows%20Terminal%20Session%20Management.md
[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0.md
[Configuration object for profiles]: https://github.com/microsoft/terminal/blob/main/doc/specs/Configuration%20object%20for%20profiles.md
[Session Management Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%234472%20-%20Windows%20Terminal%20Session%20Management.md
[The Old New Thing: How can I launch an unelevated process from my elevated process, redux]: https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443
[Workspace Trust]: https://code.visualstudio.com/docs/editor/workspace-trust

View File

@@ -559,4 +559,4 @@ runtime.
[Tab Tear-out in the community toolkit]: https://github.com/windows-toolkit/Sample-TabView-TearOff
[Quake mode scenarios]: https://github.com/microsoft/terminal/issues/653#issuecomment-661370107
[`ISwapChainPanelNative2::SetSwapChainHandle`]: https://docs.microsoft.com/en-us/windows/win32/api/windows.ui.xaml.media.dxinterop/nf-windows-ui-xaml-media-dxinterop-iswapchainpanelnative2-setswapchainhandle
[Process Model 2.0 Spec]: ./doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md
[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md

View File

@@ -730,7 +730,7 @@ user to differentiate between the two behaviors.
[#5727]: https://github.com/microsoft/terminal/issues/5727
[#9992]: https://github.com/microsoft/terminal/issues/9992
[Process Model 2.0 Spec]: ../%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md
[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md
[Quake 3 sample]: https://youtu.be/ZmR6HQbuHPA?t=27
[`RegisterHotKey`]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey
[`dev/migrie/f/653-QUAKE-MODE`]: https://github.com/microsoft/terminal/tree/dev/migrie/f/653-QUAKE-MODE

View File

@@ -23,7 +23,7 @@ contexts without needing to replicate an entire json blob.
This spec was largely inspired by the following diagram from @DHowett:
![figure 1](data-mockup-002.png)
![figure 1](data-mockup.png)
The goal is to introduce an `id` parameter by which actions could be uniquely
referred to. If we'd ever like to use an action outside the list of `actions`, we
@@ -36,54 +36,38 @@ Discussion with the team lead to the understanding that the name `actions` would
be even better, as a way of making the meaning of the "list of actions" more
obvious.
We will then restructure `defaults.json`, and also users' settings files (via `fixUpUserSettings`), in the following manner:
* Instead of each `command` json block containing both the `action` (along with additional arguments) and the `keys`, these will now be split up -
* There will now be one json block for just the `command`/`action`, which will also contain the `id`. These json blocks will be in their own list called `actions`.
* There will be another json block for the `keys`, which will refer to the action to be invoked by `id`. These json blocks will be in their own list called `keybindings`.
For example, let's take a look at the `split pane right` action in `defaults.json` as we currently have it:
`"actions": [..., { "command": { "action": "splitPane", "split": "right" }, "keys": "alt+shift+plus" }, ...]`
This will now become:
`"actions": [..., { "command": { "action": "splitPane", "split": "right" }, "id": "Terminal.SplitPaneRight" }, ...]`
`"keybindings": [..., { "keys": "alt+shift+plus", "id": "Terminal.SplitPaneRight" }, ...]`
Here is how we will parse settings file and construct the relevant settings model objects:
* We will first scan the `actions` list. We'll
When we're parsing `actions`, we'll make three passes:
* The first pass will scan the list for objects with an `id` property. We'll
attempt to parse those entries into `ActionAndArgs` which we'll store in the
global `id->ActionAndArgs` map. All actions defined in `defaults.json` must have an `id` specified, and all actions provided by fragments must also have `id`s. Any actions from the defaults or fragments that do not provide `id`s will be ignored. As for user-specified commands, if no `id` is set, we will auto-generate one for that command based on the action and any additional arguments. For example, the `split pane right` command above might result in an autogenerated id `User.SplitPaneRight`.
* Note: this step is also where we will generate _commands_. We will use the name provided in the entry if it's set or the action's `GenerateName` method.
* Next we will scan the `keybindings` list. These entries will
create a `KeyChord->ActionAndArgs` entry in the keybindings map. Since these objects should all contain an `id`, we will simply use the `id->ActionAndArgs` map we created in the previous step. Any object with `keys` set but no `id` will be ignored.
global `id->ActionAndArgs` map. If any entry doesn't have an `id` set, we'll
skip it in this phase. If an entry doesn't have a `command` set, we'll ignore
it in this pass.
* The second pass will scan for _keybindings_. Any entries with `keys` set will
create a `KeyChord->ActionAndArgs` entry in the keybindings map. If the entry
has an `id` set, then we'll simply re-use the action we've already parsed for
the `id`, from the action map. If there isn't an `id`, then we'll parse the
action manually at this time. Entries without a `keys` set will be ignored in
this pass.
* The final pass will be to generate _commands_. Similar to the keybindings
pass, we'll attempt to lookup actions for entries with an `id` set. If there
isn't an `id`, then we'll parse the action manually at this time. We'll then
get the name for the entry, either from the `name` property if it's set, or
the action's `GenerateName` method.
For a visual representation:
For a visual representation, let's assume the user has the following in their
`actions`:
![figure 2](data-mockup-actions-ids-keys-maps.png)
![figure 2](data-mockup-actions.png)
### Nested actions
We'll first parse the `actions` to generate the mapping of `id`->`Actions`:
We allow certain actions that take a form like this:
![figure 3](data-mockup-actions-and-ids.png)
```
{
// Select color scheme...
"name": { "key": "SetColorSchemeParentCommandName" },
"commands": [
{
"iterateOn": "schemes",
"name": "${scheme.name}",
"command": { "action": "setColorScheme", "colorScheme": "${scheme.name}" }
}
]
}
```
Then, we'll parse the `actions` to generate the mapping of keys to actions, with
some actions already being defined in the map of `id`->`Actions`:
For this case, having an `id` on the top level could potentially make sense when it comes to using that `id` in a menu, but not in the case of using that `id` for a keybinding. For the initial implementation, we will not support an `id` for these types of actions, which leaves us open to revisiting this in the future.
![figure 4](data-mockup-actions-and-ids-and-keys.png)
### Layering
When layering `actions`, if a later settings file contains an action with the
same `id`, it will replace the current value. In this way, users can redefine
@@ -103,9 +87,6 @@ As we add additional menus to the Terminal, like the customization for the new
tab dropdown, or the tab context menu, or the `TermControl` context menu, they
could all refer to these actions by `id`, rather than duplicating the same json.
As for fragments, all actions in fragments _must_ have an `id`. If a fragment provides an action without an `id`, or provides an `id` that clashes with one of the actions in `defaults.json`, that action will be ignored.
> 👉 NOTE: This will mean that actions will now need an `OriginTag`, similar to profiles and color schemes
### Existing Scenarios
@@ -234,8 +215,8 @@ actions manually.
the tab context menu or the control context menu.
<!-- Footnotes -->
[Command Palette Spec]: ./doc/specs/%232046%20-%20Command%20Palette.md
[New Tab Menu Customization Spec]: ./doc/specs/%231571%20-%20New%20Tab%20Menu%20Customization.md
[Command Palette Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%232046%20-%20Command%20Palette.md
[New Tab Menu Customization Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%231571%20-%20New%20Tab%20Menu%20Customization.md
[#1571]: https://github.com/microsoft/terminal/issues/1571
[#1912]: https://github.com/microsoft/terminal/issues/1912

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -1,396 +0,0 @@
---
author: Dustin Howett @DHowett <duhowett@microsoft.com>
created on: 2020-08-16
last updated: 2023-12-12
issue id: "#7335"
---
# Console Allocation Policy
## Abstract
Due to the design of the console subsystem on Windows as it has existed since Windows 95, every application that is
stamped with the `IMAGE_SUBSYSTEM_WINDOWS_CUI` subsystem in its PE header will be allocated a console by kernel32.
Any application that is stamped `IMAGE_SUBSYSTEM_WINDOWS_GUI` will not automatically be allocated a console.
This has worked fine for many years: when you double-click a console application in your GUI shell, it is allocated a
console. When you run a GUI application from your console shell, it is **not** allocated a console. The shell will
**not** wait for it to exit before returning you to a prompt.
There is a large class of applications that do not fit neatly into this mold. Take Python, Ruby, Perl, Lua, or even
VBScript: These languages are not relegated to running in a console session; they can be used to write fully-fledged GUI
applications like any other language.
Because their interpreters are console subsystem applications, however, any user double-clicking a shortcut to a Python
or Perl application will be presented with a console window that the language runtime may choose to garbage collect, or
may choose not to.
If the runtime chooses to hide the window, there will still be a brief period during which that window is visible. It is
inescapable.
Likewise, any user running that GUI application from a console shell will see their shell hang until the application
terminates.
All of these scripting languages worked around this by shipping two binaries each, identical in every way expect in
their subsystem fields. python/pythonw, perl/perlw, ruby/rubyw, wscript/cscript.
PowerShell[^1] is waiting to deal with this problem because they don't necessarily want to ship a `pwshw.exe` for all
of their GUI-only authors. Every additional `*w` version of an application is an additional maintenance burden and
source of cognitive overhead[^2] for users.
On the other side, you have mostly-GUI applications that want to print output to a console **if there is one
connected**.
These applications are still primarily GUI-driven, but they might support arguments like `/?` or `--help`. They only
need a console when they need to print out some text. Sometimes they'll allocate their own console (which opens a new
window) to display in, and sometimes they'll reattach to the originating console. VSCode does the latter, and so when
you run `code` from CMD, and then `exit` CMD, your console window sticks around because VSCode is still attached to it.
It will never print anything, and your only option is to close it.
There's another risk in reattaching, too. Given that the shell decides whether to wait based on the subsystem
field, GUI subsystem applications that reattach to their owning consoles *just to print some text* end up stomping on
the output of any shell that doesn't wait for them:
```
C:\> application --help
application - the interesting application
C:\> Usage: application [OPTIONS] ...
```
> _(the prompt is interleaved with the output)_
## Solution Design
I propose that we introduce a fusion manifest field, **consoleAllocationPolicy**, with the following values:
* _absent_
* `detached`
This field allows an application to disable the automatic allocation of a console, regardless of the [process creation flags]
passed to [`CreateProcess`] and its subsystem value.
It would look (roughly) like this:
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application>
<windowsSettings>
<consoleAllocationPolicy xmlns="http://schemas.microsoft.com/SMI/2024/WindowsSettings">detached</consoleAllocationPolicy>
</windowsSettings>
</application>
</assembly>
```
The effects of this field will only apply to binaries in the `IMAGE_SUBSYSTEM_WINDOWS_CUI` subsystem, as it pertains to
the particulars of their console allocation.
**All console inheritance will proceed as normal.** Since this field takes effect only in the absence of console
inheritance, CUI applications will still be able to run inside an existing console session.
| policy | behavior |
| - | - |
| _absent_ | _default behavior_ |
| `detached` | The new process is not attached to a console session (similar to `DETACHED_PROCESS`) unless one was inherited. |
An application that specifies the `detached` allocation policy will _not_ present a console window when launched by
Explorer, Task Scheduler, etc.
### Interaction with existing APIs
[`CreateProcess`] supports a number of [process creation flags] that dictate how a spawned application will behave with
regards to console allocation:
* `DETACHED_PROCESS`: No console inheritance, no console host spawned for the new process.
* `CREATE_NEW_CONSOLE`: No console inheritance, new console host **is** spawned for the new process.
* `CREATE_NO_WINDOW`: No console inheritance, new console host **is** spawned for the new process.
* this is the same as `CREATE_NEW_CONSOLE`, except that the first connection packet specifies that the window should
be invisible
Due to the design of [`CreateProcess`] and `ShellExecute`, this specification recommends that an allocation policy of
`detached` _override_ the inclusion of `CREATE_NEW_CONSOLE` in the `dwFlags` parameter to [`CreateProcess`].
> **Note**
> `ShellExecute` passes `CREATE_NEW_CONSOLE` _by default_ on all invocations. This impacts our ability to resolve the
> conflicts between these two APIs--`detached` policy and `CREATE_NEW_CONSOLE`--without auditing every call site in
> every Windows application that calls `ShellExecute` on a console application. Doing so is infeasible.
### Application impact
An application that opts into the `detached` console allocation policy will **not** be allocated a console unless one is
inherited. This presents an issue for applications like PowerShell that do want a console window when they are launched
directly.
Applications in this category can call `AllocConsole()` early in their startup to get fine-grained control over when a
console is presented.
The call to `AllocConsole()` will fail safely if the application has already inherited a console handle. It will succeed
if the application does not currently have a console handle.
> **Note**
> **Backwards Compatibility**: The behavior of `AllocConsole()` is not changing in response to this specification;
> therefore, applications that intend to run on older versions of Windows that do not support console allocation
> policies, which call `AllocConsole()`, will continue to behave normally.
### New APIs
Because a console-subsystem application may still want fine-grained control over when and how its console window is
spawned, we propose the inclusion of a new API, `AllocConsoleWithOptions(PALLOC_CONSOLE_OPTIONS)`.
#### `AllocConsoleWithOptions`
```c++
// Console Allocation Modes
typedef enum ALLOC_CONSOLE_MODE {
ALLOC_CONSOLE_MODE_DEFAULT = 0,
ALLOC_CONSOLE_MODE_NEW_WINDOW = 1,
ALLOC_CONSOLE_MODE_NO_WINDOW = 2
} ALLOC_CONSOLE_MODE;
typedef enum ALLOC_CONSOLE_RESULT {
ALLOC_CONSOLE_RESULT_NO_CONSOLE = 0,
ALLOC_CONSOLE_RESULT_NEW_CONSOLE = 1,
ALLOC_CONSOLE_RESULT_EXISTING_CONSOLE = 2
} ALLOC_CONSOLE_RESULT, *PALLOC_CONSOLE_RESULT;
typedef
struct ALLOC_CONSOLE_OPTIONS
{
ALLOC_CONSOLE_MODE mode;
BOOL useShowWindow;
WORD showWindow;
} ALLOC_CONSOLE_OPTIONS, *PALLOC_CONSOLE_OPTIONS;
WINBASEAPI
HRESULT
WINAPI
AllocConsoleWithOptions(_In_opt_ PALLOC_CONSOLE_OPTIONS allocOptions, _Out_opt_ PALLOC_CONSOLE_RESULT result);
```
**AllocConsoleWithOptions** affords an application control over how and when it begins a console session.
> [!NOTE]
> Unlike `AllocConsole`, `AllocConsoleWithOptions` without a mode (`ALLOC_CONSOLE_MODE_DEFAULT`) will only allocate a console if one was
> requested during `CreateProcess`.
>
> To override this behavior, pass one of `ALLOC_CONSOLE_MODE_NEW_WINDOW` (which is equivalent to being spawned with
> `CREATE_NEW_WINDOW`) or `ALLOC_CONSOLE_MODE_NO_WINDOW` (which is equivalent to being spawned with `CREATE_NO_CONSOLE`.)
##### Parameters
**allocOptions**: A pointer to a `ALLOC_CONSOLE_OPTIONS`.
**result**: An optional out pointer, which will be populated with a member of the `ALLOC_CONSOLE_RESULT` enum.
##### `ALLOC_CONSOLE_OPTIONS`
###### Members
**mode**: See the table below for the descriptions of the available modes.
**useShowWindow**: Specifies whether the value in `showWindow` should be used.
**showWindow**: If `useShowWindow` is set, specifies the ["show command"] used to display your
console window.
###### Return Value
`AllocConsoleWithOptions` will return `S_OK` and populate `result` to indicate whether--and how--a console session was
created.
`AllocConsoleWithOptions` will return a failing `HRESULT` if the request could not be completed.
###### Modes
| Mode | Description |
|:-------------------------------:| ------------------------------------------------------------------------------------------------------------------------------ |
| `ALLOC_CONSOLE_MODE_DEFAULT` | Allocate a console session if (and how) one was requested by the parent process. |
| `ALLOC_CONSOLE_MODE_NEW_WINDOW` | Allocate a console session with a window, even if this process was created with `CREATE_NO_CONSOLE` or `DETACHED_PROCESS`. |
| `ALLOC_CONSOLE_MODE_NO_WINDOW` | Allocate a console session _without_ a window, even if this process was created with `CREATE_NEW_WINDOW` or `DETACHED_PROCESS` |
###### Notes
Applications seeking backwards compatibility are encouraged to delay-load `AllocConsoleWithOptions` or check for its presence in
the `api-ms-win-core-console-l1` APISet.
## Inspiration
Fusion manifest entries are used to make application-scoped decisions like this all the time, like `longPathAware` and
`heapType`.
CUI applications that can spawn a UI (or GUI applications that can print to a console) are commonplace on other
platforms because there is no subsystem differentiation.
## UI/UX Design
There is no UI for this feature.
## Capabilities
### Accessibility
This should have no impact on accessibility.
### Security
One reviewer brought up the potential for a malicious actor to spawn an endless stream of headless daemon processes.
This proposal in no way changes the facilities available to malicious people for causing harm: they could have simply
used `IMAGE_SUBSYSTEM_WINDOWS_GUI` and not presented a UI--an option that has been available to them for 35 years.
### Reliability
This should have no impact on reliability.
### Compatibility
An existing application opting into **detached** may constitute a breaking change, but the scope of the breakage is
restricted to that application and is expected to be managed by the application.
All behavioral changes are opt-in.
> **EXAMPLE**: If Python updates python.exe to specify an allocation policy of **detached**, graphical python applications
> will become double-click runnable from the graphical shell without spawning a console window. _However_, console-based
> python applications will no longer spawn a console window when double-clicked from the graphical shell.
>
> In addition, if python.exe specifies **detached**, Console APIs will fail until a console is allocated.
Python could work around this by calling [`AllocConsole`] or [new API `AllocConsoleWithOptions`](#allocconsolewithoptions)
if it can be detected that console I/O is required.
#### Downlevel
On downlevel versions of Windows that do not understand (or expect) this manifest field, applications will allocate
consoles as specified by their image subsystem (described in the [abstract](#abstract) above).
### Performance, Power, and Efficiency
This should have no impact on performance, power or efficiency.
## Potential Issues
### Shell Hang
I am **not** proposing a change in how shells determine whether to wait for an application before returning to a prompt.
This means that a console subsystem application that intends to primarily present a UI but occasionally print text to a
console (therefore choosing the **detached** allocation policy) will cause the shell to "hang" and wait for it to
exit.
The decision to pause/wait is made entirely in the calling shell, and the console subsystem cannot influence that
decision.
Because the vast majority of shells on Windows "hang" by calling `WaitFor...Object` with a HANDLE to the spawned
process, an application that wants to be a "hybrid" CUI/GUI application will be forced to spawn a separate process to
detach from the shell and then terminate its main process.
This is very similar to the forking model seen in many POSIX-compliant operating systems.
### Launching interactively from Explorer, Task Scheduler, etc.
Applications like PowerShell may wish to retain automatic console allocation, and **detached** would be unsuitable for
them. If PowerShell specifies the `detached` console allocation policy, launching `pwsh.exe` from File Explorer it will
no longer spawn a console. This would almost certainly break PowerShell for all users.
Such applications can use `AllocConsole()` early in their startup.
At the same time, PowerShell wants `-WindowStyle Hidden` to suppress the console _before it's created_.
Applications in this category can use `AllocConsoleWithOptions()` to specify additional information about the new console window.
PowerShell, and any other shell that wishes to maintain interactive launch from the graphical shell, can start in
**detached** mode and then allocate a console as necessary. Therefore:
* PowerShell will set `<consoleAllocationPolicy>detached</consoleAllocationPolicy>`
* On startup, it will process its commandline arguments.
* If `-WindowStyle Hidden` is **not** present (the default case), it can:
* `AllocConsole()` or `AllocConsoleWithOptions(NULL)`
* Either of these APIs will present a console window (or not) based on the flags passed through `STARTUPINFO` during
[`CreateProcess`].
* If `-WindowStyle Hidden` is present, it can:
* `AllocConsoleWithOptions(&alloc)` where `alloc.mode` specifies `ALLOC_CONSOLE_MODE_HIDDEN`
## Future considerations
We're introducing a new manifest field today -- what if we want to introduce more? Should we have a `consoleSettings`
manifest block?
Are there other allocation policies we need to consider?
## Resources
### Rejected Solutions
- A new PE subsystem, `IMAGE_SUBSYSTEM_WINDOWS_HYBRID`
- it would behave like **inheritOnly**
- relies on shells to update and check for this
- checking a subsystem doesn't work right with app execution aliases[^3]
- This is not a new problem, but it digs the hole a little deeper.
- requires standardization outside of Microsoft because the PE format is a dependency of the UEFI specification[^4]
- 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
- 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?
- fragile, somewhat silly, exporting symbols from EXEs is annoying and uncommon
An earlier version of this specification offered the **always** allocation policy, with the following behaviors:
> **STRUCK FROM SPECIFICATION**
>
> * A GUI subsystem application would always get a console window.
> * A command-line shell would not wait for it to exit before returning a prompt.
It was cut because a GUI application that wants a console window can simply attach to an existing console session or
allocate a new one. We found no compelling use case that would require the forced allocation of a console session
outside of the application's code.
An earlier version of this specification offered the **inheritOnly** allocation policy, instead of the finer-grained
**hidden** and **detached** policies. We deemed it insufficient for PowerShell's use case because any application
launched by an **inheritOnly** PowerShell would immediately force the uncontrolled allocation of a console window.
> **STRUCK FROM SPECIFICATION**
>
> The move to **hidden** allows PowerShell to offer a fully-fledged console connection that can be itself inherited by a
> downstream application.
#### Additional allocation policies
An earlier revision of this specification suggested two allocation policies:
> **STRUCK FROM SPECIFICATION**
>
> **hidden** is intended to be used by console applications that want finer-grained control over the visibility of their
> console windows, but that still need a console host to service console APIs. This includes most scripting language
> interpreters.
>
> **detached** is intended to be used by primarily graphical applications that would like to operate against a console _if
> one is present_ but do not mind its absence. This includes any graphical tool with a `--help` or `/?` argument.
The `hidden` policy was rejected due to an incompatibility with modern console hosting, as `hidden` would require an
application to interact with the console window via `GetConsoleWindow()` and explicitly show it.
> **STRUCK FROM SPECIFICATION**
>
> ##### ShowWindow and ConPTY
>
> The pseudoconsole creates a hidden window to service `GetConsoleWindow()`, and it can be trivially shown using
> `ShowWindow`. If we recommend that applications `ShowWindow` on startup, we will need to guard the pseudoconsole's
> pseudo-window from being shown.
[^1]: [Powershell -WindowStyle Hidden still shows a window briefly]
[^2]: [StackOverflow: pythonw.exe or python.exe?]
[^3]: [PowerShell: Windows Store applications incorrectly assumed to be console applications]
[^4]: [UEFI spec 2.6 appendix Q.1]
[Powershell -WindowStyle Hidden still shows a window briefly]: https://github.com/PowerShell/PowerShell/issues/3028
[PowerShell: Windows Store applications incorrectly assumed to be console applications]: https://github.com/PowerShell/PowerShell/issues/9970
[StackOverflow: pythonw.exe or python.exe?]: https://stackoverflow.com/questions/9705982/pythonw-exe-or-python-exe
[UEFI spec 2.6 appendix Q.1]: https://www.uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf
[`AllocConsole`]: https://docs.microsoft.com/windows/console/allocconsole
[`CreateProcess`]: https://docs.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
[process creation flags]: https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
["show command"]: https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-showwindow

View File

@@ -290,7 +290,7 @@ though. **I recommend we ignore this for now, and leave this as a follow-up**.
For reference, refer to the following from iTerm2:
![image](https://user-images.githubusercontent.com/2578976/64075757-fa971980-ccee-11e9-9e44-47aaf3bca76c.png)
We don't have a menu bar like on macOS, but we do have a tab context menu. We
We don't have a menu bar like on MacOS, but we do have a tab context menu. We
could add these items as a nested entry under each tab. If we wanted to do this,
we should also make sure to dynamically change the icon of the MenuItem to
reflect the current broadcast state.

View File

@@ -373,7 +373,7 @@ changes, or the active pane in a tab changes:
`TabRowControl` to match.
The `tab.cornerRadius` might be a bit trickier to implement. Currently, there's
no XAML resource that controls this, nor is this something that's exposed by
not a XAML resource that controls this, nor is this something that's exposed by
the TabView control. Fortunately, this is something that's exposed to us
programmatically. We'll need to manually set that value on each `TabViewItem` as
we create new tabs. When we reload settings, we'll need to make sure to come

View File

@@ -142,4 +142,4 @@ Feature Notes:
[#4472]: https://github.com/microsoft/terminal/issues/4472
[#8048]: https://github.com/microsoft/terminal/pull/8048
[Terminal 2022 Roadmap]: ./roadmap-2022.md
[Terminal 2022 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/roadmap-2022.md

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -8,5 +8,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
### Fonts Included
* Cascadia Code, Cascadia Mono (2111.01)
* from microsoft/cascadia-code@de36d62e777d34d3bed92a7e23988e5d61e0ba02
* Cascadia Code, Cascadia Mono (2404.23)
* from microsoft/cascadia-code@1034791e5fc6e060a448d2b29cd94a6c683edb36

View File

@@ -1,23 +0,0 @@
// Demo shader to show passing in an image using
// experimental.pixelShaderImagePath. This shader simply displays the Terminal
// contents on top of the given image.
//
// The image loaded by the terminal will be placed into the `image` texture.
SamplerState samplerState;
Texture2D shaderTexture : register(t0);
Texture2D image : register(t1);
cbuffer PixelShaderSettings {
float Time;
float Scale;
float2 Resolution;
float4 Background;
};
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
float4 terminalColor = shaderTexture.Sample(samplerState, tex);
float4 imageColor = image.Sample(samplerState, tex);
return lerp(imageColor, terminalColor, terminalColor.a);
}

View File

@@ -118,6 +118,6 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AppDescription" xml:space="preserve">
<value>А şςѓάţćћ ǻрр ƒθŗ χÂΜĿ Íŝĺąήðş ŧеšτş !!! !!! !!! !</value>
<value>Ά śςґàτсн ąρφ ƒоř ΧΆΜĻ Ìŝļàиđś τёşτś !!! !!! !!! !</value>
</data>
</root>

View File

@@ -118,6 +118,6 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AppDescription" xml:space="preserve">
<value>Ă šςґаτćĥ àρφ ƒǿя ЖΆΜĹ Іѕℓаñďş ťêšţŝ !!! !!! !!! !</value>
<value>Ά śςґàτсн ąρφ ƒоř ΧΆΜĻ Ìŝļàиđś τёşτś !!! !!! !!! !</value>
</data>
</root>

View File

@@ -118,6 +118,6 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AppDescription" xml:space="preserve">
<value>Ă śćяǻт¢н ãрρ ƒσг ХĂМĽ Īşłдήďѕ ťέśτş !!! !!! !!! !</value>
<value>Ά śςґàτсн ąρφ ƒоř ΧΆΜĻ Ìŝļàиđś τёşτś !!! !!! !!! !</value>
</data>
</root>

View File

@@ -31,12 +31,9 @@ namespace winrt::SampleApp::implementation
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted in-proc...",
winrt::hstring{},
L"",
false,
L"",
nullptr,
32,
80,
winrt::guid(),
winrt::guid()) };
// "Microsoft.Terminal.TerminalConnection.ConptyConnection"

View File

@@ -392,18 +392,6 @@ til::CoordType ROW::AdjustToGlyphStart(til::CoordType column) const noexcept
return _adjustBackward(_clampedColumn(column));
}
// Returns the (exclusive) ending column of the glyph at the given column.
// In other words, if you have 3 wide glyphs
// AA BB CC
// 01 23 45 <-- column
// Examples:
// - `AdjustToGlyphEnd(4)` returns 6.
// - `AdjustToGlyphEnd(3)` returns 4.
til::CoordType ROW::AdjustToGlyphEnd(til::CoordType column) const noexcept
{
return _adjustForward(_clampedColumnInclusive(column));
}
// Routine Description:
// - clears char data in column in row
// Arguments:
@@ -939,10 +927,36 @@ uint16_t ROW::size() const noexcept
return _columnCount;
}
// Routine Description:
// - Retrieves the column that is one after the last non-space character in the row.
til::CoordType ROW::GetLastNonSpaceColumn() const noexcept
til::CoordType ROW::MeasureLeft() const noexcept
{
const auto text = GetText();
const auto beg = text.begin();
const auto end = text.end();
auto it = beg;
for (; it != end; ++it)
{
if (*it != L' ')
{
break;
}
}
return gsl::narrow_cast<til::CoordType>(it - beg);
}
til::CoordType ROW::MeasureRight() const noexcept
{
if (_wrapForced)
{
auto width = _columnCount;
if (_doubleBytePadded)
{
width--;
}
return width;
}
const auto text = GetText();
const auto beg = text.begin();
const auto end = text.end();
@@ -962,42 +976,7 @@ til::CoordType ROW::GetLastNonSpaceColumn() const noexcept
//
// An example: The row is 10 cells wide and `it` points to the second character.
// `it - beg` would return 1, but it's possible it's actually 1 wide glyph and 8 whitespace.
return gsl::narrow_cast<til::CoordType>(GetReadableColumnCount() - (end - it));
}
til::CoordType ROW::MeasureLeft() const noexcept
{
const auto text = GetText();
const auto beg = text.begin();
const auto end = text.end();
auto it = beg;
for (; it != end; ++it)
{
if (*it != L' ')
{
break;
}
}
return gsl::narrow_cast<til::CoordType>(it - beg);
}
// Routine Description:
// - Retrieves the column that is one after the last valid character in the row.
til::CoordType ROW::MeasureRight() const noexcept
{
if (_wrapForced)
{
auto width = _columnCount;
if (_doubleBytePadded)
{
width--;
}
return width;
}
return GetLastNonSpaceColumn();
return gsl::narrow_cast<til::CoordType>(_columnCount - (end - it));
}
bool ROW::ContainsText() const noexcept

View File

@@ -137,7 +137,6 @@ public:
til::CoordType NavigateToPrevious(til::CoordType column) const noexcept;
til::CoordType NavigateToNext(til::CoordType column) const noexcept;
til::CoordType AdjustToGlyphStart(til::CoordType column) const noexcept;
til::CoordType AdjustToGlyphEnd(til::CoordType column) const noexcept;
void ClearCell(til::CoordType column);
OutputCellIterator WriteCells(OutputCellIterator it, til::CoordType columnBegin, std::optional<bool> wrap = std::nullopt, std::optional<til::CoordType> limitRight = std::nullopt);
@@ -152,7 +151,6 @@ public:
TextAttribute GetAttrByColumn(til::CoordType column) const;
std::vector<uint16_t> GetHyperlinks() const;
uint16_t size() const noexcept;
til::CoordType GetLastNonSpaceColumn() const noexcept;
til::CoordType MeasureLeft() const noexcept;
til::CoordType MeasureRight() const noexcept;
bool ContainsText() const noexcept;

View File

@@ -111,28 +111,6 @@ const til::point_span* Search::GetCurrent() const noexcept
return nullptr;
}
void Search::HighlightResults() const
{
std::vector<til::inclusive_rect> toSelect;
const auto& textBuffer = _renderData->GetTextBuffer();
for (const auto& r : _results)
{
const auto rbStart = textBuffer.BufferToScreenPosition(r.start);
const auto rbEnd = textBuffer.BufferToScreenPosition(r.end);
til::inclusive_rect re;
re.top = rbStart.y;
re.bottom = rbEnd.y;
re.left = rbStart.x;
re.right = rbEnd.x;
toSelect.emplace_back(re);
}
_renderData->SelectSearchRegions(std::move(toSelect));
}
// Routine Description:
// - Takes the found word and selects it in the screen buffer
@@ -149,7 +127,6 @@ bool Search::SelectCurrent() const
return true;
}
_renderData->ClearSelection();
return false;
}

View File

@@ -33,7 +33,6 @@ public:
void FindNext() noexcept;
const til::point_span* GetCurrent() const noexcept;
void HighlightResults() const;
bool SelectCurrent() const;
const std::vector<til::point_span>& Results() const noexcept;

View File

@@ -1366,32 +1366,36 @@ til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, co
{
auto result = target;
const auto bufferSize = GetSize();
auto stayAtOrigin = false;
// ignore left boundary. Continue until readable text found
while (_GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar)
{
if (result == bufferSize.Origin())
if (!bufferSize.DecrementInBounds(result))
{
//looped around and hit origin (no word between origin and target)
return result;
// first char in buffer is a DelimiterChar or ControlChar
// we can't move any further back
stayAtOrigin = true;
break;
}
bufferSize.DecrementInBounds(result);
}
// make sure we expand to the left boundary or the beginning of the word
while (_GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar)
{
if (result == bufferSize.Origin())
if (!bufferSize.DecrementInBounds(result))
{
// first char in buffer is a RegularChar
// we can't move any further back
return result;
break;
}
bufferSize.DecrementInBounds(result);
}
// move off of delimiter
bufferSize.IncrementInBounds(result);
// move off of delimiter and onto word start
if (!stayAtOrigin && _GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar)
{
bufferSize.IncrementInBounds(result);
}
return result;
}
@@ -1409,16 +1413,10 @@ til::point TextBuffer::_GetWordStartForSelection(const til::point target, const
const auto bufferSize = GetSize();
const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters);
const bool isControlChar = initialDelimiter == DelimiterClass::ControlChar;
// expand left until we hit the left boundary or a different delimiter class
while (result != bufferSize.Origin() && _GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter)
while (result.x > bufferSize.Left() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
{
//prevent selection wrapping on whitespace selection
if (isControlChar && result.x == bufferSize.Left())
{
break;
}
bufferSize.DecrementInBounds(result);
}
@@ -1496,21 +1494,25 @@ til::point TextBuffer::_GetWordEndForAccessibility(const til::point target, cons
}
else
{
while (result != limit && result != bufferSize.BottomRightInclusive() && _GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar)
auto iter{ GetCellDataAt(result, bufferSize) };
while (iter && iter.Pos() != limit && _GetDelimiterClassAt(iter.Pos(), wordDelimiters) == DelimiterClass::RegularChar)
{
// Iterate through readable text
bufferSize.IncrementInBounds(result);
++iter;
}
while (result != limit && result != bufferSize.BottomRightInclusive() && _GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar)
while (iter && iter.Pos() != limit && _GetDelimiterClassAt(iter.Pos(), wordDelimiters) != DelimiterClass::RegularChar)
{
// expand to the beginning of the NEXT word
bufferSize.IncrementInBounds(result);
++iter;
}
// Special case: we tried to move one past the end of the buffer
result = iter.Pos();
// Special case: we tried to move one past the end of the buffer,
// but iter prevented that (because that pos doesn't exist).
// Manually increment onto the EndExclusive point.
if (result == bufferSize.BottomRightInclusive())
if (!iter)
{
bufferSize.IncrementInBounds(result, true);
}
@@ -1530,18 +1532,19 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st
{
const auto bufferSize = GetSize();
// can't expand right
if (target.x == bufferSize.RightInclusive())
{
return target;
}
auto result = target;
const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters);
const bool isControlChar = initialDelimiter == DelimiterClass::ControlChar;
// expand right until we hit the right boundary as a ControlChar or a different delimiter class
while (result != bufferSize.BottomRightInclusive() && _GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter)
// expand right until we hit the right boundary or a different delimiter class
while (result.x < bufferSize.RightInclusive() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
{
if (isControlChar && result.x == bufferSize.RightInclusive())
{
break;
}
bufferSize.IncrementInBoundsCircular(result);
bufferSize.IncrementInBounds(result);
}
if (_GetDelimiterClassAt(result, wordDelimiters) != initialDelimiter)
@@ -1953,6 +1956,135 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
}
}
// Routine Description:
// - Retrieves the text data from the selected region and presents it in a clipboard-ready format (given little post-processing).
// Arguments:
// - includeCRLF - inject CRLF pairs to the end of each line
// - trimTrailingWhitespace - remove the trailing whitespace at the end of each line
// - textRects - the rectangular regions from which the data will be extracted from the buffer (i.e.: selection rects)
// - GetAttributeColors - function used to map TextAttribute to RGB COLORREFs. If null, only extract the text.
// - formatWrappedRows - if set we will apply formatting (CRLF inclusion and whitespace trimming) on wrapped rows
// Return Value:
// - The text, background color, and foreground color data of the selected region of the text buffer.
const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
const bool trimTrailingWhitespace,
const std::vector<til::inclusive_rect>& selectionRects,
std::function<std::pair<COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors,
const bool formatWrappedRows) const
{
TextAndColor data;
const auto copyTextColor = GetAttributeColors != nullptr;
// preallocate our vectors to reduce reallocs
const auto rows = selectionRects.size();
data.text.reserve(rows);
if (copyTextColor)
{
data.FgAttr.reserve(rows);
data.BkAttr.reserve(rows);
}
// for each row in the selection
for (size_t i = 0; i < rows; i++)
{
const auto iRow = selectionRects.at(i).top;
const auto highlight = Viewport::FromInclusive(selectionRects.at(i));
// retrieve the data from the screen buffer
auto it = GetCellDataAt(highlight.Origin(), highlight);
// allocate a string buffer
std::wstring selectionText;
std::vector<COLORREF> selectionFgAttr;
std::vector<COLORREF> selectionBkAttr;
// preallocate to avoid reallocs
selectionText.reserve(gsl::narrow<size_t>(highlight.Width()) + 2); // + 2 for \r\n if we munged it
if (copyTextColor)
{
selectionFgAttr.reserve(gsl::narrow<size_t>(highlight.Width()) + 2);
selectionBkAttr.reserve(gsl::narrow<size_t>(highlight.Width()) + 2);
}
// copy char data into the string buffer, skipping trailing bytes
while (it)
{
const auto& cell = *it;
if (cell.DbcsAttr() != DbcsAttribute::Trailing)
{
const auto chars = cell.Chars();
selectionText.append(chars);
if (copyTextColor)
{
const auto cellData = cell.TextAttr();
const auto [CellFgAttr, CellBkAttr] = GetAttributeColors(cellData);
for (size_t j = 0; j < chars.size(); ++j)
{
selectionFgAttr.push_back(CellFgAttr);
selectionBkAttr.push_back(CellBkAttr);
}
}
}
++it;
}
// We apply formatting to rows if the row was NOT wrapped or formatting of wrapped rows is allowed
const auto shouldFormatRow = formatWrappedRows || !GetRowByOffset(iRow).WasWrapForced();
if (trimTrailingWhitespace)
{
if (shouldFormatRow)
{
// remove the spaces at the end (aka trim the trailing whitespace)
while (!selectionText.empty() && selectionText.back() == UNICODE_SPACE)
{
selectionText.pop_back();
if (copyTextColor)
{
selectionFgAttr.pop_back();
selectionBkAttr.pop_back();
}
}
}
}
// apply CR/LF to the end of the final string, unless we're the last line.
// a.k.a if we're earlier than the bottom, then apply CR/LF.
if (includeCRLF && i < selectionRects.size() - 1)
{
if (shouldFormatRow)
{
// then we can assume a CR/LF is proper
selectionText.push_back(UNICODE_CARRIAGERETURN);
selectionText.push_back(UNICODE_LINEFEED);
if (copyTextColor)
{
// can't see CR/LF so just use black FG & BK
const auto Blackness = RGB(0x00, 0x00, 0x00);
selectionFgAttr.push_back(Blackness);
selectionFgAttr.push_back(Blackness);
selectionBkAttr.push_back(Blackness);
selectionBkAttr.push_back(Blackness);
}
}
}
data.text.emplace_back(std::move(selectionText));
if (copyTextColor)
{
data.FgAttr.emplace_back(std::move(selectionFgAttr));
data.BkAttr.emplace_back(std::move(selectionBkAttr));
}
}
return data;
}
size_t TextBuffer::SpanLength(const til::point coordStart, const til::point coordEnd) const
{
const auto bufferSize = GetSize();
@@ -1991,292 +2123,186 @@ std::wstring TextBuffer::GetPlainText(const til::point& start, const til::point&
}
// Routine Description:
// - Given a copy request and a row, retrieves the row bounds [begin, end) and
// a boolean indicating whether a line break should be added to this row.
// - Generates a CF_HTML compliant structure based on the passed in text and color data
// Arguments:
// - req - the copy request
// - iRow - the row index
// - row - the row
// Return Value:
// - The row bounds and a boolean for line break
std::tuple<til::CoordType, til::CoordType, bool> TextBuffer::_RowCopyHelper(const TextBuffer::CopyRequest& req, const til::CoordType iRow, const ROW& row) const
{
til::CoordType rowBeg = 0;
til::CoordType rowEnd = 0;
if (req.blockSelection)
{
const auto lineRendition = row.GetLineRendition();
const auto minX = req.bufferCoordinates ? req.minX : ScreenToBufferLine(til::point{ req.minX, iRow }, lineRendition).x;
const auto maxX = req.bufferCoordinates ? req.maxX : ScreenToBufferLine(til::point{ req.maxX, iRow }, lineRendition).x;
rowBeg = minX;
rowEnd = maxX + 1; // +1 to get an exclusive end
}
else
{
const auto lineRendition = row.GetLineRendition();
const auto beg = req.bufferCoordinates ? req.beg : ScreenToBufferLine(req.beg, lineRendition);
const auto end = req.bufferCoordinates ? req.end : ScreenToBufferLine(req.end, lineRendition);
rowBeg = iRow != beg.y ? 0 : beg.x;
rowEnd = iRow != end.y ? row.GetReadableColumnCount() : end.x + 1; // +1 to get an exclusive end
}
// Our selection mechanism doesn't stick to glyph boundaries at the moment.
// We need to adjust begin and end points manually to avoid partially
// selected glyphs.
rowBeg = row.AdjustToGlyphStart(rowBeg);
rowEnd = row.AdjustToGlyphEnd(rowEnd);
// When `formatWrappedRows` is set, apply formatting on all rows (wrapped
// and non-wrapped), but when it's false, format non-wrapped rows only.
const auto shouldFormatRow = req.formatWrappedRows || !row.WasWrapForced();
// trim trailing whitespace
if (shouldFormatRow && req.trimTrailingWhitespace)
{
rowEnd = std::min(rowEnd, row.GetLastNonSpaceColumn());
}
// line breaks
const auto addLineBreak = shouldFormatRow && req.includeLineBreak;
return { rowBeg, rowEnd, addLineBreak };
}
// Routine Description:
// - Retrieves the text data from the buffer and presents it in a clipboard-ready format.
// Arguments:
// - req - the copy request having the bounds of the selected region and other related configuration flags.
// Return Value:
// - The text data from the selected region of the text buffer. Empty if the copy request is invalid.
std::wstring TextBuffer::GetPlainText(const CopyRequest& req) const
{
if (req.beg > req.end)
{
return {};
}
std::wstring selectedText;
for (auto iRow = req.beg.y; iRow <= req.end.y; ++iRow)
{
const auto& row = GetRowByOffset(iRow);
const auto& [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper(req, iRow, row);
// save selected text
selectedText += row.GetText(rowBeg, rowEnd);
if (addLineBreak && iRow != req.end.y)
{
selectedText += L"\r\n";
}
}
return selectedText;
}
// Routine Description:
// - Generates a CF_HTML compliant structure from the selected region of the buffer
// Arguments:
// - req - the copy request having the bounds of the selected region and other related configuration flags.
// - rows - the text and color data we will format & encapsulate
// - backgroundColor - default background color for characters, also used in padding
// - fontHeightPoints - the unscaled font height
// - fontFaceName - the name of the font used
// - backgroundColor - default background color for characters, also used in padding
// - isIntenseBold - true if being intense is treated as being bold
// - GetAttributeColors - function to get the colors of the text attributes as they're rendered
// Return Value:
// - string containing the generated HTML. Empty if the copy request is invalid.
std::string TextBuffer::GenHTML(const CopyRequest& req,
// - string containing the generated HTML
std::string TextBuffer::GenHTML(const TextAndColor& rows,
const int fontHeightPoints,
const std::wstring_view fontFaceName,
const COLORREF backgroundColor,
const bool isIntenseBold,
std::function<std::tuple<COLORREF, COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors) const noexcept
const COLORREF backgroundColor)
{
// GH#5347 - Don't provide a title for the generated HTML, as many
// web applications will paste the title first, followed by the HTML
// content, which is unexpected.
if (req.beg > req.end)
{
return {};
}
try
{
std::string htmlBuilder;
std::ostringstream htmlBuilder;
// First we have to add some standard HTML boiler plate required for
// CF_HTML as part of the HTML Clipboard format
constexpr std::string_view htmlHeader = "<!DOCTYPE><HTML><HEAD></HEAD><BODY>";
htmlBuilder += htmlHeader;
// First we have to add some standard
// HTML boiler plate required for CF_HTML
// as part of the HTML Clipboard format
const std::string htmlHeader =
"<!DOCTYPE><HTML><HEAD></HEAD><BODY>";
htmlBuilder << htmlHeader;
htmlBuilder += "<!--StartFragment -->";
htmlBuilder << "<!--StartFragment -->";
// apply global style in div element
{
htmlBuilder += "<DIV STYLE=\"";
htmlBuilder += "display:inline-block;";
htmlBuilder += "white-space:pre;";
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("background-color:{};"), Utils::ColorToHexString(backgroundColor));
htmlBuilder << "<DIV STYLE=\"";
htmlBuilder << "display:inline-block;";
htmlBuilder << "white-space:pre;";
htmlBuilder << "background-color:";
htmlBuilder << Utils::ColorToHexString(backgroundColor);
htmlBuilder << ";";
htmlBuilder << "font-family:";
htmlBuilder << "'";
htmlBuilder << ConvertToA(CP_UTF8, fontFaceName);
htmlBuilder << "',";
// even with different font, add monospace as fallback
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("font-family:'{}',monospace;"), til::u16u8(fontFaceName));
htmlBuilder << "monospace;";
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("font-size:{}pt;"), fontHeightPoints);
htmlBuilder << "font-size:";
htmlBuilder << fontHeightPoints;
htmlBuilder << "pt;";
// note: MS Word doesn't support padding (in this way at least)
// todo: customizable padding
htmlBuilder += "padding:4px;";
htmlBuilder << "padding:";
htmlBuilder << 4; // todo: customizable padding
htmlBuilder << "px;";
htmlBuilder += "\">";
htmlBuilder << "\">";
}
for (auto iRow = req.beg.y; iRow <= req.end.y; ++iRow)
// copy text and info color from buffer
auto hasWrittenAnyText = false;
std::optional<COLORREF> fgColor = std::nullopt;
std::optional<COLORREF> bkColor = std::nullopt;
for (size_t row = 0; row < rows.text.size(); row++)
{
const auto& row = GetRowByOffset(iRow);
const auto [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper(req, iRow, row);
const auto rowBegU16 = gsl::narrow_cast<uint16_t>(rowBeg);
const auto rowEndU16 = gsl::narrow_cast<uint16_t>(rowEnd);
const auto runs = row.Attributes().slice(rowBegU16, rowEndU16).runs();
size_t startOffset = 0;
auto x = rowBegU16;
for (const auto& [attr, length] : runs)
if (row != 0)
{
const auto nextX = gsl::narrow_cast<uint16_t>(x + length);
const auto [fg, bg, ul] = GetAttributeColors(attr);
const auto fgHex = Utils::ColorToHexString(fg);
const auto bgHex = Utils::ColorToHexString(bg);
const auto ulHex = Utils::ColorToHexString(ul);
const auto ulStyle = attr.GetUnderlineStyle();
const auto isUnderlined = ulStyle != UnderlineStyle::NoUnderline;
const auto isCrossedOut = attr.IsCrossedOut();
const auto isOverlined = attr.IsOverlined();
htmlBuilder += "<SPAN STYLE=\"";
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("color:{};"), fgHex);
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("background-color:{};"), bgHex);
if (isIntenseBold && attr.IsIntense())
{
htmlBuilder += "font-weight:bold;";
}
if (attr.IsItalic())
{
htmlBuilder += "font-style:italic;";
}
if (isCrossedOut || isOverlined)
{
fmt::format_to(std::back_inserter(htmlBuilder),
FMT_COMPILE("text-decoration:{} {} {};"),
isCrossedOut ? "line-through" : "",
isOverlined ? "overline" : "",
fgHex);
}
if (isUnderlined)
{
// Since underline, overline and strikethrough use the same css property,
// we cannot apply different colors to them at the same time. However, we
// can achieve the desired result by creating a nested <span> and applying
// underline style and color to it.
htmlBuilder += "\"><SPAN STYLE=\"";
switch (ulStyle)
{
case UnderlineStyle::NoUnderline:
break;
case UnderlineStyle::DoublyUnderlined:
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline double {};"), ulHex);
break;
case UnderlineStyle::CurlyUnderlined:
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline wavy {};"), ulHex);
break;
case UnderlineStyle::DottedUnderlined:
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline dotted {};"), ulHex);
break;
case UnderlineStyle::DashedUnderlined:
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline dashed {};"), ulHex);
break;
case UnderlineStyle::SinglyUnderlined:
default:
fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline {};"), ulHex);
break;
}
}
htmlBuilder += "\">";
// text
std::string unescapedText;
THROW_IF_FAILED(til::u16u8(row.GetText(x, nextX), unescapedText));
for (const auto c : unescapedText)
{
switch (c)
{
case '<':
htmlBuilder += "&lt;";
break;
case '>':
htmlBuilder += "&gt;";
break;
case '&':
htmlBuilder += "&amp;";
break;
default:
htmlBuilder += c;
}
}
if (isUnderlined)
{
// close the nested span we created for underline
htmlBuilder += "</SPAN>";
}
htmlBuilder += "</SPAN>";
// advance to next run of text
x = nextX;
htmlBuilder << "<BR>";
}
// never add line break to the last row.
if (addLineBreak && iRow < req.end.y)
for (size_t col = 0; col < rows.text.at(row).length(); col++)
{
htmlBuilder += "<BR>";
const auto writeAccumulatedChars = [&](bool includeCurrent) {
if (col >= startOffset)
{
const auto unescapedText = ConvertToA(CP_UTF8, std::wstring_view(rows.text.at(row)).substr(startOffset, col - startOffset + includeCurrent));
for (const auto c : unescapedText)
{
switch (c)
{
case '<':
htmlBuilder << "&lt;";
break;
case '>':
htmlBuilder << "&gt;";
break;
case '&':
htmlBuilder << "&amp;";
break;
default:
htmlBuilder << c;
}
}
startOffset = col;
}
};
if (rows.text.at(row).at(col) == '\r' || rows.text.at(row).at(col) == '\n')
{
// do not include \r nor \n as they don't have color attributes
// and are not HTML friendly. For line break use '<BR>' instead.
writeAccumulatedChars(false);
break;
}
auto colorChanged = false;
if (!fgColor.has_value() || rows.FgAttr.at(row).at(col) != fgColor.value())
{
fgColor = rows.FgAttr.at(row).at(col);
colorChanged = true;
}
if (!bkColor.has_value() || rows.BkAttr.at(row).at(col) != bkColor.value())
{
bkColor = rows.BkAttr.at(row).at(col);
colorChanged = true;
}
if (colorChanged)
{
writeAccumulatedChars(false);
if (hasWrittenAnyText)
{
htmlBuilder << "</SPAN>";
}
htmlBuilder << "<SPAN STYLE=\"";
htmlBuilder << "color:";
htmlBuilder << Utils::ColorToHexString(fgColor.value());
htmlBuilder << ";";
htmlBuilder << "background-color:";
htmlBuilder << Utils::ColorToHexString(bkColor.value());
htmlBuilder << ";";
htmlBuilder << "\">";
}
hasWrittenAnyText = true;
// if this is the last character in the row, flush the whole row
if (col == rows.text.at(row).length() - 1)
{
writeAccumulatedChars(true);
}
}
}
htmlBuilder += "</DIV>";
if (hasWrittenAnyText)
{
// last opened span wasn't closed in loop above, so close it now
htmlBuilder << "</SPAN>";
}
htmlBuilder += "<!--EndFragment -->";
htmlBuilder << "</DIV>";
htmlBuilder << "<!--EndFragment -->";
constexpr std::string_view HtmlFooter = "</BODY></HTML>";
htmlBuilder += HtmlFooter;
htmlBuilder << HtmlFooter;
// once filled with values, there will be exactly 157 bytes in the clipboard header
constexpr size_t ClipboardHeaderSize = 157;
// these values are byte offsets from start of clipboard
const auto htmlStartPos = ClipboardHeaderSize;
const auto htmlEndPos = ClipboardHeaderSize + gsl::narrow<size_t>(htmlBuilder.length());
const auto htmlEndPos = ClipboardHeaderSize + gsl::narrow<size_t>(htmlBuilder.tellp());
const auto fragStartPos = ClipboardHeaderSize + gsl::narrow<size_t>(htmlHeader.length());
const auto fragEndPos = htmlEndPos - HtmlFooter.length();
// header required by HTML 0.9 format
std::string clipHeaderBuilder;
clipHeaderBuilder += "Version:0.9\r\n";
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("StartHTML:{:0>10}\r\n"), htmlStartPos);
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("EndHTML:{:0>10}\r\n"), htmlEndPos);
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("StartFragment:{:0>10}\r\n"), fragStartPos);
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("EndFragment:{:0>10}\r\n"), fragEndPos);
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("StartSelection:{:0>10}\r\n"), fragStartPos);
fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("EndSelection:{:0>10}\r\n"), fragEndPos);
std::ostringstream clipHeaderBuilder;
clipHeaderBuilder << "Version:0.9\r\n";
clipHeaderBuilder << std::setfill('0');
clipHeaderBuilder << "StartHTML:" << std::setw(10) << htmlStartPos << "\r\n";
clipHeaderBuilder << "EndHTML:" << std::setw(10) << htmlEndPos << "\r\n";
clipHeaderBuilder << "StartFragment:" << std::setw(10) << fragStartPos << "\r\n";
clipHeaderBuilder << "EndFragment:" << std::setw(10) << fragEndPos << "\r\n";
clipHeaderBuilder << "StartSelection:" << std::setw(10) << fragStartPos << "\r\n";
clipHeaderBuilder << "EndSelection:" << std::setw(10) << fragEndPos << "\r\n";
return clipHeaderBuilder + htmlBuilder;
return clipHeaderBuilder.str() + htmlBuilder.str();
}
catch (...)
{
@@ -2286,36 +2312,25 @@ std::string TextBuffer::GenHTML(const CopyRequest& req,
}
// Routine Description:
// - Generates an RTF document from the selected region of the buffer
// - Generates an RTF document based on the passed in text and color data
// RTF 1.5 Spec: https://www.biblioscape.com/rtf15_spec.htm
// RTF 1.9.1 Spec: https://msopenspecs.azureedge.net/files/Archive_References/[MSFT-RTF].pdf
// Arguments:
// - req - the copy request having the bounds of the selected region and other related configuration flags.
// - rows - the text and color data we will format & encapsulate
// - backgroundColor - default background color for characters, also used in padding
// - fontHeightPoints - the unscaled font height
// - fontFaceName - the name of the font used
// - backgroundColor - default background color for characters, also used in padding
// - isIntenseBold - true if being intense is treated as being bold
// - GetAttributeColors - function to get the colors of the text attributes as they're rendered
// - htmlTitle - value used in title tag of html header. Used to name the application
// Return Value:
// - string containing the generated RTF. Empty if the copy request is invalid.
std::string TextBuffer::GenRTF(const CopyRequest& req,
const int fontHeightPoints,
const std::wstring_view fontFaceName,
const COLORREF backgroundColor,
const bool isIntenseBold,
std::function<std::tuple<COLORREF, COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors) const noexcept
// - string containing the generated RTF
std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoints, const std::wstring_view fontFaceName, const COLORREF backgroundColor)
{
if (req.beg > req.end)
{
return {};
}
try
{
std::string rtfBuilder;
std::ostringstream rtfBuilder;
// start rtf
rtfBuilder += "{";
rtfBuilder << "{";
// Standard RTF header.
// This is similar to the header generated by WordPad.
@@ -2331,11 +2346,10 @@ std::string TextBuffer::GenRTF(const CopyRequest& req,
// Some features are blocked by default to maintain compatibility
// with older programs (Eg. Word 97-2003). `nouicompat` disables this
// behavior, and unblocks these features. See: Spec 1.9.1, Pg. 51.
rtfBuilder += "\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat";
rtfBuilder << "\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat";
// font table
// Brace escape: add an extra brace (of same kind) after a brace to escape it within the format string.
fmt::format_to(std::back_inserter(rtfBuilder), FMT_COMPILE("{{\\fonttbl{{\\f0\\fmodern\\fcharset0 {};}}}}"), til::u16u8(fontFaceName));
rtfBuilder << "{\\fonttbl{\\f0\\fmodern\\fcharset0 " << ConvertToA(CP_UTF8, fontFaceName) << ";}}";
// map to keep track of colors:
// keys are colors represented by COLORREF
@@ -2343,8 +2357,8 @@ std::string TextBuffer::GenRTF(const CopyRequest& req,
std::unordered_map<COLORREF, size_t> colorMap;
// RTF color table
std::string colorTableBuilder;
colorTableBuilder += "{\\colortbl ;";
std::ostringstream colorTableBuilder;
colorTableBuilder << "{\\colortbl ;";
const auto getColorTableIndex = [&](const COLORREF color) -> size_t {
// Exclude the 0 index for the default color, and start with 1.
@@ -2352,127 +2366,103 @@ std::string TextBuffer::GenRTF(const CopyRequest& req,
const auto [it, inserted] = colorMap.emplace(color, colorMap.size() + 1);
if (inserted)
{
const auto red = static_cast<int>(GetRValue(color));
const auto green = static_cast<int>(GetGValue(color));
const auto blue = static_cast<int>(GetBValue(color));
fmt::format_to(std::back_inserter(colorTableBuilder), FMT_COMPILE("\\red{}\\green{}\\blue{};"), red, green, blue);
colorTableBuilder << "\\red" << static_cast<int>(GetRValue(color))
<< "\\green" << static_cast<int>(GetGValue(color))
<< "\\blue" << static_cast<int>(GetBValue(color))
<< ";";
}
return it->second;
};
// content
std::string contentBuilder;
// \viewkindN: View mode of the document to be used. N=4 specifies that the document is in Normal view. (maybe unnecessary?)
// \ucN: Number of unicode fallback characters after each codepoint. (global)
contentBuilder += "\\viewkind4\\uc1";
std::ostringstream contentBuilder;
contentBuilder << "\\viewkind4\\uc4";
// paragraph styles
// \pard: paragraph description
// \slmultN: line-spacing multiple
// \fN: font to be used for the paragraph, where N is the font index in the font table
contentBuilder += "\\pard\\slmult1\\f0";
// \fs specifies font size in half-points i.e. \fs20 results in a font size
// of 10 pts. That's why, font size is multiplied by 2 here.
contentBuilder << "\\pard\\slmult1\\f0\\fs" << std::to_string(2 * fontHeightPoints)
// Set the background color for the page. But, the
// standard way (\cbN) to do this isn't supported in Word.
// However, the following control words sequence works
// in Word (and other RTF editors also) for applying the
// text background color. See: Spec 1.9.1, Pg. 23.
<< "\\chshdng0\\chcbpat" << getColorTableIndex(backgroundColor)
<< " ";
// \fsN: specifies font size in half-points. E.g. \fs20 results in a font
// size of 10 pts. That's why, font size is multiplied by 2 here.
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\fs{}"), std::to_string(2 * fontHeightPoints));
// Set the background color for the page. But the standard way (\cbN) to do
// this isn't supported in Word. However, the following control words sequence
// works in Word (and other RTF editors also) for applying the text background
// color. See: Spec 1.9.1, Pg. 23.
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\chshdng0\\chcbpat{}"), getColorTableIndex(backgroundColor));
for (auto iRow = req.beg.y; iRow <= req.end.y; ++iRow)
std::optional<COLORREF> fgColor = std::nullopt;
std::optional<COLORREF> bkColor = std::nullopt;
for (size_t row = 0; row < rows.text.size(); ++row)
{
const auto& row = GetRowByOffset(iRow);
const auto [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper(req, iRow, row);
const auto rowBegU16 = gsl::narrow_cast<uint16_t>(rowBeg);
const auto rowEndU16 = gsl::narrow_cast<uint16_t>(rowEnd);
const auto runs = row.Attributes().slice(rowBegU16, rowEndU16).runs();
size_t startOffset = 0;
auto x = rowBegU16;
for (auto& [attr, length] : runs)
if (row != 0)
{
const auto nextX = gsl::narrow_cast<uint16_t>(x + length);
const auto [fg, bg, ul] = GetAttributeColors(attr);
const auto fgIdx = getColorTableIndex(fg);
const auto bgIdx = getColorTableIndex(bg);
const auto ulIdx = getColorTableIndex(ul);
const auto ulStyle = attr.GetUnderlineStyle();
// start an RTF group that can be closed later to restore the
// default attribute.
contentBuilder += "{";
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\cf{}"), fgIdx);
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\chshdng0\\chcbpat{}"), bgIdx);
if (isIntenseBold && attr.IsIntense())
{
contentBuilder += "\\b";
}
if (attr.IsItalic())
{
contentBuilder += "\\i";
}
if (attr.IsCrossedOut())
{
contentBuilder += "\\strike";
}
switch (ulStyle)
{
case UnderlineStyle::NoUnderline:
break;
case UnderlineStyle::DoublyUnderlined:
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\uldb\\ulc{}"), ulIdx);
break;
case UnderlineStyle::CurlyUnderlined:
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\ulwave\\ulc{}"), ulIdx);
break;
case UnderlineStyle::DottedUnderlined:
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\uld\\ulc{}"), ulIdx);
break;
case UnderlineStyle::DashedUnderlined:
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\uldash\\ulc{}"), ulIdx);
break;
case UnderlineStyle::SinglyUnderlined:
default:
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\ul\\ulc{}"), ulIdx);
break;
}
// RTF commands and the text data must be separated by a space.
// Otherwise, if the text begins with a space then that space will
// be interpreted as part of the last command, and will be lost.
contentBuilder += " ";
const auto unescapedText = row.GetText(x, nextX); // including character at nextX
_AppendRTFText(contentBuilder, unescapedText);
contentBuilder += "}"; // close RTF group
// advance to next run of text
x = nextX;
contentBuilder << "\\line "; // new line
}
// never add line break to the last row.
if (addLineBreak && iRow < req.end.y)
for (size_t col = 0; col < rows.text.at(row).length(); ++col)
{
contentBuilder += "\\line";
const auto writeAccumulatedChars = [&](bool includeCurrent) {
if (col >= startOffset)
{
const auto text = std::wstring_view{ rows.text.at(row) }.substr(startOffset, col - startOffset + includeCurrent);
_AppendRTFText(contentBuilder, text);
startOffset = col;
}
};
if (rows.text.at(row).at(col) == '\r' || rows.text.at(row).at(col) == '\n')
{
// do not include \r nor \n as they don't have color attributes.
// For line break use \line instead.
writeAccumulatedChars(false);
break;
}
auto colorChanged = false;
if (!fgColor.has_value() || rows.FgAttr.at(row).at(col) != fgColor.value())
{
fgColor = rows.FgAttr.at(row).at(col);
colorChanged = true;
}
if (!bkColor.has_value() || rows.BkAttr.at(row).at(col) != bkColor.value())
{
bkColor = rows.BkAttr.at(row).at(col);
colorChanged = true;
}
if (colorChanged)
{
writeAccumulatedChars(false);
contentBuilder << "\\chshdng0\\chcbpat" << getColorTableIndex(bkColor.value())
<< "\\cf" << getColorTableIndex(fgColor.value())
<< " ";
}
// if this is the last character in the row, flush the whole row
if (col == rows.text.at(row).length() - 1)
{
writeAccumulatedChars(true);
}
}
}
// end colortbl
colorTableBuilder << "}";
// add color table to the final RTF
rtfBuilder += colorTableBuilder + "}";
rtfBuilder << colorTableBuilder.str();
// add the text content to the final RTF
rtfBuilder += contentBuilder + "}";
rtfBuilder << contentBuilder.str();
return rtfBuilder;
// end rtf
rtfBuilder << "}";
return rtfBuilder.str();
}
catch (...)
{
@@ -2481,7 +2471,7 @@ std::string TextBuffer::GenRTF(const CopyRequest& req,
}
}
void TextBuffer::_AppendRTFText(std::string& contentBuilder, const std::wstring_view& text)
void TextBuffer::_AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text)
{
for (const auto codeUnit : text)
{
@@ -2492,18 +2482,16 @@ void TextBuffer::_AppendRTFText(std::string& contentBuilder, const std::wstring_
case L'\\':
case L'{':
case L'}':
contentBuilder += "\\";
[[fallthrough]];
contentBuilder << "\\" << gsl::narrow<char>(codeUnit);
break;
default:
contentBuilder += gsl::narrow_cast<char>(codeUnit);
contentBuilder << gsl::narrow<char>(codeUnit);
}
}
else
{
// Windows uses unsigned wchar_t - RTF uses signed ones.
// '?' is the fallback ascii character.
const auto codeUnitRTFStr = std::to_string(til::bit_cast<int16_t>(codeUnit));
fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\u{}?"), codeUnitRTFStr);
contentBuilder << "\\u" << std::to_string(til::bit_cast<int16_t>(codeUnit)) << "?";
}
}
}

View File

@@ -230,94 +230,33 @@ public:
std::wstring GetCustomIdFromId(uint16_t id) const;
void CopyHyperlinkMaps(const TextBuffer& OtherBuffer);
class TextAndColor
{
public:
std::vector<std::wstring> text;
std::vector<std::vector<COLORREF>> FgAttr;
std::vector<std::vector<COLORREF>> BkAttr;
};
size_t SpanLength(const til::point coordStart, const til::point coordEnd) const;
const TextAndColor GetText(const bool includeCRLF,
const bool trimTrailingWhitespace,
const std::vector<til::inclusive_rect>& textRects,
std::function<std::pair<COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors = nullptr,
const bool formatWrappedRows = false) const;
std::wstring GetPlainText(const til::point& start, const til::point& end) const;
struct CopyRequest
{
// beg and end coordinates are inclusive
til::point beg;
til::point end;
static std::string GenHTML(const TextAndColor& rows,
const int fontHeightPoints,
const std::wstring_view fontFaceName,
const COLORREF backgroundColor);
til::CoordType minX;
til::CoordType maxX;
bool blockSelection = false;
bool trimTrailingWhitespace = true;
bool includeLineBreak = true;
bool formatWrappedRows = false;
// whether beg, end coordinates are in buffer coordinates or screen coordinates
bool bufferCoordinates = false;
CopyRequest() = default;
constexpr CopyRequest(const TextBuffer& buffer, const til::point& beg, const til::point& end, const bool blockSelection, const bool includeLineBreak, const bool trimTrailingWhitespace, const bool formatWrappedRows, const bool bufferCoordinates = false) noexcept :
beg{ std::max(beg, til::point{ 0, 0 }) },
end{ std::min(end, til::point{ buffer._width - 1, buffer._height - 1 }) },
minX{ std::min(this->beg.x, this->end.x) },
maxX{ std::max(this->beg.x, this->end.x) },
blockSelection{ blockSelection },
includeLineBreak{ includeLineBreak },
trimTrailingWhitespace{ trimTrailingWhitespace },
formatWrappedRows{ formatWrappedRows },
bufferCoordinates{ bufferCoordinates }
{
}
static CopyRequest FromConfig(const TextBuffer& buffer,
const til::point& beg,
const til::point& end,
const bool singleLine,
const bool blockSelection,
const bool trimBlockSelection,
const bool bufferCoordinates = false) noexcept
{
return {
buffer,
beg,
end,
blockSelection,
/* includeLineBreak */
// - SingleLine mode collapses all rows into one line, unless we're in
// block selection mode.
// - Block selection should preserve the visual structure by including
// line breaks on all rows (together with `formatWrappedRows`).
// (Selects like a box, pastes like a box)
!singleLine || blockSelection,
/* trimTrailingWhitespace */
// Trim trailing whitespace if we're not in single line mode and — either
// we're not in block selection mode or, we're in block selection mode and
// trimming is allowed.
!singleLine && (!blockSelection || trimBlockSelection),
/* formatWrappedRows */
// In block selection, we should apply formatting to wrapped rows as well.
// (Otherwise, they're only applied to non-wrapped rows.)
blockSelection,
bufferCoordinates
};
}
};
std::wstring GetPlainText(const CopyRequest& req) const;
std::string GenHTML(const CopyRequest& req,
const int fontHeightPoints,
const std::wstring_view fontFaceName,
const COLORREF backgroundColor,
const bool isIntenseBold,
std::function<std::tuple<COLORREF, COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors) const noexcept;
std::string GenRTF(const CopyRequest& req,
const int fontHeightPoints,
const std::wstring_view fontFaceName,
const COLORREF backgroundColor,
const bool isIntenseBold,
std::function<std::tuple<COLORREF, COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors) const noexcept;
static std::string GenRTF(const TextAndColor& rows,
const int fontHeightPoints,
const std::wstring_view fontFaceName,
const COLORREF backgroundColor);
struct PositionInformation
{
@@ -365,9 +304,8 @@ private:
til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
void _PruneHyperlinks();
void _trimMarksOutsideBuffer();
std::tuple<til::CoordType, til::CoordType, bool> _RowCopyHelper(const CopyRequest& req, const til::CoordType iRow, const ROW& row) const;
static void _AppendRTFText(std::string& contentBuilder, const std::wstring_view& text);
static void _AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text);
Microsoft::Console::Render::Renderer& _renderer;

View File

@@ -38,9 +38,7 @@
</Properties>
<Dependencies>
<!-- rescap:appLicensing only works on 22000+. Until that's fixed, MinVersion will not let people install it on Windows 10... -->
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.22000.0" MaxVersionTested="10.0.22621.0" />
<TargetDeviceFamily Name="Windows.DesktopServer" MinVersion="10.0.22000.0" MaxVersionTested="10.0.22621.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.22621.0" />
</Dependencies>
<Resources>
@@ -94,7 +92,7 @@
Description="Console host built from microsoft/terminal open source repository"
PublicFolder="Public">
<uap3:Properties>
<Clsid>{A854D02A-F2FE-44A5-BB24-D03F4CF830D4}</Clsid>
<Clsid>{1F9F2BF5-5BC3-4F17-B0E6-912413F1F451}</Clsid>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
@@ -105,7 +103,7 @@
Description="Terminal host built from microsoft/terminal open source repository"
PublicFolder="Public">
<uap3:Properties>
<Clsid>{1706609C-A4CE-4C0D-B7D2-C19BF66398A5}</Clsid>
<Clsid>{051F34EE-C1FD-4B19-AF75-9BA54648434C}</Clsid>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>

View File

@@ -166,7 +166,7 @@
<comment>{Locked=qps-ploc,qps-ploca,qps-plocm}</comment>
</data>
<data name="AppDescription" xml:space="preserve">
<value>Τнĕ Ņëω Ẅίηđŏẃś Ťėŗmįйάĺ !!! !!! !</value>
<value>Τĥз Йéщ Ẃįńđôẃѕ Тéѓmĩиâļ !!! !!! !</value>
</data>
<data name="AppDescriptionDev" xml:space="preserve">
<value>The Windows Terminal, but Unofficial</value>
@@ -177,22 +177,22 @@
<comment>{Locked}</comment>
</data>
<data name="AppDescriptionPre" xml:space="preserve">
<value>Ŵíňďōẁŝ Тєřмīπǻļ ωїτĥ å ρѓēνіéŵ θƒ ũφсőмϊπġ ƒєąτΰґёѕ !!! !!! !!! !!! !!! </value>
<value>Щΐňδόŵѕ Ŧęřмĭʼnäℓ ẅîťħ à φřеνίëẃ θƒ џрсøмΐʼnğ ƒĕāŧųřэś !!! !!! !!! !!! !!! </value>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Dev" xml:space="preserve">
<value>Open in Terminal (&amp;Dev)</value>
<comment>{Locked} The dev build will never be seen in multiple languages</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Canary" xml:space="preserve">
<value>Ŏрέи ìη Тèřmīŋªŀ (&amp;Cãńãґγ) !!! !!! !</value>
<value>Θρēņ ïη Ţéгmĭηäŀ (&amp;Çäņдѓγ) !!! !!! !</value>
<comment>This is a menu item that will be displayed in the Windows File Explorer that launches the Canary version of Windows Terminal. Please mark one of the characters to be an accelerator key.</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Preview" xml:space="preserve">
<value>Φφєň ΐñ Ŧéгмϊñаľ &amp;Pŕėνĭώ !!! !!! !</value>
<value>Όрèп ìņ Ţêŕmїʼnåļ &amp;Рѓзνι℮ω !!! !!! !</value>
<comment>This is a menu item that will be displayed in the Windows File Explorer that launches the Preview version of Windows Terminal. Please mark one of the characters to be an accelerator key.</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem" xml:space="preserve">
<value>Ωρєⁿ ïπ &amp;Těѓmĭñäĺ !!! !!</value>
<value>Óрêп ìл &amp;Ťěѓmιиåĺ !!! !!</value>
<comment>This is a menu item that will be displayed in the Windows File Explorer that launches the non-preview version of Windows Terminal. Please mark one of the characters to be an accelerator key.</comment>
</data>
</root>

View File

@@ -166,7 +166,7 @@
<comment>{Locked=qps-ploc,qps-ploca,qps-plocm}</comment>
</data>
<data name="AppDescription" xml:space="preserve">
<value>Ťĥё Ñēщ Шίⁿðоẅś Ťėгмîήāľ !!! !!! !</value>
<value>Τĥз Йéщ Ẃįńđôẃѕ Тéѓmĩиâļ !!! !!! !</value>
</data>
<data name="AppDescriptionDev" xml:space="preserve">
<value>The Windows Terminal, but Unofficial</value>
@@ -177,22 +177,22 @@
<comment>{Locked}</comment>
</data>
<data name="AppDescriptionPre" xml:space="preserve">
<value>Шίηđóŵš Ŧĕяmїйà ẁітħ ª φяęνîёщ όƒ ûφĉбмíήĝ ƒêåťµřεŝ !!! !!! !!! !!! !!! </value>
<value>Щΐňδόŵѕ Ŧęřмĭʼnäℓ ẅîťħ à φřеνίëẃ θƒ џрсøмΐʼnğ ƒĕāŧųřэś !!! !!! !!! !!! !!! </value>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Dev" xml:space="preserve">
<value>Open in Terminal (&amp;Dev)</value>
<comment>{Locked} The dev build will never be seen in multiple languages</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Canary" xml:space="preserve">
<value>Óрëπ íи Ťēяmілǻŀ (&amp;Cäηàřÿ) !!! !!! !</value>
<value>Θρēņ ïη Ţéгmĭηäŀ (&amp;Çäņдѓγ) !!! !!! !</value>
<comment>This is a menu item that will be displayed in the Windows File Explorer that launches the Canary version of Windows Terminal. Please mark one of the characters to be an accelerator key.</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Preview" xml:space="preserve">
<value>Óрзń ΐή Ŧěґмιлāℓ &amp;Pѓéνīĕω !!! !!! !</value>
<value>Όрèп ìņ Ţêŕmїʼnåļ &amp;Рѓзνι℮ω !!! !!! !</value>
<comment>This is a menu item that will be displayed in the Windows File Explorer that launches the Preview version of Windows Terminal. Please mark one of the characters to be an accelerator key.</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem" xml:space="preserve">
<value>Ôрëη ïп &amp;Tēѓмϊŋãł !!! !!</value>
<value>Óрêп ìл &amp;Ťěѓmιиåĺ !!! !!</value>
<comment>This is a menu item that will be displayed in the Windows File Explorer that launches the non-preview version of Windows Terminal. Please mark one of the characters to be an accelerator key.</comment>
</data>
</root>

View File

@@ -166,7 +166,7 @@
<comment>{Locked=qps-ploc,qps-ploca,qps-plocm}</comment>
</data>
<data name="AppDescription" xml:space="preserve">
<value>Тĥё Ńēẁ Шіπđοωš Тěřмιňαŀ !!! !!! !</value>
<value>Τĥз Йéщ Ẃįńđôẃѕ Тéѓmĩиâļ !!! !!! !</value>
</data>
<data name="AppDescriptionDev" xml:space="preserve">
<value>The Windows Terminal, but Unofficial</value>
@@ -177,22 +177,22 @@
<comment>{Locked}</comment>
</data>
<data name="AppDescriptionPre" xml:space="preserve">
<value>Шίήďŏшś Ŧêямĩňāľ ŵíτн ă ρѓëνιêω øƒ ũρčŏмįηğ ƒєáţũŗêš !!! !!! !!! !!! !!! </value>
<value>Щΐňδόŵѕ Ŧęřмĭʼnäℓ ẅîťħ à φřеνίëẃ θƒ џрсøмΐʼnğ ƒĕāŧųřэś !!! !!! !!! !!! !!! </value>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Dev" xml:space="preserve">
<value>Open in Terminal (&amp;Dev)</value>
<comment>{Locked} The dev build will never be seen in multiple languages</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Canary" xml:space="preserve">
<value>Фφěй ĩń Тêřmιπâł (&amp;Cãⁿǻřу) !!! !!! !</value>
<value>Θρēņ ïη Ţéгmĭηäŀ (&amp;Çäņдѓγ) !!! !!! !</value>
<comment>This is a menu item that will be displayed in the Windows File Explorer that launches the Canary version of Windows Terminal. Please mark one of the characters to be an accelerator key.</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Preview" xml:space="preserve">
<value>Óφ℮ʼn ΐŋ Τėřmīйäĺ &amp;Pяєνϊëẃ !!! !!! !</value>
<value>Όрèп ìņ Ţêŕmїʼnåļ &amp;Рѓзνι℮ω !!! !!! !</value>
<comment>This is a menu item that will be displayed in the Windows File Explorer that launches the Preview version of Windows Terminal. Please mark one of the characters to be an accelerator key.</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem" xml:space="preserve">
<value>Ορέл ϊŋ &amp;Téŕmįñā !!! !!</value>
<value>Óрêп ìл &amp;Ťěѓmιиåĺ !!! !!</value>
<comment>This is a menu item that will be displayed in the Windows File Explorer that launches the non-preview version of Windows Terminal. Please mark one of the characters to be an accelerator key.</comment>
</data>
</root>

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{EFC0B7EF-BB0D-44EC-BFC9-772AE4391D17}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<ProjectName>Microsoft.Terminal.Connection.Interfaces</ProjectName>
<TargetName>Microsoft.Terminal.Connection.Interfaces</TargetName>
<ConfigurationType>Utility</ConfigurationType>
<RootNamespace>Microsoft.Terminal.TerminalConnection</RootNamespace>
<TerminalMidlRT>true</TerminalMidlRT>
<!-- Force our output directory -->
<OpenConsoleCppWinRTProject>true</OpenConsoleCppWinRTProject>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
<Import Project="$(OpenConsoleDir)src\common.build.pre.props" />
<ItemGroup>
<Midl Include="ITerminalConnection.idl" />
</ItemGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
</Project>

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{40503EDC-E3E4-46AB-BC26-D293B956CAE8}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<ProjectName>Microsoft.Terminal.Core.Interfaces</ProjectName>
<TargetName>Microsoft.Terminal.Core.Interfaces</TargetName>
<ConfigurationType>Utility</ConfigurationType>
<RootNamespace>Microsoft.Terminal.Core</RootNamespace>
<TerminalMidlRT>true</TerminalMidlRT>
<!-- Force our output directory -->
<OpenConsoleCppWinRTProject>true</OpenConsoleCppWinRTProject>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
<Import Project="$(OpenConsoleDir)src\common.build.pre.props" />
<ItemGroup>
<Midl Include="ICoreSettings.idl" />
<Midl Include="ICoreAppearance.idl" />
</ItemGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
</Project>

View File

@@ -0,0 +1,404 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalSettingsModel/ColorScheme.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "../types/inc/colorTable.hpp"
#include "JsonTestClass.h"
using namespace Microsoft::Console;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class ColorSchemeTests : public JsonTestClass
{
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(ColorSchemeTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(ParseSimpleColorScheme);
TEST_METHOD(LayerColorSchemesOnArray);
TEST_METHOD(UpdateSchemeReferences);
static Core::Color rgb(uint8_t r, uint8_t g, uint8_t b) noexcept
{
return Core::Color{ r, g, b, 255 };
}
};
void ColorSchemeTests::ParseSimpleColorScheme()
{
const std::string campbellScheme{ "{"
"\"background\" : \"#0C0C0C\","
"\"black\" : \"#0C0C0C\","
"\"blue\" : \"#0037DA\","
"\"brightBlack\" : \"#767676\","
"\"brightBlue\" : \"#3B78FF\","
"\"brightCyan\" : \"#61D6D6\","
"\"brightGreen\" : \"#16C60C\","
"\"brightPurple\" : \"#B4009E\","
"\"brightRed\" : \"#E74856\","
"\"brightWhite\" : \"#F2F2F2\","
"\"brightYellow\" : \"#F9F1A5\","
"\"cursorColor\" : \"#FFFFFF\","
"\"cyan\" : \"#3A96DD\","
"\"foreground\" : \"#F2F2F2\","
"\"green\" : \"#13A10E\","
"\"name\" : \"Campbell\","
"\"purple\" : \"#881798\","
"\"red\" : \"#C50F1F\","
"\"selectionBackground\" : \"#131313\","
"\"white\" : \"#CCCCCC\","
"\"yellow\" : \"#C19C00\""
"}" };
const auto schemeObject = VerifyParseSucceeded(campbellScheme);
auto scheme = ColorScheme::FromJson(schemeObject);
VERIFY_ARE_EQUAL(L"Campbell", scheme->Name());
VERIFY_ARE_EQUAL(til::color(0xf2, 0xf2, 0xf2, 255), til::color{ scheme->Foreground() });
VERIFY_ARE_EQUAL(til::color(0x0c, 0x0c, 0x0c, 255), til::color{ scheme->Background() });
VERIFY_ARE_EQUAL(til::color(0x13, 0x13, 0x13, 255), til::color{ scheme->SelectionBackground() });
VERIFY_ARE_EQUAL(til::color(0xFF, 0xFF, 0xFF, 255), til::color{ scheme->CursorColor() });
std::array<COLORREF, COLOR_TABLE_SIZE> expectedCampbellTable;
const auto campbellSpan = std::span{ expectedCampbellTable };
Utils::InitializeColorTable(campbellSpan);
for (size_t i = 0; i < expectedCampbellTable.size(); i++)
{
const til::color expected{ expectedCampbellTable.at(i) };
const til::color actual{ scheme->Table().at(static_cast<uint32_t>(i)) };
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"Roundtrip Test for Color Scheme");
auto outJson{ scheme->ToJson() };
VERIFY_ARE_EQUAL(schemeObject, outJson);
}
void ColorSchemeTests::LayerColorSchemesOnArray()
{
static constexpr std::string_view inboxSettings{ R"({
"schemes": [
{
"background": "#0C0C0C",
"black": "#0C0C0C",
"blue": "#0037DA",
"brightBlack": "#767676",
"brightBlue": "#3B78FF",
"brightCyan": "#61D6D6",
"brightGreen": "#16C60C",
"brightPurple": "#B4009E",
"brightRed": "#E74856",
"brightWhite": "#F2F2F2",
"brightYellow": "#F9F1A5",
"cursorColor": "#FFFFFF",
"cyan": "#3A96DD",
"foreground": "#CCCCCC",
"green": "#13A10E",
"name": "Campbell",
"purple": "#881798",
"red": "#C50F1F",
"selectionBackground": "#FFFFFF",
"white": "#CCCCCC",
"yellow": "#C19C00"
}
]
})" };
static constexpr std::string_view userSettings{ R"({
"profiles": [
{
"name" : "profile0"
}
],
"schemes": [
{
"background": "#121314",
"black": "#121314",
"blue": "#121314",
"brightBlack": "#121314",
"brightBlue": "#121314",
"brightCyan": "#121314",
"brightGreen": "#121314",
"brightPurple": "#121314",
"brightRed": "#121314",
"brightWhite": "#121314",
"brightYellow": "#121314",
"cursorColor": "#121314",
"cyan": "#121314",
"foreground": "#121314",
"green": "#121314",
"name": "Campbell",
"purple": "#121314",
"red": "#121314",
"selectionBackground": "#121314",
"white": "#121314",
"yellow": "#121314"
},
{
"background": "#012456",
"black": "#0C0C0C",
"blue": "#0037DA",
"brightBlack": "#767676",
"brightBlue": "#3B78FF",
"brightCyan": "#61D6D6",
"brightGreen": "#16C60C",
"brightPurple": "#B4009E",
"brightRed": "#E74856",
"brightWhite": "#F2F2F2",
"brightYellow": "#F9F1A5",
"cursorColor": "#FFFFFF",
"cyan": "#3A96DD",
"foreground": "#CCCCCC",
"green": "#13A10E",
"name": "Campbell Powershell",
"purple": "#881798",
"red": "#C50F1F",
"selectionBackground": "#FFFFFF",
"white": "#CCCCCC",
"yellow": "#C19C00"
}
]
})" };
const auto settings = winrt::make_self<CascadiaSettings>(userSettings, inboxSettings);
const auto colorSchemes = settings->GlobalSettings().ColorSchemes();
VERIFY_ARE_EQUAL(2u, colorSchemes.Size());
const auto scheme0 = winrt::get_self<ColorScheme>(colorSchemes.Lookup(L"Campbell"));
VERIFY_ARE_EQUAL(rgb(0x12, 0x13, 0x14), scheme0->Foreground());
VERIFY_ARE_EQUAL(rgb(0x12, 0x13, 0x14), scheme0->Background());
const auto scheme1 = winrt::get_self<ColorScheme>(colorSchemes.Lookup(L"Campbell Powershell"));
VERIFY_ARE_EQUAL(rgb(0xCC, 0xCC, 0xCC), scheme1->Foreground());
VERIFY_ARE_EQUAL(rgb(0x01, 0x24, 0x56), scheme1->Background());
}
void ColorSchemeTests::UpdateSchemeReferences()
{
static constexpr std::string_view settingsString{ R"json({
"defaultProfile": "Inherited reference",
"profiles": {
"defaults": {
"colorScheme": "Campbell"
},
"list": [
{
"name": "Explicit scheme reference",
"colorScheme": "Campbell"
},
{
"name": "Explicit reference; hidden",
"colorScheme": "Campbell",
"hidden": true
},
{
"name": "Inherited reference"
},
{
"name": "Different reference",
"colorScheme": "One Half Dark"
},
{
"name": "rename neither",
"colorScheme":
{
"dark": "One Half Dark",
"light": "One Half Light"
}
},
{
"name": "rename only light",
"colorScheme":
{
"dark": "One Half Dark",
"light": "Campbell"
}
},
{
"name": "rename only dark",
"colorScheme":
{
"dark": "Campbell",
"light": "One Half Light"
}
}
]
},
"schemes": [
{
"background": "#0C0C0C",
"black": "#0C0C0C",
"blue": "#0037DA",
"brightBlack": "#767676",
"brightBlue": "#3B78FF",
"brightCyan": "#61D6D6",
"brightGreen": "#16C60C",
"brightPurple": "#B4009E",
"brightRed": "#E74856",
"brightWhite": "#F2F2F2",
"brightYellow": "#F9F1A5",
"cursorColor": "#FFFFFF",
"cyan": "#3A96DD",
"foreground": "#CCCCCC",
"green": "#13A10E",
"name": "Campbell",
"purple": "#881798",
"red": "#C50F1F",
"selectionBackground": "#FFFFFF",
"white": "#CCCCCC",
"yellow": "#C19C00"
},
{
"background": "#0C0C0C",
"black": "#0C0C0C",
"blue": "#0037DA",
"brightBlack": "#767676",
"brightBlue": "#3B78FF",
"brightCyan": "#61D6D6",
"brightGreen": "#16C60C",
"brightPurple": "#B4009E",
"brightRed": "#E74856",
"brightWhite": "#F2F2F2",
"brightYellow": "#F9F1A5",
"cursorColor": "#FFFFFF",
"cyan": "#3A96DD",
"foreground": "#CCCCCC",
"green": "#13A10E",
"name": "Campbell (renamed)",
"purple": "#881798",
"red": "#C50F1F",
"selectionBackground": "#FFFFFF",
"white": "#CCCCCC",
"yellow": "#C19C00"
},
{
"background": "#282C34",
"black": "#282C34",
"blue": "#61AFEF",
"brightBlack": "#5A6374",
"brightBlue": "#61AFEF",
"brightCyan": "#56B6C2",
"brightGreen": "#98C379",
"brightPurple": "#C678DD",
"brightRed": "#E06C75",
"brightWhite": "#DCDFE4",
"brightYellow": "#E5C07B",
"cursorColor": "#FFFFFF",
"cyan": "#56B6C2",
"foreground": "#DCDFE4",
"green": "#98C379",
"name": "One Half Dark",
"purple": "#C678DD",
"red": "#E06C75",
"selectionBackground": "#FFFFFF",
"white": "#DCDFE4",
"yellow": "#E5C07B"
},
{
"name": "One Half Light",
"foreground": "#383A42",
"background": "#FAFAFA",
"cursorColor": "#4F525D",
"black": "#383A42",
"red": "#E45649",
"green": "#50A14F",
"yellow": "#C18301",
"blue": "#0184BC",
"purple": "#A626A4",
"cyan": "#0997B3",
"white": "#FAFAFA",
"brightBlack": "#4F525D",
"brightRed": "#DF6C75",
"brightGreen": "#98C379",
"brightYellow": "#E4C07A",
"brightBlue": "#61AFEF",
"brightPurple": "#C577DD",
"brightCyan": "#56B5C1",
"brightWhite": "#FFFFFF"
}
]
})json" };
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString) };
const auto newName{ L"Campbell (renamed)" };
settings->UpdateColorSchemeReferences(L"Campbell", newName);
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasLightColorSchemeName());
const auto& profiles{ settings->AllProfiles() };
{
const auto& prof{ profiles.GetAt(0) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(1) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(2) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_FALSE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_FALSE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(3) };
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(4) };
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(L"One Half Light", prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(5) };
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(6) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(L"One Half Light", prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
}
}

View File

@@ -15,11 +15,24 @@ using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace SettingsModelUnitTests
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class CommandTests : public JsonTestClass
{
TEST_CLASS(CommandTests);
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(CommandTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(ManyCommandsSameAction);
TEST_METHOD(LayerCommand);

View File

@@ -19,11 +19,24 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Control;
using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers;
namespace SettingsModelUnitTests
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class DeserializationTests : public JsonTestClass
{
TEST_CLASS(DeserializationTests);
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(DeserializationTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(ValidateProfilesExist);
TEST_METHOD(ValidateDefaultProfileExists);
@@ -61,13 +74,6 @@ namespace SettingsModelUnitTests
TEST_METHOD(TestInheritedCommand);
TEST_METHOD(LoadFragmentsWithMultipleUpdates);
TEST_METHOD(FragmentActionSimple);
TEST_METHOD(FragmentActionNoKeys);
TEST_METHOD(FragmentActionNested);
TEST_METHOD(FragmentActionNestedNoName);
TEST_METHOD(FragmentActionIterable);
TEST_METHOD(FragmentActionRoundtrip);
TEST_METHOD(MigrateReloadEnvVars);
private:
@@ -2017,189 +2023,6 @@ namespace SettingsModelUnitTests
VERIFY_ARE_EQUAL(L"NewName", loader.userSettings.profiles[0]->Name());
}
void DeserializationTests::FragmentActionSimple()
{
static constexpr std::wstring_view fragmentSource{ L"fragment" };
static constexpr std::string_view fragmentJson{ R"({
"actions": [
{
"command": { "action": "addMark" },
"name": "Test Action"
},
]
})" };
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson };
loader.MergeInboxIntoUserSettings();
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
loader.FinalizeLayering();
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto actionMap = winrt::get_self<implementation::ActionMap>(settings->GlobalSettings().ActionMap());
const auto actionsByName = actionMap->NameMap();
VERIFY_IS_NOT_NULL(actionsByName.TryLookup(L"Test Action"));
}
void DeserializationTests::FragmentActionNoKeys()
{
static constexpr std::wstring_view fragmentSource{ L"fragment" };
static constexpr std::string_view fragmentJson{ R"({
"actions": [
{
"command": { "action": "addMark" },
"keys": "ctrl+f",
"name": "Test Action"
},
]
})" };
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson };
loader.MergeInboxIntoUserSettings();
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
loader.FinalizeLayering();
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto actionMap = winrt::get_self<implementation::ActionMap>(settings->GlobalSettings().ActionMap());
const auto actionsByName = actionMap->NameMap();
VERIFY_IS_NOT_NULL(actionsByName.TryLookup(L"Test Action"));
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('F'), 0 }));
}
void DeserializationTests::FragmentActionNested()
{
static constexpr std::wstring_view fragmentSource{ L"fragment" };
static constexpr std::string_view fragmentJson{ R"({
"actions": [
{
"name": "nested command",
"commands": [
{
"name": "child1",
"command": { "action": "newTab", "commandline": "ssh me@first.com" }
},
{
"name": "child2",
"command": { "action": "newTab", "commandline": "ssh me@second.com" }
}
]
},
]
})" };
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson };
loader.MergeInboxIntoUserSettings();
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
loader.FinalizeLayering();
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto actionMap = winrt::get_self<implementation::ActionMap>(settings->GlobalSettings().ActionMap());
const auto actionsByName = actionMap->NameMap();
const auto& nested{ actionsByName.TryLookup(L"nested command") };
VERIFY_IS_NOT_NULL(nested);
VERIFY_IS_TRUE(nested.HasNestedCommands());
}
void DeserializationTests::FragmentActionNestedNoName()
{
// Basically the same as TestNestedCommandWithoutName
static constexpr std::wstring_view fragmentSource{ L"fragment" };
static constexpr std::string_view fragmentJson{ R"({
"actions": [
{
"commands": [
{
"name": "child1",
"command": { "action": "newTab", "commandline": "ssh me@first.com" }
},
{
"name": "child2",
"command": { "action": "newTab", "commandline": "ssh me@second.com" }
}
]
},
]
})" };
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson };
loader.MergeInboxIntoUserSettings();
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
loader.FinalizeLayering();
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
}
void DeserializationTests::FragmentActionIterable()
{
static constexpr std::wstring_view fragmentSource{ L"fragment" };
static constexpr std::string_view fragmentJson{ R"({
"actions": [
{
"name": "nested",
"commands": [
{
"iterateOn": "schemes",
"name": "${scheme.name}",
"command": { "action": "setColorScheme", "colorScheme": "${scheme.name}" }
}
]
},
]
})" };
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson };
loader.MergeInboxIntoUserSettings();
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
loader.FinalizeLayering();
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto actionMap = winrt::get_self<implementation::ActionMap>(settings->GlobalSettings().ActionMap());
const auto actionsByName = actionMap->NameMap();
const auto& nested{ actionsByName.TryLookup(L"nested") };
VERIFY_IS_NOT_NULL(nested);
VERIFY_IS_TRUE(nested.HasNestedCommands());
VERIFY_ARE_EQUAL(settings->GlobalSettings().ColorSchemes().Size(), nested.NestedCommands().Size());
}
void DeserializationTests::FragmentActionRoundtrip()
{
static constexpr std::wstring_view fragmentSource{ L"fragment" };
static constexpr std::string_view fragmentJson{ R"({
"actions": [
{
"command": { "action": "addMark" },
"name": "Test Action"
},
]
})" };
implementation::SettingsLoader loader{ std::string_view{}, DefaultJson };
loader.MergeInboxIntoUserSettings();
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
loader.FinalizeLayering();
const auto oldSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
const auto actionMap = winrt::get_self<implementation::ActionMap>(oldSettings->GlobalSettings().ActionMap());
const auto actionsByName = actionMap->NameMap();
VERIFY_IS_NOT_NULL(actionsByName.TryLookup(L"Test Action"));
const auto oldResult{ oldSettings->ToJson() };
Log::Comment(L"Now, create a _new_ settings object from the re-serialization of the first");
implementation::SettingsLoader newLoader{ toString(oldResult), DefaultJson };
// NOTABLY! Don't load the fragment here.
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto& newActionMap = winrt::get_self<implementation::ActionMap>(newSettings->GlobalSettings().ActionMap());
const auto newActionsByName = newActionMap->NameMap();
VERIFY_IS_NULL(newActionsByName.TryLookup(L"Test Action"));
}
void DeserializationTests::MigrateReloadEnvVars()
{
static constexpr std::string_view settings1Json{ R"(

View File

@@ -17,11 +17,24 @@ using namespace WEX::TestExecution;
using namespace WEX::Common;
using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers;
namespace SettingsModelUnitTests
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class KeyBindingsTests : public JsonTestClass
{
TEST_CLASS(KeyBindingsTests);
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(KeyBindingsTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(KeyChords);
TEST_METHOD(ManyKeysSameAction);

View File

@@ -18,11 +18,24 @@ using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace SettingsModelUnitTests
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class NewTabMenuTests : public JsonTestClass
{
TEST_CLASS(NewTabMenuTests);
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(NewTabMenuTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(DefaultsToRemainingProfiles);
TEST_METHOD(ParseEmptyFolder);

View File

@@ -15,11 +15,24 @@ using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace SettingsModelUnitTests
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class ProfileTests : public JsonTestClass
{
TEST_CLASS(ProfileTests);
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(ProfileTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(ProfileGeneratesGuid);
TEST_METHOD(LayerProfileProperties);

View File

@@ -16,11 +16,24 @@ using namespace WEX::Common;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Control;
namespace SettingsModelUnitTests
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class SerializationTests : public JsonTestClass
{
TEST_CLASS(SerializationTests);
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(SerializationTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(GlobalSettings);
TEST_METHOD(Profile);
@@ -32,10 +45,6 @@ namespace SettingsModelUnitTests
TEST_METHOD(RoundtripReloadEnvVars);
TEST_METHOD(DontRoundtripNoReloadEnvVars);
TEST_METHOD(RoundtripUserModifiedColorSchemeCollision);
TEST_METHOD(RoundtripUserModifiedColorSchemeCollisionUnusedByProfiles);
TEST_METHOD(RoundtripUserDeletedColorSchemeCollision);
private:
// Method Description:
// - deserializes and reserializes a json string representing a settings object model of type T
@@ -59,19 +68,6 @@ namespace SettingsModelUnitTests
// written alphabetically.
VERIFY_ARE_EQUAL(toString(json), toString(result));
}
// Helper to remove the `$schema` property from a json object. We
// populate that based off the local path to the settings file. Of
// course, that's entirely unpredictable in tests. So cut it out before
// we do any sort of roundtrip testing.
static Json::Value removeSchema(Json::Value json)
{
if (json.isMember("$schema"))
{
json.removeMember("$schema");
}
return json;
}
};
void SerializationTests::GlobalSettings()
@@ -199,34 +195,9 @@ namespace SettingsModelUnitTests
"source": "local"
})" };
static constexpr std::string_view profileWithIcon{ R"(
{
"guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}",
"name": "profileWithIcon",
"hidden": false,
"icon": "foo.png"
})" };
static constexpr std::string_view profileWithNullIcon{ R"(
{
"guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}",
"name": "profileWithNullIcon",
"hidden": false,
"icon": null
})" };
static constexpr std::string_view profileWithNoIcon{ R"(
{
"guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}",
"name": "profileWithNoIcon",
"hidden": false,
"icon": "none"
})" };
RoundtripTest<implementation::Profile>(profileString);
RoundtripTest<implementation::Profile>(smallProfileString);
RoundtripTest<implementation::Profile>(weirdProfileString);
RoundtripTest<implementation::Profile>(profileWithIcon);
RoundtripTest<implementation::Profile>(profileWithNullIcon);
RoundtripTest<implementation::Profile>(profileWithNoIcon);
}
void SerializationTests::ColorScheme()
@@ -291,6 +262,15 @@ namespace SettingsModelUnitTests
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+c" },
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" }
])" };
// GH#13323 - these can be fragile. In the past, the order these get
// re-serialized as has been not entirely stable. We don't really care
// about the order they get re-serialized in, but the tests aren't
// clever enough to compare the structure, only the literal string
// itself. Feel free to change as needed.
static constexpr std::string_view actionsString4B{ R"([
{ "command": { "action": "findMatch", "direction": "prev" }, "keys": "ctrl+shift+r" },
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" }
])" };
// command with name and icon and multiple key chords
static constexpr std::string_view actionsString5{ R"([
@@ -404,6 +384,7 @@ namespace SettingsModelUnitTests
Log::Comment(L"complex commands with key chords");
RoundtripTest<implementation::ActionMap>(actionsString4A);
RoundtripTest<implementation::ActionMap>(actionsString4B);
Log::Comment(L"command with name and icon and multiple key chords");
RoundtripTest<implementation::ActionMap>(actionsString5);
@@ -502,8 +483,7 @@ namespace SettingsModelUnitTests
const auto settings{ winrt::make_self<implementation::CascadiaSettings>(settingsString) };
const auto result{ settings->ToJson() };
VERIFY_ARE_EQUAL(toString(removeSchema(VerifyParseSucceeded(settingsString))),
toString(removeSchema(result)));
VERIFY_ARE_EQUAL(toString(VerifyParseSucceeded(settingsString)), toString(result));
}
void SerializationTests::LegacyFontSettings()
@@ -646,306 +626,4 @@ namespace SettingsModelUnitTests
VERIFY_IS_FALSE(newSettings->ProfileDefaults().HasReloadEnvironmentVariables(),
L"Ensure that the new settings object didn't find a reloadEnvironmentVariables");
}
void SerializationTests::RoundtripUserModifiedColorSchemeCollision()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
},
{
"name": "profile1",
"colorScheme": "Tango Dark",
"guid": "{d0a65a9d-8665-4128-97a4-a581aa747aa7}"
}
],
"schemes": [
{
"background": "#121314",
"black": "#121314",
"blue": "#121314",
"brightBlack": "#121314",
"brightBlue": "#121314",
"brightCyan": "#121314",
"brightGreen": "#121314",
"brightPurple": "#121314",
"brightRed": "#121314",
"brightWhite": "#121314",
"brightYellow": "#121314",
"cursorColor": "#121314",
"cyan": "#121314",
"foreground": "#121314",
"green": "#121314",
"name": "Campbell",
"purple": "#121314",
"red": "#121314",
"selectionBackground": "#121314",
"white": "#121314",
"yellow": "#121314"
},
{
"background": "#000000",
"black": "#000000",
"blue": "#3465A4",
"brightBlack": "#555753",
"brightBlue": "#729FCF",
"brightCyan": "#34E2E2",
"brightGreen": "#8AE234",
"brightPurple": "#AD7FA8",
"brightRed": "#EF2929",
"brightWhite": "#EEEEEC",
"brightYellow": "#FCE94F",
"cursorColor": "#FFFFFF",
"cyan": "#06989A",
"foreground": "#D3D7CF",
"green": "#4E9A06",
"name": "Tango Dark",
"purple": "#75507B",
"red": "#CC0000",
"selectionBackground": "#FFFFFF",
"white": "#D3D7CF",
"yellow": "#C4A000"
},
]
})" };
// Key differences: one fewer color scheme (Tango Dark has been deleted) and defaults.colorScheme is set.
static constexpr std::string_view newSettingsJson{ R"-(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles":
{
"defaults": {
"colorScheme": "Campbell (modified)"
},
"list":
[
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
},
{
"name": "profile1",
"colorScheme": "Tango Dark",
"guid": "{d0a65a9d-8665-4128-97a4-a581aa747aa7}"
}
]
},
"actions": [ ],
"schemes": [
{
"background": "#121314",
"black": "#121314",
"blue": "#121314",
"brightBlack": "#121314",
"brightBlue": "#121314",
"brightCyan": "#121314",
"brightGreen": "#121314",
"brightPurple": "#121314",
"brightRed": "#121314",
"brightWhite": "#121314",
"brightYellow": "#121314",
"cursorColor": "#121314",
"cyan": "#121314",
"foreground": "#121314",
"green": "#121314",
"name": "Campbell",
"purple": "#121314",
"red": "#121314",
"selectionBackground": "#121314",
"white": "#121314",
"yellow": "#121314"
}
]
})-" };
implementation::SettingsLoader oldLoader{ oldSettingsJson, DefaultJson };
oldLoader.MergeInboxIntoUserSettings();
oldLoader.FinalizeLayering();
VERIFY_IS_TRUE(oldLoader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto oldSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(oldLoader));
const auto oldResult{ oldSettings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, DefaultJson };
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
newLoader.FixupUserSettings();
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
void SerializationTests::RoundtripUserModifiedColorSchemeCollisionUnusedByProfiles()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
}
],
"schemes": [
{
"background": "#111111",
"black": "#111111",
"blue": "#111111",
"brightBlack": "#111111",
"brightBlue": "#111111",
"brightCyan": "#111111",
"brightGreen": "#111111",
"brightPurple": "#111111",
"brightRed": "#111111",
"brightWhite": "#111111",
"brightYellow": "#111111",
"cursorColor": "#111111",
"cyan": "#111111",
"foreground": "#111111",
"green": "#111111",
"name": "Tango Dark",
"purple": "#111111",
"red": "#111111",
"selectionBackground": "#111111",
"white": "#111111",
"yellow": "#111111"
},
]
})" };
// Key differences: Tango Dark has been renamed; nothing else has changed
static constexpr std::string_view newSettingsJson{ R"-(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles":
{
"list":
[
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
}
]
},
"actions": [ ],
"schemes": [
{
"background": "#111111",
"black": "#111111",
"blue": "#111111",
"brightBlack": "#111111",
"brightBlue": "#111111",
"brightCyan": "#111111",
"brightGreen": "#111111",
"brightPurple": "#111111",
"brightRed": "#111111",
"brightWhite": "#111111",
"brightYellow": "#111111",
"cursorColor": "#111111",
"cyan": "#111111",
"foreground": "#111111",
"green": "#111111",
"name": "Tango Dark (modified)",
"purple": "#111111",
"red": "#111111",
"selectionBackground": "#111111",
"white": "#111111",
"yellow": "#111111"
},
]
})-" };
implementation::SettingsLoader oldLoader{ oldSettingsJson, DefaultJson };
oldLoader.MergeInboxIntoUserSettings();
oldLoader.FinalizeLayering();
VERIFY_IS_TRUE(oldLoader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto oldSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(oldLoader));
const auto oldResult{ oldSettings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, DefaultJson };
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
newLoader.FixupUserSettings();
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
void SerializationTests::RoundtripUserDeletedColorSchemeCollision()
{
static constexpr std::string_view oldSettingsJson{ R"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
}
],
"schemes": [
{
"name": "Tango Dark",
"foreground": "#D3D7CF",
"background": "#000000",
"cursorColor": "#FFFFFF",
"black": "#000000",
"red": "#CC0000",
"green": "#4E9A06",
"yellow": "#C4A000",
"blue": "#3465A4",
"purple": "#75507B",
"cyan": "#06989A",
"white": "#D3D7CF",
"brightBlack": "#555753",
"brightRed": "#EF2929",
"brightGreen": "#8AE234",
"brightYellow": "#FCE94F",
"brightBlue": "#729FCF",
"brightPurple": "#AD7FA8",
"brightCyan": "#34E2E2",
"brightWhite": "#EEEEEC"
}
]
})" };
// Key differences: Tango Dark has been deleted, as it was identical to the inbox one.
static constexpr std::string_view newSettingsJson{ R"-(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles":
{
"list":
[
{
"name": "profile0",
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}"
}
]
},
"actions": [ ],
"schemes": [ ]
})-" };
implementation::SettingsLoader oldLoader{ oldSettingsJson, DefaultJson };
oldLoader.MergeInboxIntoUserSettings();
oldLoader.FinalizeLayering();
VERIFY_IS_TRUE(oldLoader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk");
const auto oldSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(oldLoader));
const auto oldResult{ oldSettings->ToJson() };
implementation::SettingsLoader newLoader{ newSettingsJson, DefaultJson };
newLoader.MergeInboxIntoUserSettings();
newLoader.FinalizeLayering();
newLoader.FixupUserSettings();
const auto newSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(newLoader));
const auto newResult{ newSettings->ToJson() };
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
}
}

View File

@@ -14,9 +14,9 @@
<PropertyGroup>
<ProjectGuid>{CA5CAD1A-9B68-456A-B13E-C8218070DC42}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>SettingsModelUnitTests</RootNamespace>
<ProjectName>UnitTests_SettingsModel</ProjectName>
<TargetName>SettingsModel.Unit.Tests</TargetName>
<RootNamespace>SettingsModelLocalTests</RootNamespace>
<ProjectName>LocalTests_SettingsModel</ProjectName>
<TargetName>SettingsModel.LocalTests</TargetName>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<OpenConsoleCppWinRTProject>true</OpenConsoleCppWinRTProject>
<!-- TerminalCppWinrt is not set intentionally -->
@@ -64,6 +64,7 @@
_ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's -->
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\dll\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj" />
</ItemGroup>
<!-- ========================= Globals ======================== -->
@@ -97,16 +98,4 @@
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
<Import Project="$(OpenConsoleDir)src\common.build.tests.props" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
<ItemGroup>
<ProjectPriFiles Include="$(TargetDir)\Microsoft.Terminal.Settings.Model.pri" />
<OutputPriFiles Include="$(TargetDir)\resources.pri" />
</ItemGroup>
<Target Name="AfterBuild" Inputs="@(ProjectPriFiles)" Outputs="@(OutputPriFiles)">
<Copy SourceFiles="@(ProjectPriFiles)"
DestinationFiles="@(OutputPriFiles)"
UseHardLinksIfPossible="true"
SkipUnchangedFiles="true" />
</Target>
</Project>

Some files were not shown because too many files have changed in this diff Show More