mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-22 11:09:42 +01:00
Compare commits
111 Commits
vanzue-pat
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3da4e8522c | ||
|
|
9f972fe410 | ||
|
|
93a0689e9e | ||
|
|
31f74040aa | ||
|
|
b0a500e875 | ||
|
|
3b3df5b74f | ||
|
|
2e2e060f4e | ||
|
|
35ed9f5b56 | ||
|
|
b5584eee76 | ||
|
|
5380b477a5 | ||
|
|
37c80b40bf | ||
|
|
04d626a053 | ||
|
|
b111899ac0 | ||
|
|
e6e0f6d541 | ||
|
|
68c364f75e | ||
|
|
7649c16d2e | ||
|
|
66489acaac | ||
|
|
d62eeb39b9 | ||
|
|
87b79a7ee8 | ||
|
|
367504537d | ||
|
|
9b6794f359 | ||
|
|
0cfb321b9b | ||
|
|
4b0534acf8 | ||
|
|
b79cf60d94 | ||
|
|
74efb00f40 | ||
|
|
bf83bbd94d | ||
|
|
12b7555006 | ||
|
|
77a0d77471 | ||
|
|
9a3667c91f | ||
|
|
45145cf1a4 | ||
|
|
f758907850 | ||
|
|
9323598fed | ||
|
|
e8c63a26e0 | ||
|
|
6f867d178c | ||
|
|
4250bd82ac | ||
|
|
c4047a8ee2 | ||
|
|
d9c168c3fe | ||
|
|
210febe270 | ||
|
|
e53cb409b4 | ||
|
|
98c771788b | ||
|
|
5dee7c9d89 | ||
|
|
c2ba9af144 | ||
|
|
29c15601f0 | ||
|
|
558e2af6cb | ||
|
|
70c5e3c9d5 | ||
|
|
986ffbeede | ||
|
|
8466c47059 | ||
|
|
229d3b4991 | ||
|
|
7ca9798df5 | ||
|
|
a6e39d5535 | ||
|
|
edf02497af | ||
|
|
02b583267d | ||
|
|
e2569ec4ee | ||
|
|
c760962573 | ||
|
|
ec2480385b | ||
|
|
458d3a2699 | ||
|
|
a58b802cb9 | ||
|
|
a72bf5aed7 | ||
|
|
fb49f6a5e5 | ||
|
|
b4a7bb4a7a | ||
|
|
8615c48c5c | ||
|
|
1aa78e1b96 | ||
|
|
b0c862dd67 | ||
|
|
b6e3b8a3ee | ||
|
|
a2d0d3b262 | ||
|
|
ee53a6d138 | ||
|
|
126a3c0de8 | ||
|
|
a94bd91dba | ||
|
|
b2f2462ad6 | ||
|
|
b6f0ced53e | ||
|
|
a405f27d19 | ||
|
|
86d04cc3bd | ||
|
|
381482e9a0 | ||
|
|
9e7d212c31 | ||
|
|
11c9d913cc | ||
|
|
31f5af7e14 | ||
|
|
568c2ca388 | ||
|
|
27124972cd | ||
|
|
ca9488c875 | ||
|
|
d73a8a0a2c | ||
|
|
c953ce7eca | ||
|
|
9fc7a180d4 | ||
|
|
ccca007562 | ||
|
|
07c85065f2 | ||
|
|
c06767b73e | ||
|
|
4c639a085c | ||
|
|
8aa7349f32 | ||
|
|
fa86f2223c | ||
|
|
30215e8b4f | ||
|
|
ee386e7be4 | ||
|
|
9ba2030b5e | ||
|
|
1ba832b732 | ||
|
|
46f27c1612 | ||
|
|
1d81ef8935 | ||
|
|
e19cdba074 | ||
|
|
4223286061 | ||
|
|
8e6bd141ca | ||
|
|
d29121c3fd | ||
|
|
58e0530980 | ||
|
|
9272e3112b | ||
|
|
a64095c3d3 | ||
|
|
49480041cd | ||
|
|
547b664a8c | ||
|
|
72320bea79 | ||
|
|
910de53a0a | ||
|
|
11f60de543 | ||
|
|
e7eb2d0239 | ||
|
|
aefae2935e | ||
|
|
727367960e | ||
|
|
32700658fd | ||
|
|
a7cb535515 |
6
.github/actions/spell-check/allow/code.txt
vendored
6
.github/actions/spell-check/allow/code.txt
vendored
@@ -282,3 +282,9 @@ xef
|
||||
xes
|
||||
PACKAGEVERSIONNUMBER
|
||||
APPXMANIFESTVERSION
|
||||
|
||||
# MRU lists
|
||||
CACHEWRITE
|
||||
MRUCMPPROC
|
||||
MRUINFO
|
||||
REGSTR
|
||||
|
||||
14
.github/actions/spell-check/expect.txt
vendored
14
.github/actions/spell-check/expect.txt
vendored
@@ -38,6 +38,7 @@ ALLAPPS
|
||||
ALLCHILDREN
|
||||
ALLINPUT
|
||||
Allman
|
||||
Allmodule
|
||||
ALLOWUNDO
|
||||
ALLVIEW
|
||||
ALPHATYPE
|
||||
@@ -246,6 +247,7 @@ CONTEXTMENUHANDLER
|
||||
contractversion
|
||||
CONTROLPARENT
|
||||
copiedcolorrepresentation
|
||||
coppied
|
||||
copyable
|
||||
COPYPEN
|
||||
COREWINDOW
|
||||
@@ -444,6 +446,7 @@ ERRORIMAGE
|
||||
ERRORTITLE
|
||||
ESettings
|
||||
esrp
|
||||
etd
|
||||
ETDT
|
||||
etl
|
||||
etw
|
||||
@@ -528,8 +531,8 @@ frm
|
||||
FROMTOUCH
|
||||
fsanitize
|
||||
fsmgmt
|
||||
fxf
|
||||
fuzzingtesting
|
||||
fxf
|
||||
FZE
|
||||
gacutil
|
||||
Gaeilge
|
||||
@@ -734,6 +737,7 @@ INSTALLSTARTMENUSHORTCUT
|
||||
INSTALLSTATE
|
||||
Inste
|
||||
Interlop
|
||||
intput
|
||||
INTRESOURCE
|
||||
INVALIDARG
|
||||
invalidoperatioexception
|
||||
@@ -816,6 +820,7 @@ LMEM
|
||||
LMENU
|
||||
LOADFROMFILE
|
||||
LOBYTE
|
||||
localappdata
|
||||
localpackage
|
||||
LOCALSYSTEM
|
||||
LOCATIONCHANGE
|
||||
@@ -1118,6 +1123,7 @@ oldtheme
|
||||
oleaut
|
||||
OLECHAR
|
||||
onebranch
|
||||
OOBEUI
|
||||
openas
|
||||
opencode
|
||||
OPENFILENAME
|
||||
@@ -1383,8 +1389,8 @@ RIDEV
|
||||
RIGHTSCROLLBAR
|
||||
riid
|
||||
RKey
|
||||
RNumber
|
||||
Rns
|
||||
RNumber
|
||||
rop
|
||||
ROUNDSMALL
|
||||
ROWSETEXT
|
||||
@@ -1395,6 +1401,7 @@ Rsp
|
||||
rstringalnum
|
||||
rstringalpha
|
||||
rstringdigit
|
||||
rtb
|
||||
RTB
|
||||
RTLREADING
|
||||
rtm
|
||||
@@ -1529,6 +1536,7 @@ SLGP
|
||||
sln
|
||||
SMALLICON
|
||||
smartphone
|
||||
smileys
|
||||
SMTO
|
||||
SNAPPROCESS
|
||||
snk
|
||||
@@ -1756,8 +1764,8 @@ Uptool
|
||||
urld
|
||||
Usb
|
||||
USEDEFAULT
|
||||
USEINSTALLERFORTEST
|
||||
USEFILEATTRIBUTES
|
||||
USEINSTALLERFORTEST
|
||||
USESHOWWINDOW
|
||||
USESTDHANDLES
|
||||
USRDLL
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
<PackageVersion Include="NLog.Schema" Version="5.2.8" />
|
||||
<PackageVersion Include="OpenAI" Version="2.0.0" />
|
||||
<PackageVersion Include="ReverseMarkdown" Version="4.1.0" />
|
||||
<PackageVersion Include="RtfPipe" Version="2.0.7677.4303" />
|
||||
<PackageVersion Include="ScipBe.Common.Office.OneNote" Version="3.0.1" />
|
||||
<PackageVersion Include="SharpCompress" Version="0.37.2" />
|
||||
<!-- Don't update SkiaSharp.Views.WinUI to version 3.* branch as this brakes the HexBox control in Registry Preview. -->
|
||||
|
||||
@@ -751,6 +751,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.TimeDa
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WindowWalker.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests.csproj", "{B0FE6EF3-5FB3-B8DC-7507-008BBB392FD8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UITest-Settings", "src\settings-ui\UITest-Settings\UITest-Settings.csproj", "{129A8FCD-CB54-4AD1-AC42-2BFCE159107A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UITest-ColorPicker", "src\modules\colorPicker\UITest-ColorPicker\UITest-ColorPicker.csproj", "{E4BAAD93-A499-42FD-A741-7E9591594B61}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedPaste-UITests", "src\modules\AdvancedPaste\UITest-AdvancedPaste\AdvancedPaste-UITests.csproj", "{2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9B3962F4-AB69-4C2A-8917-2C8448AC6960}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -2733,14 +2741,6 @@ Global
|
||||
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|x64.ActiveCfg = Release|x64
|
||||
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|x64.Build.0 = Release|x64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|x64.Build.0 = Debug|x64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|x64.ActiveCfg = Release|x64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|x64.Build.0 = Release|x64
|
||||
{9D3F3793-EFE3-4525-8782-238015DABA62}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{9D3F3793-EFE3-4525-8782-238015DABA62}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{9D3F3793-EFE3-4525-8782-238015DABA62}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2757,6 +2757,14 @@ Global
|
||||
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}.Release|x64.ActiveCfg = Release|x64
|
||||
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1}.Release|x64.Build.0 = Release|x64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|x64.Build.0 = Debug|x64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|x64.ActiveCfg = Release|x64
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|x64.Build.0 = Release|x64
|
||||
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2797,6 +2805,30 @@ Global
|
||||
{B0FE6EF3-5FB3-B8DC-7507-008BBB392FD8}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{B0FE6EF3-5FB3-B8DC-7507-008BBB392FD8}.Release|x64.ActiveCfg = Release|x64
|
||||
{B0FE6EF3-5FB3-B8DC-7507-008BBB392FD8}.Release|x64.Build.0 = Release|x64
|
||||
{129A8FCD-CB54-4AD1-AC42-2BFCE159107A}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{129A8FCD-CB54-4AD1-AC42-2BFCE159107A}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{129A8FCD-CB54-4AD1-AC42-2BFCE159107A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{129A8FCD-CB54-4AD1-AC42-2BFCE159107A}.Debug|x64.Build.0 = Debug|x64
|
||||
{129A8FCD-CB54-4AD1-AC42-2BFCE159107A}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{129A8FCD-CB54-4AD1-AC42-2BFCE159107A}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{129A8FCD-CB54-4AD1-AC42-2BFCE159107A}.Release|x64.ActiveCfg = Release|x64
|
||||
{129A8FCD-CB54-4AD1-AC42-2BFCE159107A}.Release|x64.Build.0 = Release|x64
|
||||
{E4BAAD93-A499-42FD-A741-7E9591594B61}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E4BAAD93-A499-42FD-A741-7E9591594B61}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E4BAAD93-A499-42FD-A741-7E9591594B61}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E4BAAD93-A499-42FD-A741-7E9591594B61}.Debug|x64.Build.0 = Debug|x64
|
||||
{E4BAAD93-A499-42FD-A741-7E9591594B61}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{E4BAAD93-A499-42FD-A741-7E9591594B61}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E4BAAD93-A499-42FD-A741-7E9591594B61}.Release|x64.ActiveCfg = Release|x64
|
||||
{E4BAAD93-A499-42FD-A741-7E9591594B61}.Release|x64.Build.0 = Release|x64
|
||||
{2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}.Debug|x64.Build.0 = Debug|x64
|
||||
{2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}.Release|x64.ActiveCfg = Release|x64
|
||||
{2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -3054,8 +3086,8 @@ Global
|
||||
{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
|
||||
{605E914B-7232-4789-AF46-BF5D3DDFC14E} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
|
||||
{E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
|
||||
{D5E5F5EA-1B6C-4A73-88BE-304F36C9E4EE} = {9873BA05-4C41-4819-9283-CF45D795431B}
|
||||
{7F5B9557-5878-4438-A721-3E28296BA193} = {9873BA05-4C41-4819-9283-CF45D795431B}
|
||||
{D5E5F5EA-1B6C-4A73-88BE-304F36C9E4EE} = {9B3962F4-AB69-4C2A-8917-2C8448AC6960}
|
||||
{7F5B9557-5878-4438-A721-3E28296BA193} = {9B3962F4-AB69-4C2A-8917-2C8448AC6960}
|
||||
{DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{0A84F764-3A88-44CD-AA96-41BDBD48627B} = {DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C}
|
||||
{E4585179-2AC1-4D5F-A3FF-CFC5392F694C} = {DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C}
|
||||
@@ -3081,16 +3113,20 @@ Global
|
||||
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||
{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{9D3F3793-EFE3-4525-8782-238015DABA62} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
|
||||
{BCDC7246-F4F8-4EED-8DE6-037AA2E7C6D1} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
|
||||
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{840455DF-5634-51BB-D937-9D7D32F0B0C2} = {7520A2FE-00A2-49B8-83ED-DB216E874C04}
|
||||
{15EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
|
||||
{2CF0567E-1E00-4E3F-1561-BF85F5CE5FE7} = {15EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{790247CB-2B95-E139-E933-09D10137EEAF} = {15EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{18525614-CDB2-8BBE-B1B4-3812CD990C22} = {15EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{B0FE6EF3-5FB3-B8DC-7507-008BBB392FD8} = {15EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{129A8FCD-CB54-4AD1-AC42-2BFCE159107A} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
|
||||
{E4BAAD93-A499-42FD-A741-7E9591594B61} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0}
|
||||
{2B1505FA-132A-460B-B22B-7CC3FFAB0C5D} = {9B3962F4-AB69-4C2A-8917-2C8448AC6960}
|
||||
{9B3962F4-AB69-4C2A-8917-2C8448AC6960} = {9873BA05-4C41-4819-9283-CF45D795431B}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
@@ -24,5 +24,16 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
this.Find<NavigationViewItem>(value).Click();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select a text item from the ComboBox.
|
||||
/// </summary>
|
||||
/// <param name="value">The text to select from the ComboBox.</param>
|
||||
public void SelectTxt(string value)
|
||||
{
|
||||
this.Click(); // First click to expand the ComboBox
|
||||
Thread.Sleep(100); // Wait for the dropdown to appear
|
||||
this.Find<Element>(value).Click(); // Find and click the text item using basic Element type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
src/common/UITestAutomation/Element/RadioButton.cs
Normal file
38
src/common/UITestAutomation/Element/RadioButton.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a radio button UI element in the application.
|
||||
/// </summary>
|
||||
public class RadioButton : Element
|
||||
{
|
||||
private static readonly string ExpectedControlType = "ControlType.RadioButton";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RadioButton"/> class.
|
||||
/// </summary>
|
||||
public RadioButton()
|
||||
{
|
||||
this.TargetControlType = RadioButton.ExpectedControlType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the RadioButton is selected.
|
||||
/// </summary>
|
||||
public bool IsSelected => this.Selected;
|
||||
|
||||
/// <summary>
|
||||
/// Select the RadioButton.
|
||||
/// </summary>
|
||||
public void Select()
|
||||
{
|
||||
if (!this.IsSelected)
|
||||
{
|
||||
this.Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}</ProjectGuid>
|
||||
<RootNamespace>Microsoft.AdvancedPaste.UITests</RootNamespace>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
|
||||
<!-- This is a UI test, so don't run as part of MSBuild -->
|
||||
<RunVSTest>false</RunVSTest>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\UITests-AdvancedPaste\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Appium.WebDriver" />
|
||||
<PackageReference Include="MSTest" />
|
||||
<PackageReference Include="System.Net.Http" />
|
||||
<PackageReference Include="System.Private.Uri" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="TestFiles\**\*.*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,791 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Microsoft.AdvancedPaste.UITests.Helper;
|
||||
using Microsoft.CodeCoverage.Core.Reports.Coverage;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
using static System.Resources.ResXFileRef;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
using static System.Windows.Forms.VisualStyles.VisualStyleElement.ToolTip;
|
||||
|
||||
namespace Microsoft.AdvancedPaste.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class AdvancedPasteUITest : UITestBase
|
||||
{
|
||||
private readonly string testFilesFolderPath;
|
||||
private readonly string tempRTFFileName = "TempFile.rtf";
|
||||
private readonly string pasteAsPlainTextRawFileName = "PasteAsPlainTextFileRaw.rtf";
|
||||
private readonly string pasteAsPlainTextPlainFileName = "PasteAsPlainTextFilePlain.rtf";
|
||||
private readonly string pasteAsPlainTextPlainNoRepeatFileName = "PasteAsPlainTextFilePlainNoRepeat.rtf";
|
||||
private readonly string wordpadPath = @"C:\Program Files\wordpad\wordpad.exe";
|
||||
|
||||
private readonly string tempTxtFileName = "TempFile.txt";
|
||||
private readonly string pasteAsMarkdownSrcFile = "PasteAsMarkdownFile.html";
|
||||
private readonly string pasteAsMarkdownResultFile = "PasteAsMarkdownResultFile.txt";
|
||||
|
||||
private readonly string pasteAsJsonFileName = "PasteAsJsonFile.xml";
|
||||
private readonly string pasteAsJsonResultFile = "PasteAsJsonResultFile.txt";
|
||||
|
||||
private bool _notepadSettingsChanged;
|
||||
|
||||
// Static constructor - runs before any instance is created
|
||||
static AdvancedPasteUITest()
|
||||
{
|
||||
// Using the predefined settings.
|
||||
// paste as plain text: win + ctrl + alt + o
|
||||
// paste as markdown text: win + ctrl + alt + m
|
||||
// paste as json text: win + ctrl + alt + j
|
||||
CopySettingsFileBeforeTests();
|
||||
}
|
||||
|
||||
public AdvancedPasteUITest()
|
||||
: base(PowerToysModule.PowerToysSettings, size: WindowSize.Small)
|
||||
{
|
||||
Type currentTestType = typeof(AdvancedPasteUITest);
|
||||
string? dirName = Path.GetDirectoryName(currentTestType.Assembly.Location);
|
||||
Assert.IsNotNull(dirName, "Failed to get directory name of the current test assembly.");
|
||||
|
||||
string testFilesFolder = Path.Combine(dirName, "TestFiles");
|
||||
Assert.IsTrue(Directory.Exists(testFilesFolder), $"Test files directory not found at: {testFilesFolder}");
|
||||
|
||||
testFilesFolderPath = testFilesFolder;
|
||||
|
||||
// ignore the notepad settings in pipeline
|
||||
_notepadSettingsChanged = true;
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
Session.CloseMainWindow();
|
||||
SendKeys(Key.Win, Key.M);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("AdvancedPasteUITest")]
|
||||
[TestCategory("PasteAsPlainText")]
|
||||
[Ignore("Temporarily disabled due to wordpad.exe is missing in pipeline.")]
|
||||
public void TestCasePasteAsPlainText()
|
||||
{
|
||||
// Copy some rich text(e.g word of the text is different color, another work is bold, underlined, etd.).
|
||||
// Paste the text using standard Windows Ctrl + V shortcut and ensure that rich text is pasted(with all colors, formatting, etc.)
|
||||
DeleteAndCopyFile(pasteAsPlainTextRawFileName, tempRTFFileName);
|
||||
ContentCopyAndPasteDirectly(tempRTFFileName, isRTF: true);
|
||||
|
||||
var resultWithFormatting = FileReader.CompareRtfFiles(
|
||||
Path.Combine(testFilesFolderPath, tempRTFFileName),
|
||||
Path.Combine(testFilesFolderPath, pasteAsPlainTextRawFileName),
|
||||
compareFormatting: true);
|
||||
|
||||
Assert.IsTrue(resultWithFormatting.IsConsistent, "RTF files should be identical including formatting");
|
||||
|
||||
// Paste the text using Paste As Plain Text activation shortcut and ensure that plain text without any formatting is pasted.
|
||||
// Paste again the text using standard Windows Ctrl + V shortcut and ensure the text is now pasted plain without formatting as well.
|
||||
DeleteAndCopyFile(pasteAsPlainTextRawFileName, tempRTFFileName);
|
||||
ContentCopyAndPasteWithShortcutThenPasteAgain(tempRTFFileName, isRTF: true);
|
||||
resultWithFormatting = FileReader.CompareRtfFiles(
|
||||
Path.Combine(testFilesFolderPath, tempRTFFileName),
|
||||
Path.Combine(testFilesFolderPath, pasteAsPlainTextPlainFileName),
|
||||
compareFormatting: true);
|
||||
Assert.IsTrue(resultWithFormatting.IsConsistent, "RTF files should be identical without formatting");
|
||||
|
||||
// Copy some rich text again.
|
||||
// Open Advanced Paste window using hotkey, click Paste as Plain Text button and confirm that plain text without any formatting is pasted.
|
||||
DeleteAndCopyFile(pasteAsPlainTextRawFileName, tempRTFFileName);
|
||||
ContentCopyAndPasteCase3(tempRTFFileName, isRTF: true);
|
||||
resultWithFormatting = FileReader.CompareRtfFiles(
|
||||
Path.Combine(testFilesFolderPath, tempRTFFileName),
|
||||
Path.Combine(testFilesFolderPath, pasteAsPlainTextPlainNoRepeatFileName),
|
||||
compareFormatting: true);
|
||||
Assert.IsTrue(resultWithFormatting.IsConsistent, "RTF files should be identical without formatting");
|
||||
|
||||
// Copy some rich text again.
|
||||
// Open Advanced Paste window using hotkey, press Ctrl + 1 and confirm that plain text without any formatting is pasted.
|
||||
DeleteAndCopyFile(pasteAsPlainTextRawFileName, tempRTFFileName);
|
||||
ContentCopyAndPasteCase4(tempRTFFileName, isRTF: true);
|
||||
resultWithFormatting = FileReader.CompareRtfFiles(
|
||||
Path.Combine(testFilesFolderPath, tempRTFFileName),
|
||||
Path.Combine(testFilesFolderPath, pasteAsPlainTextPlainNoRepeatFileName),
|
||||
compareFormatting: true);
|
||||
Assert.IsTrue(resultWithFormatting.IsConsistent, "RTF files should be identical without formatting");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("AdvancedPasteUITest")]
|
||||
[TestCategory("PasteAsMarkdownCase1")]
|
||||
public void TestCasePasteAsMarkdownCase1()
|
||||
{
|
||||
if (_notepadSettingsChanged == false)
|
||||
{
|
||||
ChangeNotePadSettings();
|
||||
}
|
||||
|
||||
// Copy some text(e.g.some HTML text - convertible to Markdown)
|
||||
// Paste the text using set hotkey and confirm that pasted text is converted to markdown
|
||||
DeleteAndCopyFile(pasteAsMarkdownSrcFile, tempTxtFileName);
|
||||
ContentCopyAndPasteAsMarkdownCase1(tempTxtFileName);
|
||||
var result = FileReader.CompareRtfFiles(
|
||||
Path.Combine(testFilesFolderPath, tempTxtFileName),
|
||||
Path.Combine(testFilesFolderPath, pasteAsMarkdownResultFile),
|
||||
compareFormatting: true);
|
||||
Assert.IsTrue(result.IsConsistent, "Paste as markdown using shortcut failed.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("AdvancedPasteUITest")]
|
||||
[TestCategory("PasteAsMarkdownCase2")]
|
||||
public void TestCasePasteAsMarkdownCase2()
|
||||
{
|
||||
if (_notepadSettingsChanged == false)
|
||||
{
|
||||
ChangeNotePadSettings();
|
||||
}
|
||||
|
||||
// Copy some text(same as in the previous step or different.If nothing is coppied between steps, previously pasted Markdown text will be picked up from clipboard and converted again to nested Markdown).
|
||||
// Open Advanced Paste window using hotkey, click Paste as markdown button and confirm that pasted text is converted to markdown
|
||||
DeleteAndCopyFile(pasteAsMarkdownSrcFile, tempTxtFileName);
|
||||
ContentCopyAndPasteAsMarkdownCase2(tempTxtFileName);
|
||||
var result = FileReader.CompareRtfFiles(
|
||||
Path.Combine(testFilesFolderPath, tempTxtFileName),
|
||||
Path.Combine(testFilesFolderPath, pasteAsMarkdownResultFile),
|
||||
compareFormatting: true);
|
||||
Assert.IsTrue(result.IsConsistent, "Paste as markdown using shortcut failed.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("AdvancedPasteUITest")]
|
||||
[TestCategory("PasteAsMarkdownCase3")]
|
||||
public void TestCasePasteAsMarkdownCase3()
|
||||
{
|
||||
if (_notepadSettingsChanged == false)
|
||||
{
|
||||
ChangeNotePadSettings();
|
||||
}
|
||||
|
||||
// Copy some text(same as in the previous step or different.If nothing is coppied between steps, previously pasted Markdown text will be picked up from clipboard and converted again to nested Markdown).
|
||||
// Open Advanced Paste window using hotkey, press Ctrl + 2 and confirm that pasted text is converted to markdown
|
||||
DeleteAndCopyFile(pasteAsMarkdownSrcFile, tempTxtFileName);
|
||||
ContentCopyAndPasteAsMarkdownCase3(tempTxtFileName);
|
||||
var result = FileReader.CompareRtfFiles(
|
||||
Path.Combine(testFilesFolderPath, tempTxtFileName),
|
||||
Path.Combine(testFilesFolderPath, pasteAsMarkdownResultFile),
|
||||
compareFormatting: true);
|
||||
Assert.IsTrue(result.IsConsistent, "Paste as markdown using shortcut failed.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("AdvancedPasteUITest")]
|
||||
[TestCategory("PasteAsJSONCase1")]
|
||||
public void TestCasePasteAsJSONCase1()
|
||||
{
|
||||
if (_notepadSettingsChanged == false)
|
||||
{
|
||||
ChangeNotePadSettings();
|
||||
}
|
||||
|
||||
// Copy some XML or CSV text(or any other text, it will be converted to simple JSON object)
|
||||
// Paste the text using set hotkey and confirm that pasted text is converted to JSON
|
||||
DeleteAndCopyFile(pasteAsJsonFileName, tempTxtFileName);
|
||||
ContentCopyAndPasteAsJsonCase1(tempTxtFileName);
|
||||
var result = FileReader.CompareRtfFiles(
|
||||
Path.Combine(testFilesFolderPath, tempTxtFileName),
|
||||
Path.Combine(testFilesFolderPath, pasteAsJsonResultFile),
|
||||
compareFormatting: true);
|
||||
Assert.IsTrue(result.IsConsistent, "Paste as Json using shortcut failed.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("AdvancedPasteUITest")]
|
||||
[TestCategory("PasteAsJSONCase2")]
|
||||
public void TestCasePasteAsJSONCase2()
|
||||
{
|
||||
if (_notepadSettingsChanged == false)
|
||||
{
|
||||
ChangeNotePadSettings();
|
||||
}
|
||||
|
||||
// Copy some text(same as in the previous step or different.If nothing is coppied between steps, previously pasted JSON text will be picked up from clipboard and converted again to nested JSON).
|
||||
// Open Advanced Paste window using hotkey, click Paste as markdown button and confirm that pasted text is converted to markdown
|
||||
DeleteAndCopyFile(pasteAsJsonFileName, tempTxtFileName);
|
||||
ContentCopyAndPasteAsJsonCase2(tempTxtFileName);
|
||||
var result = FileReader.CompareRtfFiles(
|
||||
Path.Combine(testFilesFolderPath, tempTxtFileName),
|
||||
Path.Combine(testFilesFolderPath, pasteAsJsonResultFile),
|
||||
compareFormatting: true);
|
||||
Assert.IsTrue(result.IsConsistent, "Paste as Json using shortcut failed.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("AdvancedPasteUITest")]
|
||||
[TestCategory("PasteAsJSONCase3")]
|
||||
public void TestCasePasteAsJSONCase3()
|
||||
{
|
||||
if (_notepadSettingsChanged == false)
|
||||
{
|
||||
ChangeNotePadSettings();
|
||||
}
|
||||
|
||||
// Copy some text(same as in the previous step or different.If nothing is coppied between steps, previously pasted JSON text will be picked up from clipboard and converted again to nested JSON).
|
||||
// Open Advanced Paste window using hotkey, press Ctrl + 3 and confirm that pasted text is converted to markdown
|
||||
DeleteAndCopyFile(pasteAsJsonFileName, tempTxtFileName);
|
||||
ContentCopyAndPasteAsJsonCase3(tempTxtFileName);
|
||||
var result = FileReader.CompareRtfFiles(
|
||||
Path.Combine(testFilesFolderPath, tempTxtFileName),
|
||||
Path.Combine(testFilesFolderPath, pasteAsJsonResultFile),
|
||||
compareFormatting: true);
|
||||
Assert.IsTrue(result.IsConsistent, "Paste as Json using shortcut failed.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Clipboard History
|
||||
- [] Open Settings and Enable clipboard history (if not enabled already). Open Advanced Paste window with hotkey, click Clipboard history and try deleting some entry. Check OS clipboard history (Win+V), and confirm that the same entry no longer exist.
|
||||
- [] Open Advanced Paste window with hotkey, click Clipboard history, and click any entry (but first). Observe that entry is put on top of clipboard history. Check OS clipboard history (Win+V), and confirm that the same entry is on top of the clipboard.
|
||||
- [] Open Settings and Disable clipboard history. Open Advanced Paste window with hotkey and observe that Clipboard history button is disabled.
|
||||
* Disable Advanced Paste, try different Advanced Paste hotkeys and confirm that it's disabled and nothing happens.
|
||||
*/
|
||||
private void TestCaseClipboardHistory()
|
||||
{
|
||||
}
|
||||
|
||||
private void ContentCopyAndPasteDirectly(string fileName, bool isRTF = false)
|
||||
{
|
||||
string tempFile = Path.Combine(testFilesFolderPath, fileName);
|
||||
|
||||
Process process = Process.Start(isRTF ? wordpadPath : "notepad.exe", tempFile);
|
||||
if (process == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start {(isRTF ? "WordPad" : "Notepad")}.");
|
||||
}
|
||||
|
||||
Thread.Sleep(15000);
|
||||
|
||||
var window = FindWindowWithFlexibleTitle(Path.GetFileName(tempFile), isRTF);
|
||||
|
||||
window.Click();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.A);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.C);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Delete);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.V);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Backspace);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.S);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
process.Kill(true);
|
||||
}
|
||||
|
||||
private void ContentCopyAndPasteWithShortcutThenPasteAgain(string fileName, bool isRTF = false)
|
||||
{
|
||||
string tempFile = Path.Combine(testFilesFolderPath, fileName);
|
||||
|
||||
Process process = Process.Start(isRTF ? wordpadPath : "notepad.exe", tempFile);
|
||||
if (process == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start {(isRTF ? "WordPad" : "Notepad")}.");
|
||||
}
|
||||
|
||||
Thread.Sleep(15000);
|
||||
|
||||
var window = FindWindowWithFlexibleTitle(Path.GetFileName(tempFile), isRTF);
|
||||
|
||||
window.Click();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.A);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.C);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Delete);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Win, Key.LCtrl, Key.Alt, Key.O);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.V);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.S);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
process.Kill(true);
|
||||
}
|
||||
|
||||
private void ContentCopyAndPasteCase3(string fileName, bool isRTF = false)
|
||||
{
|
||||
// Copy some rich text again.
|
||||
// Open Advanced Paste window using hotkey, click Paste as Plain Text button and confirm that plain text without any formatting is pasted.
|
||||
string tempFile = Path.Combine(testFilesFolderPath, fileName);
|
||||
|
||||
Process process = Process.Start(isRTF ? wordpadPath : "notepad.exe", tempFile);
|
||||
if (process == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start {(isRTF ? "WordPad" : "Notepad")}.");
|
||||
}
|
||||
|
||||
Thread.Sleep(15000);
|
||||
|
||||
var window = FindWindowWithFlexibleTitle(Path.GetFileName(tempFile), isRTF);
|
||||
|
||||
window.Click();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.A);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.C);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Delete);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// Open Advanced Paste window using hotkey
|
||||
this.SendKeys(Key.Win, Key.Shift, Key.V);
|
||||
Thread.Sleep(15000);
|
||||
|
||||
// Click Paste as Plain Text button and confirm that plain text without any formatting is pasted.
|
||||
var apWind = this.Find<Window>("Advanced Paste", global: true);
|
||||
apWind.Find<TextBlock>("Paste as plain text").Click();
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.S);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
process.Kill(true);
|
||||
}
|
||||
|
||||
private void ContentCopyAndPasteCase4(string fileName, bool isRTF = false)
|
||||
{
|
||||
// Copy some rich text again.
|
||||
// Open Advanced Paste window using hotkey, press Ctrl + 1 and confirm that plain text without any formatting is pasted.
|
||||
string tempFile = Path.Combine(testFilesFolderPath, fileName);
|
||||
|
||||
Process process = Process.Start(isRTF ? wordpadPath : "notepad.exe", tempFile);
|
||||
if (process == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start {(isRTF ? "WordPad" : "Notepad")}.");
|
||||
}
|
||||
|
||||
Thread.Sleep(15000);
|
||||
var window = FindWindowWithFlexibleTitle(Path.GetFileName(tempFile), isRTF);
|
||||
|
||||
window.Click();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.A);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.C);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Delete);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// Open Advanced Paste window using hotkey
|
||||
this.SendKeys(Key.Win, Key.Shift, Key.V);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// press Ctrl + 1 and confirm that plain text without any formatting is pasted.
|
||||
this.SendKeys(Key.LCtrl, Key.Num1);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.S);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
process.Kill(true);
|
||||
}
|
||||
|
||||
private void ContentCopyAndPasteAsMarkdownCase1(string fileName, bool isRTF = false)
|
||||
{
|
||||
// Copy some rich text again.
|
||||
// Open Advanced Paste window using hotkey, press Ctrl + 1 and confirm that plain text without any formatting is pasted.
|
||||
string tempFile = Path.Combine(testFilesFolderPath, fileName);
|
||||
|
||||
Process process = Process.Start(isRTF ? wordpadPath : "notepad.exe", tempFile);
|
||||
if (process == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start {(isRTF ? "WordPad" : "Notepad")}.");
|
||||
}
|
||||
|
||||
Thread.Sleep(15000);
|
||||
|
||||
var window = FindWindowWithFlexibleTitle(Path.GetFileName(tempFile), isRTF);
|
||||
|
||||
window.Click();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.A);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.C);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Delete);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.Win, Key.LCtrl, Key.Alt, Key.M);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.S);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private void ContentCopyAndPasteAsMarkdownCase2(string fileName, bool isRTF = false)
|
||||
{
|
||||
string tempFile = Path.Combine(testFilesFolderPath, fileName);
|
||||
|
||||
Process process = Process.Start(isRTF ? wordpadPath : "notepad.exe", tempFile);
|
||||
if (process == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start {(isRTF ? "WordPad" : "Notepad")}.");
|
||||
}
|
||||
|
||||
Thread.Sleep(15000);
|
||||
|
||||
var window = FindWindowWithFlexibleTitle(Path.GetFileName(tempFile), isRTF);
|
||||
|
||||
window.Click();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.A);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.C);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Delete);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// Open Advanced Paste window using hotkey
|
||||
this.SendKeys(Key.Win, Key.Shift, Key.V);
|
||||
Thread.Sleep(15000);
|
||||
|
||||
// click Paste as markdown button and confirm that pasted text is converted to markdown
|
||||
var apWind = this.Find<Window>("Advanced Paste", global: true);
|
||||
apWind.Find<TextBlock>("Paste as markdown").Click();
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.S);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private void ContentCopyAndPasteAsMarkdownCase3(string fileName, bool isRTF = false)
|
||||
{
|
||||
string tempFile = Path.Combine(testFilesFolderPath, fileName);
|
||||
|
||||
Process process = Process.Start(isRTF ? wordpadPath : "notepad.exe", tempFile);
|
||||
if (process == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start {(isRTF ? "WordPad" : "Notepad")}.");
|
||||
}
|
||||
|
||||
Thread.Sleep(15000);
|
||||
var window = FindWindowWithFlexibleTitle(Path.GetFileName(tempFile), isRTF);
|
||||
|
||||
window.Click();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.A);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.C);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Delete);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// Open Advanced Paste window using hotkey
|
||||
this.SendKeys(Key.Win, Key.Shift, Key.V);
|
||||
Thread.Sleep(15000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.Num2);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.S);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private void ContentCopyAndPasteAsJsonCase1(string fileName, bool isRTF = false)
|
||||
{
|
||||
// Copy some rich text again.
|
||||
// Open Advanced Paste window using hotkey, press Ctrl + 1 and confirm that plain text without any formatting is pasted.
|
||||
string tempFile = Path.Combine(testFilesFolderPath, fileName);
|
||||
|
||||
Process process = Process.Start(isRTF ? wordpadPath : "notepad.exe", tempFile);
|
||||
if (process == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start {(isRTF ? "WordPad" : "Notepad")}.");
|
||||
}
|
||||
|
||||
Thread.Sleep(15000);
|
||||
|
||||
var window = FindWindowWithFlexibleTitle(Path.GetFileName(tempFile), isRTF);
|
||||
|
||||
window.Click();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.A);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.C);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Delete);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.Win, Key.LCtrl, Key.Alt, Key.J);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.S);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private void ContentCopyAndPasteAsJsonCase2(string fileName, bool isRTF = false)
|
||||
{
|
||||
string tempFile = Path.Combine(testFilesFolderPath, fileName);
|
||||
|
||||
Process process = Process.Start(isRTF ? wordpadPath : "notepad.exe", tempFile);
|
||||
if (process == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start {(isRTF ? "WordPad" : "Notepad")}.");
|
||||
}
|
||||
|
||||
Thread.Sleep(15000);
|
||||
|
||||
var window = FindWindowWithFlexibleTitle(Path.GetFileName(tempFile), isRTF);
|
||||
|
||||
window.Click();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.A);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.C);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Delete);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// Open Advanced Paste window using hotkey
|
||||
this.SendKeys(Key.Win, Key.Shift, Key.V);
|
||||
Thread.Sleep(15000);
|
||||
|
||||
// click Paste as markdown button and confirm that pasted text is converted to markdown
|
||||
var apWind = this.Find<Window>("Advanced Paste", global: true);
|
||||
apWind.Find<TextBlock>("Paste as JSON").Click();
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.S);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private void ContentCopyAndPasteAsJsonCase3(string fileName, bool isRTF = false)
|
||||
{
|
||||
string tempFile = Path.Combine(testFilesFolderPath, fileName);
|
||||
|
||||
Process process = Process.Start(isRTF ? wordpadPath : "notepad.exe", tempFile);
|
||||
if (process == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start {(isRTF ? "WordPad" : "Notepad")}.");
|
||||
}
|
||||
|
||||
Thread.Sleep(15000);
|
||||
|
||||
var window = FindWindowWithFlexibleTitle(Path.GetFileName(tempFile), isRTF);
|
||||
|
||||
window.Click();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.A);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.LCtrl, Key.C);
|
||||
Thread.Sleep(1000);
|
||||
this.SendKeys(Key.Delete);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// Open Advanced Paste window using hotkey
|
||||
this.SendKeys(Key.Win, Key.Shift, Key.V);
|
||||
Thread.Sleep(15000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.Num3);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.SendKeys(Key.LCtrl, Key.S);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private string DeleteAndCopyFile(string sourceFileName, string destinationFileName)
|
||||
{
|
||||
string sourcePath = Path.Combine(testFilesFolderPath, sourceFileName);
|
||||
string destinationPath = Path.Combine(testFilesFolderPath, destinationFileName);
|
||||
|
||||
// Check if source file exists
|
||||
if (!File.Exists(sourcePath))
|
||||
{
|
||||
throw new FileNotFoundException($"Source file not found: {sourcePath}");
|
||||
}
|
||||
|
||||
// Delete destination file if it exists
|
||||
if (File.Exists(destinationPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(destinationPath);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
throw new IOException($"Failed to delete file {destinationPath}. The file may be in use: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the source file to the destination
|
||||
try
|
||||
{
|
||||
File.Copy(sourcePath, destinationPath);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
throw new IOException($"Failed to copy file from {sourcePath} to {destinationPath}: {ex.Message}", ex);
|
||||
}
|
||||
|
||||
return destinationPath;
|
||||
}
|
||||
|
||||
private void ChangeNotePadSettings()
|
||||
{
|
||||
Process process = Process.Start("notepad.exe");
|
||||
if (process == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start Notepad.exe");
|
||||
}
|
||||
|
||||
Thread.Sleep(15000);
|
||||
|
||||
var window = FindWindowWithFlexibleTitle("Untitled", false);
|
||||
|
||||
window.Find<PowerToys.UITest.Button>("Settings").Click();
|
||||
var combobox = window.Find<PowerToys.UITest.ComboBox>("Opening files");
|
||||
combobox.SelectTxt("Open in a new window");
|
||||
|
||||
window.Find<Group>("When Notepad starts").Click();
|
||||
|
||||
window.Find<PowerToys.UITest.RadioButton>("Open a new window").Select();
|
||||
|
||||
_notepadSettingsChanged = true;
|
||||
window.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a window with flexible title matching, trying multiple title variations
|
||||
/// </summary>
|
||||
/// <param name="baseTitle">The base title to search for</param>
|
||||
/// <param name="isRTF">Whether the window is a WordPad window</param>
|
||||
/// <returns>The found Window element or throws an exception if not found</returns>
|
||||
private Window FindWindowWithFlexibleTitle(string baseTitle, bool isRTF)
|
||||
{
|
||||
Window? window = null;
|
||||
string appType = isRTF ? "WordPad" : "Notepad";
|
||||
|
||||
// Try different title variations
|
||||
string[] titleVariations = new string[]
|
||||
{
|
||||
baseTitle + (isRTF ? " - WordPad" : " - Notepad"), // With suffix
|
||||
baseTitle, // Without suffix
|
||||
Path.GetFileNameWithoutExtension(baseTitle) + (isRTF ? " - WordPad" : " - Notepad"), // Without extension, with suffix
|
||||
Path.GetFileNameWithoutExtension(baseTitle), // Without extension, without suffix
|
||||
};
|
||||
|
||||
Exception? lastException = null;
|
||||
|
||||
foreach (string title in titleVariations)
|
||||
{
|
||||
try
|
||||
{
|
||||
window = this.Find<Window>(title, global: true);
|
||||
if (window != null)
|
||||
{
|
||||
return window;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Save the exception, but continue trying other variations
|
||||
lastException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't find the window with any variation, throw an exception with details
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to find {appType} window with title containing '{baseTitle}'. ");
|
||||
}
|
||||
|
||||
private static void CopySettingsFileBeforeTests()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Determine the assembly location and test files path
|
||||
string? assemblyLocation = Path.GetDirectoryName(typeof(AdvancedPasteUITest).Assembly.Location);
|
||||
if (assemblyLocation == null)
|
||||
{
|
||||
Debug.WriteLine("ERROR: Failed to get assembly location");
|
||||
return;
|
||||
}
|
||||
|
||||
string testFilesFolder = Path.Combine(assemblyLocation, "TestFiles");
|
||||
if (!Directory.Exists(testFilesFolder))
|
||||
{
|
||||
Debug.WriteLine($"ERROR: Test files directory not found at: {testFilesFolder}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Settings file source path
|
||||
string settingsFileName = "settings.json";
|
||||
string sourceSettingsPath = Path.Combine(testFilesFolder, settingsFileName);
|
||||
|
||||
// Make sure the source file exists
|
||||
if (!File.Exists(sourceSettingsPath))
|
||||
{
|
||||
Debug.WriteLine($"ERROR: Settings file not found at: {sourceSettingsPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the target directory in %LOCALAPPDATA%
|
||||
string targetDirectory = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Microsoft",
|
||||
"PowerToys",
|
||||
"AdvancedPaste");
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
if (!Directory.Exists(targetDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
}
|
||||
|
||||
string targetSettingsPath = Path.Combine(targetDirectory, settingsFileName);
|
||||
|
||||
// Copy the file and overwrite if it exists
|
||||
File.Copy(sourceSettingsPath, targetSettingsPath, true);
|
||||
|
||||
Debug.WriteLine($"Successfully copied settings file from {sourceSettingsPath} to {targetSettingsPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"ERROR copying settings file: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Microsoft.AdvancedPaste.UITests.Helper;
|
||||
|
||||
public class FileReader
|
||||
{
|
||||
public static string ReadContent(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
return File.ReadAllText(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to read file: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReadRTFPlainText(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var rtb = new System.Windows.Forms.RichTextBox())
|
||||
{
|
||||
rtb.Rtf = File.ReadAllText(filePath);
|
||||
return rtb.Text;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to read plain text from file: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the contents of two RTF files to check if they are consistent.
|
||||
/// </summary>
|
||||
/// <param name="firstFilePath">Path to the first RTF file</param>
|
||||
/// <param name="secondFilePath">Path to the second RTF file</param>
|
||||
/// <param name="compareFormatting">If true, compares the raw RTF content (including formatting).
|
||||
/// If false, compares only the plain text content.</param>
|
||||
/// <returns>
|
||||
/// A tuple containing: (bool isConsistent, string firstContent, string secondContent)
|
||||
/// - isConsistent: true if the files are consistent according to the comparison method
|
||||
/// - firstContent: the content of the first file
|
||||
/// - secondContent: the content of the second file
|
||||
/// </returns>
|
||||
public static (bool IsConsistent, string FirstContent, string SecondContent) CompareRtfFiles(
|
||||
string firstFilePath,
|
||||
string secondFilePath,
|
||||
bool compareFormatting = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
string firstContent, secondContent;
|
||||
|
||||
if (compareFormatting)
|
||||
{
|
||||
// Compare raw RTF content (including formatting)
|
||||
firstContent = ReadContent(firstFilePath);
|
||||
secondContent = ReadContent(secondFilePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compare only the plain text content
|
||||
firstContent = ReadRTFPlainText(firstFilePath);
|
||||
secondContent = ReadRTFPlainText(secondFilePath);
|
||||
}
|
||||
|
||||
bool isConsistent = string.Equals(firstContent, secondContent, StringComparison.Ordinal);
|
||||
return (isConsistent, firstContent, secondContent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to compare RTF files: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<note>
|
||||
<to>Tove</to>
|
||||
<from>Jani</from>
|
||||
<heading>Reminder</heading>
|
||||
<body>Don't forget me this weekend!</body>
|
||||
</note>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"note": {
|
||||
"to": "Tove",
|
||||
"from": "Jani",
|
||||
"heading": "Reminder",
|
||||
"body": "Don't forget me this weekend!"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<body>
|
||||
|
||||
<h2 title="I'm a header">The title Attribute</h2>
|
||||
|
||||
<p title="I'm a tooltip">Mouse over this paragraph, to display the title attribute as a tooltip.</p>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
## The title Attribute
|
||||
|
||||
Mouse over this paragraph, to display the title attribute as a tooltip.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"properties":{"IsAdvancedAIEnabled":{"value":false},"ShowCustomPreview":{"value":true},"CloseAfterLosingFocus":{"value":false},"advanced-paste-ui-hotkey":{"win":true,"ctrl":false,"alt":false,"shift":true,"code":86,"key":""},"paste-as-plain-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":79,"key":""},"paste-as-markdown-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":77,"key":""},"paste-as-json-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":74,"key":""},"custom-actions":{"value":[]},"additional-actions":{"image-to-text":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-file":{"isShown":true,"paste-as-txt-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-png-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-html-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true}},"transcode":{"isShown":true,"transcode-to-mp3":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"transcode-to-mp4":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true}}}},"name":"AdvancedPaste","version":"1"}
|
||||
@@ -0,0 +1,41 @@
|
||||
## [Advanced Paste](tests-checklist-template-advanced-paste-section.md)
|
||||
NOTES:
|
||||
When using Advanced Paste, make sure that window focused while starting/using Advanced paste is text editor or has text input field focused (e.g. Word).
|
||||
* Paste As Plain Text
|
||||
- [x] Copy some rich text (e.g word of the text is different color, another work is bold, underlined, etd.).
|
||||
- [x] Paste the text using standard Windows Ctrl + V shortcut and ensure that rich text is pasted (with all colors, formatting, etc.)
|
||||
- [x] Paste the text using Paste As Plain Text activation shortcut and ensure that plain text without any formatting is pasted.
|
||||
- [x] Paste again the text using standard Windows Ctrl + V shortcut and ensure the text is now pasted plain without formatting as well.
|
||||
- [x] Copy some rich text again.
|
||||
- [x] Open Advanced Paste window using hotkey, click Paste as Plain Text button and confirm that plain text without any formatting is pasted.
|
||||
- [x] Copy some rich text again.
|
||||
- [x] Open Advanced Paste window using hotkey, press Ctrl + 1 and confirm that plain text without any formatting is pasted.
|
||||
* Paste As Markdown
|
||||
- [] Open Settings and set Paste as Markdown directly hotkey
|
||||
- [x] Copy some text (e.g. some HTML text - convertible to Markdown)
|
||||
- [x] Paste the text using set hotkey and confirm that pasted text is converted to markdown
|
||||
- [x] Copy some text (same as in the previous step or different. If nothing is coppied between steps, previously pasted Markdown text will be picked up from clipboard and converted again to nested Markdown).
|
||||
- [x] Open Advanced Paste window using hotkey, click Paste as markdown button and confirm that pasted text is converted to markdown
|
||||
- [x] Copy some text (same as in the previous step or different. If nothing is coppied between steps, previously pasted Markdown text will be picked up from clipboard and converted again to nested Markdown).
|
||||
- [x] Open Advanced Paste window using hotkey, press Ctrl + 2 and confirm that pasted text is converted to markdown
|
||||
* Paste As JSON
|
||||
- [] Open Settings and set Paste as JSON directly hotkey
|
||||
- [x] Copy some XML or CSV text (or any other text, it will be converted to simple JSON object)
|
||||
- [x] Paste the text using set hotkey and confirm that pasted text is converted to JSON
|
||||
- [x] Copy some text (same as in the previous step or different. If nothing is coppied between steps, previously pasted JSON text will be picked up from clipboard and converted again to nested JSON).
|
||||
- [x] Open Advanced Paste window using hotkey, click Paste as markdown button and confirm that pasted text is converted to markdown
|
||||
- [x] Copy some text (same as in the previous step or different. If nothing is coppied between steps, previously pasted JSON text will be picked up from clipboard and converted again to nested JSON).
|
||||
- [x] Open Advanced Paste window using hotkey, press Ctrl + 3 and confirm that pasted text is converted to markdown
|
||||
* Paste as custom format using AI
|
||||
- [] Open Settings, navigate to Enable Paste with AI and set OpenAI key.
|
||||
- [] Copy some text to clipboard. Any text.
|
||||
- [] Open Advanced Paste window using hotkey, and confirm that Custom intput text box is now enabled. Write "Insert smiley after every word" and press Enter. Observe that result preview shows coppied text with smileys between words. Press Enter to paste the result and observe that it is pasted.
|
||||
- [] Open Advanced Paste window using hotkey. Input some query (any, feel free to play around) and press Enter. When result is shown, click regenerate button, to see if new result is generated. Select one of the results and paste. Observe that correct result is pasted.
|
||||
- [] Create few custom actions. Set up hotkey for custom actions and confirm they work. Enable/disable custom actions and confirm that the change is reflected in Advanced Paste UI - custom action is not listed. Try different ctrl + <num> in-app shortcuts for custom actions. Try moving custom actions up/down and confirm that the change is reflected in Advanced Paste UI.
|
||||
- [] Open Settings and disable Custom format preview. Open Advanced Paste window with hotkey, enter some query and press enter. Observe that result is now pasted right away, without showing the preview first.
|
||||
- [] Open Settings and Disable Enable Paste with AI. Open Advanced Paste window with hotkey and observe that Custom Input text box is now disabled.
|
||||
* Clipboard History
|
||||
- [] Open Settings and Enable clipboard history (if not enabled already). Open Advanced Paste window with hotkey, click Clipboard history and try deleting some entry. Check OS clipboard history (Win+V), and confirm that the same entry no longer exist.
|
||||
- [] Open Advanced Paste window with hotkey, click Clipboard history, and click any entry (but first). Observe that entry is put on top of clipboard history. Check OS clipboard history (Win+V), and confirm that the same entry is on top of the clipboard.
|
||||
- [] Open Settings and Disable clipboard history. Open Advanced Paste window with hotkey and observe that Clipboard history button is disabled.
|
||||
* Disable Advanced Paste, try different Advanced Paste hotkeys and confirm that it's disabled and nothing happens.
|
||||
@@ -9,11 +9,11 @@ using Windows.AI.Actions.Hosting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Commands;
|
||||
|
||||
internal sealed partial class ExecuteActionCommand : InvokableCommand
|
||||
public sealed partial class ExecuteActionCommand : InvokableCommand
|
||||
{
|
||||
private readonly ActionInstance actionInstance;
|
||||
|
||||
internal ExecuteActionCommand(ActionInstance actionInstance)
|
||||
public ExecuteActionCommand(ActionInstance actionInstance)
|
||||
{
|
||||
this.actionInstance = actionInstance;
|
||||
this.Name = actionInstance.DisplayInfo.Description;
|
||||
@@ -6,28 +6,29 @@ using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CmdPal.Common.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Commands;
|
||||
namespace Microsoft.CmdPal.Common.Commands;
|
||||
|
||||
internal sealed partial class OpenInConsoleCommand : InvokableCommand
|
||||
public partial class OpenInConsoleCommand : InvokableCommand
|
||||
{
|
||||
private readonly IndexerItem _item;
|
||||
internal static IconInfo OpenInConsoleIcon { get; } = new("\uE756");
|
||||
|
||||
internal OpenInConsoleCommand(IndexerItem item)
|
||||
private readonly string _path;
|
||||
|
||||
public OpenInConsoleCommand(string fullPath)
|
||||
{
|
||||
this._item = item;
|
||||
this._path = fullPath;
|
||||
this.Name = Resources.Indexer_Command_OpenPathInConsole;
|
||||
this.Icon = new IconInfo("\uE756");
|
||||
this.Icon = OpenInConsoleIcon;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(_item.FullPath);
|
||||
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(_path);
|
||||
process.StartInfo.FileName = "cmd.exe";
|
||||
|
||||
try
|
||||
@@ -36,10 +37,10 @@ internal sealed partial class OpenInConsoleCommand : InvokableCommand
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
Logger.LogError($"Unable to open {_item.FullPath}", ex);
|
||||
Logger.LogError($"Unable to open '{_path}'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return CommandResult.GoHome();
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,17 @@ using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using ManagedCommon;
|
||||
using ManagedCsWin32;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Indexer.Utils;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CmdPal.Common.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Commands;
|
||||
namespace Microsoft.CmdPal.Common.Commands;
|
||||
|
||||
internal sealed partial class OpenPropertiesCommand : InvokableCommand
|
||||
public partial class OpenPropertiesCommand : InvokableCommand
|
||||
{
|
||||
private readonly IndexerItem _item;
|
||||
internal static IconInfo OpenPropertiesIcon { get; } = new("\uE90F");
|
||||
|
||||
private readonly string _path;
|
||||
|
||||
private static unsafe bool ShowFileProperties(string filename)
|
||||
{
|
||||
@@ -31,7 +31,7 @@ internal sealed partial class OpenPropertiesCommand : InvokableCommand
|
||||
LpVerb = propertiesPtr,
|
||||
LpFile = filenamePtr,
|
||||
Show = (int)SHOW_WINDOW_CMD.SW_SHOW,
|
||||
FMask = NativeHelpers.SEEMASKINVOKEIDLIST,
|
||||
FMask = global::Windows.Win32.PInvoke.SEE_MASK_INVOKEIDLIST,
|
||||
};
|
||||
|
||||
return Shell32.ShellExecuteEx(ref info);
|
||||
@@ -43,24 +43,24 @@ internal sealed partial class OpenPropertiesCommand : InvokableCommand
|
||||
}
|
||||
}
|
||||
|
||||
internal OpenPropertiesCommand(IndexerItem item)
|
||||
public OpenPropertiesCommand(string fullPath)
|
||||
{
|
||||
this._item = item;
|
||||
this._path = fullPath;
|
||||
this.Name = Resources.Indexer_Command_OpenProperties;
|
||||
this.Icon = new IconInfo("\uE90F");
|
||||
this.Icon = OpenPropertiesIcon;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
try
|
||||
{
|
||||
ShowFileProperties(_item.FullPath);
|
||||
ShowFileProperties(_path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error showing file properties: ", ex);
|
||||
}
|
||||
|
||||
return CommandResult.GoHome();
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,17 @@
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using ManagedCsWin32;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Indexer.Utils;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CmdPal.Common.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Commands;
|
||||
namespace Microsoft.CmdPal.Common.Commands;
|
||||
|
||||
internal sealed partial class OpenWithCommand : InvokableCommand
|
||||
public partial class OpenWithCommand : InvokableCommand
|
||||
{
|
||||
private readonly IndexerItem _item;
|
||||
internal static IconInfo OpenWithIcon { get; } = new("\uE7AC");
|
||||
|
||||
private readonly string _path;
|
||||
|
||||
private static unsafe bool OpenWith(string filename)
|
||||
{
|
||||
@@ -29,7 +29,7 @@ internal sealed partial class OpenWithCommand : InvokableCommand
|
||||
LpVerb = verbPtr,
|
||||
LpFile = filenamePtr,
|
||||
Show = (int)SHOW_WINDOW_CMD.SW_SHOWNORMAL,
|
||||
FMask = NativeHelpers.SEEMASKINVOKEIDLIST,
|
||||
FMask = global::Windows.Win32.PInvoke.SEE_MASK_INVOKEIDLIST,
|
||||
};
|
||||
|
||||
return Shell32.ShellExecuteEx(ref info);
|
||||
@@ -41,16 +41,16 @@ internal sealed partial class OpenWithCommand : InvokableCommand
|
||||
}
|
||||
}
|
||||
|
||||
internal OpenWithCommand(IndexerItem item)
|
||||
public OpenWithCommand(string fullPath)
|
||||
{
|
||||
this._item = item;
|
||||
this._path = fullPath;
|
||||
this.Name = Resources.Indexer_Command_OpenWith;
|
||||
this.Icon = new IconInfo("\uE7AC");
|
||||
this.Icon = OpenWithIcon;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
OpenWith(_item.FullPath);
|
||||
OpenWith(_path);
|
||||
|
||||
return CommandResult.GoHome();
|
||||
}
|
||||
@@ -28,7 +28,24 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
|
||||
<ProjectReference Include="..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,3 +9,7 @@ GetWindowRect
|
||||
GetMonitorInfo
|
||||
SetWindowPos
|
||||
MonitorFromWindow
|
||||
|
||||
SHOW_WINDOW_CMD
|
||||
ShellExecuteEx
|
||||
SEE_MASK_INVOKEIDLIST
|
||||
99
src/modules/cmdpal/Microsoft.CmdPal.Common/Properties/Resources.Designer.cs
generated
Normal file
99
src/modules/cmdpal/Microsoft.CmdPal.Common/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Common.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open path in console.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_OpenPathInConsole {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_OpenPathInConsole", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Properties.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_OpenProperties {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_OpenProperties", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open with.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_OpenWith {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_OpenWith", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show in folder.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_ShowInFolder {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_ShowInFolder", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Indexer_Command_OpenPathInConsole" xml:space="preserve">
|
||||
<value>Open path in console</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_OpenProperties" xml:space="preserve">
|
||||
<value>Properties</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_OpenWith" xml:space="preserve">
|
||||
<value>Open with</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_ShowInFolder" xml:space="preserve">
|
||||
<value>Show in folder</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Services;
|
||||
|
||||
public interface IRunHistoryService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the run history.
|
||||
/// </summary>
|
||||
/// <returns>A list of run history items.</returns>
|
||||
IReadOnlyList<string> GetRunHistory();
|
||||
|
||||
/// <summary>
|
||||
/// Clears the run history.
|
||||
/// </summary>
|
||||
void ClearRunHistory();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a run history item.
|
||||
/// </summary>
|
||||
/// <param name="item">The run history item to add.</param>
|
||||
void AddRunHistoryItem(string item);
|
||||
}
|
||||
@@ -21,8 +21,12 @@ public partial class AppStateModel : ObservableObject
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// STATE HERE
|
||||
// Make sure that you make the setters public (JsonSerializer.Deserialize will fail silently otherwise)!
|
||||
// Make sure that any new types you add are added to JsonSerializationContext!
|
||||
public RecentCommandsManager RecentCommands { get; set; } = new();
|
||||
|
||||
public List<string> RunHistory { get; set; } = [];
|
||||
|
||||
// END SETTINGS
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -86,7 +90,7 @@ public partial class AppStateModel : ObservableObject
|
||||
{
|
||||
foreach (var item in newSettings)
|
||||
{
|
||||
savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null;
|
||||
savedSettings[item.Key] = item.Value?.DeepClone();
|
||||
}
|
||||
|
||||
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel.Options);
|
||||
@@ -121,20 +125,4 @@ public partial class AppStateModel : ObservableObject
|
||||
// now, the settings is just next to the exe
|
||||
return Path.Combine(directory, "state.json");
|
||||
}
|
||||
|
||||
// [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
|
||||
// private static readonly JsonSerializerOptions _serializerOptions = new()
|
||||
// {
|
||||
// WriteIndented = true,
|
||||
// Converters = { new JsonStringEnumConverter() },
|
||||
// };
|
||||
|
||||
// private static readonly JsonSerializerOptions _deserializerOptions = new()
|
||||
// {
|
||||
// PropertyNameCaseInsensitive = true,
|
||||
// IncludeFields = true,
|
||||
// AllowTrailingCommas = true,
|
||||
// PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate,
|
||||
// ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
// };
|
||||
}
|
||||
|
||||
@@ -207,7 +207,10 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
clone.InsertRange(startIndex, newItems);
|
||||
|
||||
// now update the actual observable list with the new contents
|
||||
ListHelpers.InPlaceUpdateList(TopLevelCommands, clone);
|
||||
lock (TopLevelCommands)
|
||||
{
|
||||
ListHelpers.InPlaceUpdateList(TopLevelCommands, clone);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ReloadAllCommandsAsync()
|
||||
|
||||
@@ -63,9 +63,13 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
|
||||
{
|
||||
return item as IContextItem;
|
||||
}
|
||||
else if (item is CommandContextItemViewModel commandItem)
|
||||
{
|
||||
return commandItem.Model.Unsafe;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((CommandContextItemViewModel)item).Model.Unsafe;
|
||||
return null;
|
||||
}
|
||||
}).ToArray();
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ public partial class App : Application
|
||||
var files = new IndexerCommandsProvider();
|
||||
files.SuppressFallbackWhen(ShellCommandsProvider.SuppressFileFallbackIf);
|
||||
services.AddSingleton<ICommandProvider>(allApps);
|
||||
|
||||
services.AddSingleton<ICommandProvider, ShellCommandsProvider>();
|
||||
services.AddSingleton<ICommandProvider, CalculatorCommandProvider>();
|
||||
services.AddSingleton<ICommandProvider>(files);
|
||||
@@ -146,6 +147,7 @@ public partial class App : Application
|
||||
services.AddSingleton(state);
|
||||
services.AddSingleton<IExtensionService, ExtensionService>();
|
||||
services.AddSingleton<TrayIconService>();
|
||||
services.AddSingleton<IRunHistoryService, RunHistoryService>();
|
||||
|
||||
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
|
||||
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
|
||||
|
||||
@@ -336,8 +336,6 @@ public sealed partial class SearchBar : UserControl,
|
||||
// ... Move the cursor to the end of the input
|
||||
FilterBox.Select(FilterBox.Text.Length, 0);
|
||||
}
|
||||
|
||||
// TODO! deal with suggestion
|
||||
}
|
||||
else if (property == nameof(ListViewModel.InitialSearchText))
|
||||
{
|
||||
|
||||
@@ -33,9 +33,14 @@ internal sealed partial class ContextItemTemplateSelector : DataTemplateSelector
|
||||
li.AllowFocusOnInteraction = false;
|
||||
dataTemplate = Separator;
|
||||
}
|
||||
else if (item is CommandContextItemViewModel commandItem)
|
||||
{
|
||||
dataTemplate = commandItem.IsCritical ? Critical : Default;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataTemplate = ((CommandContextItemViewModel)item).IsCritical ? Critical : Default;
|
||||
// Fallback for unknown types
|
||||
dataTemplate = Default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
50
src/modules/cmdpal/Microsoft.CmdPal.UI/RunHistoryService.cs
Normal file
50
src/modules/cmdpal/Microsoft.CmdPal.UI/RunHistoryService.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
internal sealed class RunHistoryService : IRunHistoryService
|
||||
{
|
||||
private readonly AppStateModel _appStateModel;
|
||||
|
||||
public RunHistoryService(AppStateModel appStateModel)
|
||||
{
|
||||
_appStateModel = appStateModel;
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> GetRunHistory()
|
||||
{
|
||||
if (_appStateModel.RunHistory.Count == 0)
|
||||
{
|
||||
var history = Microsoft.Terminal.UI.RunHistory.CreateRunHistory();
|
||||
_appStateModel.RunHistory.AddRange(history);
|
||||
}
|
||||
|
||||
return _appStateModel.RunHistory;
|
||||
}
|
||||
|
||||
public void ClearRunHistory()
|
||||
{
|
||||
_appStateModel.RunHistory.Clear();
|
||||
}
|
||||
|
||||
public void AddRunHistoryItem(string item)
|
||||
{
|
||||
// insert at the beginning of the list
|
||||
if (string.IsNullOrWhiteSpace(item))
|
||||
{
|
||||
return; // Do not add empty or whitespace items
|
||||
}
|
||||
|
||||
_appStateModel.RunHistory.Remove(item);
|
||||
|
||||
// Add the item to the front of the history
|
||||
_appStateModel.RunHistory.Insert(0, item);
|
||||
|
||||
AppStateModel.SaveState(_appStateModel);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,22 @@
|
||||
<Application>
|
||||
<Assembly Name="Microsoft.WinUI">
|
||||
<Type Name="Microsoft.UI.Xaml.Controls.FontIconSource" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.UI.Xaml.DataTemplate" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.UI.Xaml.Controls.DataTemplateSelector" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.UI.Xaml.Controls.ListViewItem" Dynamic="Required All" />
|
||||
</Assembly>
|
||||
|
||||
<!-- Add ViewModel types for AOT compatibility -->
|
||||
<Assembly Name="Microsoft.CmdPal.Core.ViewModels">
|
||||
<Type Name="Microsoft.CmdPal.Core.ViewModels.CommandContextItemViewModel" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.CmdPal.Core.ViewModels.SeparatorContextItemViewModel" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.CmdPal.Core.ViewModels.IContextItemViewModel" Dynamic="Required All" />
|
||||
<Type Name="Microsoft.CmdPal.Core.ViewModels.CommandItemViewModel" Dynamic="Required All" />
|
||||
</Assembly>
|
||||
|
||||
<!-- Add UI types for AOT compatibility -->
|
||||
<Assembly Name="Microsoft.CmdPal.UI">
|
||||
<Type Name="Microsoft.CmdPal.UI.ContextItemTemplateSelector" Dynamic="Required All" />
|
||||
</Assembly>
|
||||
<Assembly Name="Microsoft.CmdPal.UI">
|
||||
<Type Name="Microsoft.CmdPal.UI.ContextItemTemplateSelector" Dynamic="Required All" />
|
||||
|
||||
@@ -383,4 +383,5 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
icon.Height(targetSize);
|
||||
return icon;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
static Microsoft::UI::Xaml::Controls::IconSource IconSourceMUX(const winrt::hstring& iconPath, bool convertToGrayscale, const int targetSize=24);
|
||||
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath);
|
||||
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath, const int targetSize);
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,9 @@
|
||||
<ClInclude Include="IconPathConverter.h">
|
||||
<DependentUpon>IconPathConverter.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="RunHistory.h">
|
||||
<DependentUpon>RunHistory.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ResourceString.h">
|
||||
<DependentUpon>ResourceString.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -168,6 +171,9 @@
|
||||
<ClCompile Include="IconPathConverter.cpp">
|
||||
<DependentUpon>IconPathConverter.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RunHistory.cpp">
|
||||
<DependentUpon>RunHistory.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ResourceString.cpp">
|
||||
<DependentUpon>ResourceString.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -176,6 +182,7 @@
|
||||
<ItemGroup>
|
||||
<Midl Include="Converters.idl" />
|
||||
<Midl Include="IconPathConverter.idl" />
|
||||
<Midl Include="RunHistory.idl" />
|
||||
<Midl Include="IDirectKeyListener.idl" />
|
||||
<Midl Include="ResourceString.idl" />
|
||||
</ItemGroup>
|
||||
|
||||
87
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.cpp
Normal file
87
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "pch.h"
|
||||
#include "RunHistory.h"
|
||||
#include "RunHistory.g.cpp"
|
||||
|
||||
|
||||
using namespace winrt::Windows;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
{
|
||||
// Run history
|
||||
// Largely copied from the Run work circa 2022.
|
||||
|
||||
winrt::Windows::Foundation::Collections::IVector<hstring> RunHistory::CreateRunHistory()
|
||||
{
|
||||
// Load MRU history
|
||||
std::vector<hstring> history;
|
||||
|
||||
wil::unique_hmodule _comctl;
|
||||
HANDLE(WINAPI* _createMRUList)(MRUINFO* lpmi);
|
||||
int(WINAPI* _enumMRUList)(HANDLE hMRU,int nItem,void* lpData,UINT uLen);
|
||||
void(WINAPI *_freeMRUList)(HANDLE hMRU);
|
||||
int(WINAPI *_addMRUString)(HANDLE hMRU, LPCWSTR szString);
|
||||
|
||||
// Lazy load comctl32.dll
|
||||
// Theoretically, we could cache this into a magic static, but we shouldn't need to actually do this more than once in CmdPal
|
||||
_comctl.reset(LoadLibraryExW(L"comctl32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32));
|
||||
|
||||
_createMRUList = reinterpret_cast<decltype(_createMRUList)>(GetProcAddress(_comctl.get(), "CreateMRUListW"));
|
||||
FAIL_FAST_LAST_ERROR_IF(!_createMRUList);
|
||||
|
||||
_enumMRUList = reinterpret_cast<decltype(_enumMRUList)>(GetProcAddress(_comctl.get(), "EnumMRUListW"));
|
||||
FAIL_FAST_LAST_ERROR_IF(!_enumMRUList);
|
||||
|
||||
_freeMRUList = reinterpret_cast<decltype(_freeMRUList)>(GetProcAddress(_comctl.get(), "FreeMRUList"));
|
||||
FAIL_FAST_LAST_ERROR_IF(!_freeMRUList);
|
||||
|
||||
_addMRUString = reinterpret_cast<decltype(_addMRUString)>(GetProcAddress(_comctl.get(), "AddMRUStringW"));
|
||||
FAIL_FAST_LAST_ERROR_IF(!_addMRUString);
|
||||
|
||||
static const WCHAR c_szRunMRU[] = REGSTR_PATH_EXPLORER L"\\RunMRU";
|
||||
MRUINFO mi = {
|
||||
sizeof(mi),
|
||||
26,
|
||||
MRU_CACHEWRITE,
|
||||
HKEY_CURRENT_USER,
|
||||
c_szRunMRU,
|
||||
NULL // NOTE: use default string compare
|
||||
// since this is a GLOBAL MRU
|
||||
};
|
||||
|
||||
if (const auto hMruList = _createMRUList(&mi))
|
||||
{
|
||||
auto freeMRUList = wil::scope_exit([=]() {
|
||||
_freeMRUList(hMruList);
|
||||
});
|
||||
|
||||
for (int nMax = _enumMRUList(hMruList, -1, NULL, 0), i = 0; i < nMax; ++i)
|
||||
{
|
||||
WCHAR szCommand[MAX_PATH + 2];
|
||||
|
||||
const auto length = _enumMRUList(hMruList, i, szCommand, ARRAYSIZE(szCommand));
|
||||
if (length > 1)
|
||||
{
|
||||
// clip off the null-terminator
|
||||
std::wstring_view text{ szCommand, wil::safe_cast<size_t>(length - 1) };
|
||||
//#pragma disable warning(C26493)
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 26493 )
|
||||
if (text.back() == L'\\')
|
||||
{
|
||||
// old MRU format has a slash at the end with the show cmd
|
||||
text = { szCommand, wil::safe_cast<size_t>(length - 2) };
|
||||
#pragma warning( pop )
|
||||
if (text.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
history.emplace_back(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update dropdown & initial value
|
||||
return winrt::single_threaded_observable_vector<winrt::hstring>(std::move(history));
|
||||
}
|
||||
}
|
||||
21
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.h
Normal file
21
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "RunHistory.g.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
{
|
||||
struct RunHistory
|
||||
{
|
||||
RunHistory() = default;
|
||||
static winrt::Windows::Foundation::Collections::IVector<hstring> CreateRunHistory();
|
||||
|
||||
private:
|
||||
winrt::Windows::Foundation::Collections::IVector<hstring> _mruHistory;
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::UI::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(RunHistory);
|
||||
}
|
||||
11
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.idl
Normal file
11
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.idl
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.UI
|
||||
{
|
||||
static runtimeclass RunHistory
|
||||
{
|
||||
static Windows.Foundation.Collections.IVector<String> CreateRunHistory();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -64,6 +64,8 @@
|
||||
|
||||
// WIL
|
||||
#include <wil/com.h>
|
||||
#include <wil/resource.h>
|
||||
#include <wil/safecast.h>
|
||||
#include <wil/stl.h>
|
||||
#include <wil/filesystem.h>
|
||||
// Due to the use of RESOURCE_SUPPRESS_STL in result.h, we need to include resource.h first, which happens
|
||||
@@ -90,6 +92,7 @@
|
||||
|
||||
#include <winrt/Windows.ApplicationModel.Resources.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
|
||||
#include <winrt/Windows.Graphics.Imaging.h>
|
||||
#include <Windows.Graphics.Imaging.Interop.h>
|
||||
|
||||
23
src/modules/cmdpal/Microsoft.Terminal.UI/types.h
Normal file
23
src/modules/cmdpal/Microsoft.Terminal.UI/types.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MRU_CACHEWRITE 0x0002
|
||||
#define REGSTR_PATH_EXPLORER TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer")
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/shell/mrucmpproc
|
||||
typedef int(CALLBACK* MRUCMPPROC)(
|
||||
LPCTSTR pString1,
|
||||
LPCTSTR pString2);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/shell/mruinfo
|
||||
struct MRUINFO
|
||||
{
|
||||
DWORD cbSize;
|
||||
UINT uMax;
|
||||
UINT fFlags;
|
||||
HKEY hKey;
|
||||
LPCTSTR lpszSubKey;
|
||||
MRUCMPPROC lpfnCompare;
|
||||
};
|
||||
@@ -10,7 +10,6 @@ using System.Xml;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.State;
|
||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -96,7 +95,7 @@ public class UWPApplication : IProgram
|
||||
|
||||
commands.Add(
|
||||
new CommandContextItem(
|
||||
new CopyPathCommand(Location))
|
||||
new Commands.CopyPathCommand(Location))
|
||||
{
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C),
|
||||
});
|
||||
|
||||
@@ -5,21 +5,15 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.State;
|
||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -190,7 +184,7 @@ public class Win32Program : IProgram
|
||||
|
||||
public List<IContextItem> GetCommands()
|
||||
{
|
||||
List<IContextItem> commands = new List<IContextItem>();
|
||||
List<IContextItem> commands = [];
|
||||
|
||||
if (AppType != ApplicationType.InternetShortcutApplication && AppType != ApplicationType.Folder && AppType != ApplicationType.GenericFile)
|
||||
{
|
||||
@@ -208,7 +202,7 @@ public class Win32Program : IProgram
|
||||
}
|
||||
|
||||
commands.Add(new CommandContextItem(
|
||||
new CopyPathCommand(FullPath))
|
||||
new Commands.CopyPathCommand(FullPath))
|
||||
{
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C),
|
||||
});
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.CmdPal.Ext.Bookmarks.Properties;
|
||||
@@ -75,31 +73,9 @@ internal sealed partial class AddBookmarkForm : FormContent
|
||||
var formBookmark = formInput["bookmark"] ?? string.Empty;
|
||||
var hasPlaceholder = formBookmark.ToString().Contains('{') && formBookmark.ToString().Contains('}');
|
||||
|
||||
// Determine the type of the bookmark
|
||||
string bookmarkType;
|
||||
|
||||
if (formBookmark.ToString().StartsWith("http://", StringComparison.OrdinalIgnoreCase) || formBookmark.ToString().StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
bookmarkType = "web";
|
||||
}
|
||||
else if (File.Exists(formBookmark.ToString()))
|
||||
{
|
||||
bookmarkType = "file";
|
||||
}
|
||||
else if (Directory.Exists(formBookmark.ToString()))
|
||||
{
|
||||
bookmarkType = "folder";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to web if we can't determine the type
|
||||
bookmarkType = "web";
|
||||
}
|
||||
|
||||
var updated = _bookmark ?? new BookmarkData();
|
||||
updated.Name = formName.ToString();
|
||||
updated.Bookmark = formBookmark.ToString();
|
||||
updated.Type = bookmarkType;
|
||||
|
||||
AddedCommand?.Invoke(this, updated);
|
||||
return CommandResult.GoHome();
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
@@ -12,8 +14,38 @@ public class BookmarkData
|
||||
|
||||
public string Bookmark { get; set; } = string.Empty;
|
||||
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
// public string Type { get; set; } = string.Empty;
|
||||
[JsonIgnore]
|
||||
public bool IsPlaceholder => Bookmark.Contains('{') && Bookmark.Contains('}');
|
||||
|
||||
internal void GetExeAndArgs(out string exe, out string args)
|
||||
{
|
||||
ShellHelpers.ParseExecutableAndArgs(Bookmark, out exe, out args);
|
||||
}
|
||||
|
||||
internal bool IsWebUrl()
|
||||
{
|
||||
GetExeAndArgs(out var exe, out var args);
|
||||
if (string.IsNullOrEmpty(exe))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Uri.TryCreate(exe, UriKind.Absolute, out var uri))
|
||||
{
|
||||
if (uri.Scheme == Uri.UriSchemeFile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// return true if the scheme is http or https, or if there's no scheme (e.g., "www.example.com") but there is a dot in the host
|
||||
return
|
||||
uri.Scheme == Uri.UriSchemeHttp ||
|
||||
uri.Scheme == Uri.UriSchemeHttps ||
|
||||
(string.IsNullOrEmpty(uri.Scheme) && uri.Host.Contains('.'));
|
||||
}
|
||||
|
||||
// If we can't parse it as a URI, we assume it's not a web URL
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,14 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.RegularExpressions;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Bookmarks.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
@@ -25,7 +22,7 @@ internal sealed partial class BookmarkPlaceholderForm : FormContent
|
||||
private readonly string _bookmark = string.Empty;
|
||||
|
||||
// TODO pass in an array of placeholders
|
||||
public BookmarkPlaceholderForm(string name, string url, string type)
|
||||
public BookmarkPlaceholderForm(string name, string url)
|
||||
{
|
||||
_bookmark = url;
|
||||
var r = new Regex(Regex.Escape("{") + "(.*?)" + Regex.Escape("}"));
|
||||
@@ -88,23 +85,8 @@ internal sealed partial class BookmarkPlaceholderForm : FormContent
|
||||
target = target.Replace(placeholderString, placeholderData);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var uri = UrlCommand.GetUri(target);
|
||||
if (uri != null)
|
||||
{
|
||||
_ = Launcher.LaunchUriAsync(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw new UriFormatException("The provided URL is not valid.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex.Message);
|
||||
}
|
||||
var success = UrlCommand.LaunchCommand(target);
|
||||
|
||||
return CommandResult.GoHome();
|
||||
return success ? CommandResult.Dismiss() : CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -9,19 +10,30 @@ namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
internal sealed partial class BookmarkPlaceholderPage : ContentPage
|
||||
{
|
||||
private readonly Lazy<IconInfo> _icon;
|
||||
private readonly FormContent _bookmarkPlaceholder;
|
||||
|
||||
public override IContent[] GetContent() => [_bookmarkPlaceholder];
|
||||
|
||||
public override IconInfo Icon { get => _icon.Value; set => base.Icon = value; }
|
||||
|
||||
public BookmarkPlaceholderPage(BookmarkData data)
|
||||
: this(data.Name, data.Bookmark, data.Type)
|
||||
: this(data.Name, data.Bookmark)
|
||||
{
|
||||
}
|
||||
|
||||
public BookmarkPlaceholderPage(string name, string url, string type)
|
||||
public BookmarkPlaceholderPage(string name, string url)
|
||||
{
|
||||
Name = name;
|
||||
Icon = new IconInfo(UrlCommand.IconFromUrl(url, type));
|
||||
_bookmarkPlaceholder = new BookmarkPlaceholderForm(name, url, type);
|
||||
Name = Properties.Resources.bookmarks_command_name_open;
|
||||
|
||||
_bookmarkPlaceholder = new BookmarkPlaceholderForm(name, url);
|
||||
|
||||
_icon = new Lazy<IconInfo>(() =>
|
||||
{
|
||||
ShellHelpers.ParseExecutableAndArgs(url, out var exe, out var args);
|
||||
var t = UrlCommand.GetIconForPath(exe);
|
||||
t.Wait();
|
||||
return t.Result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ManagedCommon;
|
||||
@@ -35,10 +34,7 @@ public partial class BookmarksCommandProvider : CommandProvider
|
||||
private void AddNewCommand_AddedCommand(object sender, BookmarkData args)
|
||||
{
|
||||
ExtensionHost.LogMessage($"Adding bookmark ({args.Name},{args.Bookmark})");
|
||||
if (_bookmarks != null)
|
||||
{
|
||||
_bookmarks.Data.Add(args);
|
||||
}
|
||||
_bookmarks?.Data.Add(args);
|
||||
|
||||
SaveAndUpdateCommands();
|
||||
}
|
||||
@@ -116,7 +112,7 @@ public partial class BookmarksCommandProvider : CommandProvider
|
||||
// Add commands for folder types
|
||||
if (command is UrlCommand urlCommand)
|
||||
{
|
||||
if (urlCommand.Type == "folder")
|
||||
if (!bookmark.IsWebUrl())
|
||||
{
|
||||
contextMenu.Add(
|
||||
new CommandContextItem(new DirectoryPage(urlCommand.Url)));
|
||||
@@ -124,10 +120,11 @@ public partial class BookmarksCommandProvider : CommandProvider
|
||||
contextMenu.Add(
|
||||
new CommandContextItem(new OpenInTerminalCommand(urlCommand.Url)));
|
||||
}
|
||||
|
||||
listItem.Subtitle = urlCommand.Url;
|
||||
}
|
||||
|
||||
listItem.Title = bookmark.Name;
|
||||
listItem.Subtitle = bookmark.Bookmark;
|
||||
|
||||
var edit = new AddBookmarkPage(bookmark) { Icon = Icons.EditIcon };
|
||||
edit.AddedCommand += Edit_AddedCommand;
|
||||
contextMenu.Add(new CommandContextItem(edit));
|
||||
|
||||
@@ -78,6 +78,15 @@ namespace Microsoft.CmdPal.Ext.Bookmarks.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open.
|
||||
/// </summary>
|
||||
public static string bookmarks_command_name_open {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_command_name_open", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Delete.
|
||||
/// </summary>
|
||||
|
||||
@@ -148,6 +148,9 @@
|
||||
<data name="bookmarks_form_open" xml:space="preserve">
|
||||
<value>Open</value>
|
||||
</data>
|
||||
<data name="bookmarks_command_name_open" xml:space="preserve">
|
||||
<value>Open</value>
|
||||
</data>
|
||||
<data name="bookmarks_form_name_required" xml:space="preserve">
|
||||
<value>Name is required</value>
|
||||
</data>
|
||||
|
||||
@@ -3,52 +3,89 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
public partial class UrlCommand : InvokableCommand
|
||||
{
|
||||
public string Type { get; }
|
||||
private readonly Lazy<IconInfo> _icon;
|
||||
|
||||
public string Url { get; }
|
||||
|
||||
public override IconInfo Icon { get => _icon.Value; set => base.Icon = value; }
|
||||
|
||||
public UrlCommand(BookmarkData data)
|
||||
: this(data.Name, data.Bookmark, data.Type)
|
||||
: this(data.Name, data.Bookmark)
|
||||
{
|
||||
}
|
||||
|
||||
public UrlCommand(string name, string url, string type)
|
||||
public UrlCommand(string name, string url)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
Name = Properties.Resources.bookmarks_command_name_open;
|
||||
|
||||
Url = url;
|
||||
Icon = new IconInfo(IconFromUrl(Url, type));
|
||||
|
||||
_icon = new Lazy<IconInfo>(() =>
|
||||
{
|
||||
ShellHelpers.ParseExecutableAndArgs(Url, out var exe, out var args);
|
||||
var t = GetIconForPath(exe);
|
||||
t.Wait();
|
||||
return t.Result;
|
||||
});
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
var target = Url;
|
||||
try
|
||||
var success = LaunchCommand(Url);
|
||||
|
||||
return success ? CommandResult.Dismiss() : CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
internal static bool LaunchCommand(string target)
|
||||
{
|
||||
ShellHelpers.ParseExecutableAndArgs(target, out var exe, out var args);
|
||||
return LaunchCommand(exe, args);
|
||||
}
|
||||
|
||||
internal static bool LaunchCommand(string exe, string args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(exe))
|
||||
{
|
||||
var uri = GetUri(target);
|
||||
var message = "No executable found in the command.";
|
||||
Logger.LogError(message);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ShellHelpers.OpenInShell(exe, args))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we reach here, it means the command could not be executed
|
||||
// If there aren't args, then try again as a https: uri
|
||||
if (string.IsNullOrEmpty(args))
|
||||
{
|
||||
var uri = GetUri(exe);
|
||||
if (uri != null)
|
||||
{
|
||||
_ = Launcher.LaunchUriAsync(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw new UriFormatException("The provided URL is not valid.");
|
||||
Logger.LogError("The provided URL is not valid.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex.Message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static Uri? GetUri(string url)
|
||||
@@ -65,35 +102,90 @@ public partial class UrlCommand : InvokableCommand
|
||||
return uri;
|
||||
}
|
||||
|
||||
internal static string IconFromUrl(string url, string type)
|
||||
public static async Task<IconInfo> GetIconForPath(string target)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case "file":
|
||||
return "📄";
|
||||
case "folder":
|
||||
return "📁";
|
||||
case "web":
|
||||
default:
|
||||
// Get the base url up to the first placeholder
|
||||
var placeholderIndex = url.IndexOf('{');
|
||||
var baseString = placeholderIndex > 0 ? url.Substring(0, placeholderIndex) : url;
|
||||
try
|
||||
{
|
||||
var uri = GetUri(baseString);
|
||||
if (uri != null)
|
||||
{
|
||||
var hostname = uri.Host;
|
||||
var faviconUrl = $"{uri.Scheme}://{hostname}/favicon.ico";
|
||||
return faviconUrl;
|
||||
}
|
||||
}
|
||||
catch (UriFormatException ex)
|
||||
{
|
||||
Logger.LogError(ex.Message);
|
||||
}
|
||||
IconInfo? icon = null;
|
||||
|
||||
return "🔗";
|
||||
// First, try to get the icon from the thumbnail helper
|
||||
// This works for local files and folders
|
||||
icon = await MaybeGetIconForPath(target);
|
||||
if (icon != null)
|
||||
{
|
||||
return icon;
|
||||
}
|
||||
|
||||
// Okay, that failed. Try to resolve the full path of the executable
|
||||
var exeExists = false;
|
||||
var fullExePath = string.Empty;
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(200));
|
||||
|
||||
// Use Task.Run with timeout - this will actually timeout even if the sync operations don't respond to cancellation
|
||||
var pathResolutionTask = Task.Run(
|
||||
() =>
|
||||
{
|
||||
// Don't check cancellation token here - let the Task timeout handle it
|
||||
exeExists = ShellHelpers.FileExistInPath(target, out fullExePath);
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
// Wait for either completion or timeout
|
||||
pathResolutionTask.Wait(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Debug.WriteLine("Operation was canceled.");
|
||||
}
|
||||
|
||||
if (exeExists)
|
||||
{
|
||||
// If the executable exists, try to get the icon from the file
|
||||
icon = await MaybeGetIconForPath(fullExePath);
|
||||
if (icon != null)
|
||||
{
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the base url up to the first placeholder
|
||||
var placeholderIndex = target.IndexOf('{');
|
||||
var baseString = placeholderIndex > 0 ? target.Substring(0, placeholderIndex) : target;
|
||||
try
|
||||
{
|
||||
var uri = GetUri(baseString);
|
||||
if (uri != null)
|
||||
{
|
||||
var hostname = uri.Host;
|
||||
var faviconUrl = $"{uri.Scheme}://{hostname}/favicon.ico";
|
||||
icon = new IconInfo(faviconUrl);
|
||||
}
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
}
|
||||
|
||||
// If we still don't have an icon, use the target as the icon
|
||||
icon = icon ?? new IconInfo(target);
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
private static async Task<IconInfo?> MaybeGetIconForPath(string target)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stream = await ThumbnailHelper.GetThumbnail(target);
|
||||
if (stream != null)
|
||||
{
|
||||
var data = new IconData(RandomAccessStreamReference.CreateFromStream(stream));
|
||||
return new IconInfo(data, data);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Commands;
|
||||
|
||||
internal sealed partial class CopyPathCommand : InvokableCommand
|
||||
{
|
||||
private readonly IndexerItem _item;
|
||||
|
||||
internal CopyPathCommand(IndexerItem item)
|
||||
{
|
||||
this._item = item;
|
||||
this.Name = Resources.Indexer_Command_CopyPath;
|
||||
this.Icon = new IconInfo("\uE8c8");
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
try
|
||||
{
|
||||
ClipboardHelper.SetText(_item.FullPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Commands;
|
||||
|
||||
internal sealed partial class OpenFileCommand : InvokableCommand
|
||||
{
|
||||
private readonly IndexerItem _item;
|
||||
|
||||
internal OpenFileCommand(IndexerItem item)
|
||||
{
|
||||
this._item = item;
|
||||
this.Name = Resources.Indexer_Command_OpenFile;
|
||||
this.Icon = Icons.OpenFileIcon;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo.FileName = _item.FullPath;
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
Logger.LogError($"Unable to open {_item.FullPath}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return CommandResult.GoHome();
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,16 @@ internal sealed class IndexerItem
|
||||
|
||||
internal string FileName { get; init; }
|
||||
|
||||
internal IndexerItem()
|
||||
{
|
||||
}
|
||||
|
||||
internal IndexerItem(string fullPath)
|
||||
{
|
||||
FullPath = fullPath;
|
||||
FileName = Path.GetFileName(fullPath);
|
||||
}
|
||||
|
||||
internal bool IsDirectory()
|
||||
{
|
||||
if (!Path.Exists(FullPath))
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Commands;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Common.Commands;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Pages;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation.Metadata;
|
||||
|
||||
@@ -28,51 +29,79 @@ internal sealed partial class IndexerListItem : ListItem
|
||||
public IndexerListItem(
|
||||
IndexerItem indexerItem,
|
||||
IncludeBrowseCommand browseByDefault = IncludeBrowseCommand.Include)
|
||||
: base(new OpenFileCommand(indexerItem))
|
||||
: base()
|
||||
{
|
||||
FilePath = indexerItem.FullPath;
|
||||
|
||||
Title = indexerItem.FileName;
|
||||
Subtitle = indexerItem.FullPath;
|
||||
List<CommandContextItem> context = [];
|
||||
if (indexerItem.IsDirectory())
|
||||
|
||||
var commands = FileCommands(indexerItem.FullPath, browseByDefault);
|
||||
if (commands.Any())
|
||||
{
|
||||
var directoryPage = new DirectoryPage(indexerItem.FullPath);
|
||||
Command = commands.First().Command;
|
||||
MoreCommands = commands.Skip(1).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<CommandContextItem> FileCommands(string fullPath)
|
||||
{
|
||||
return FileCommands(fullPath, IncludeBrowseCommand.Include);
|
||||
}
|
||||
|
||||
internal static IEnumerable<CommandContextItem> FileCommands(
|
||||
string fullPath,
|
||||
IncludeBrowseCommand browseByDefault = IncludeBrowseCommand.Include)
|
||||
{
|
||||
List<CommandContextItem> commands = [];
|
||||
if (!Path.Exists(fullPath))
|
||||
{
|
||||
return commands;
|
||||
}
|
||||
|
||||
// detect whether it is a directory or file
|
||||
var attr = File.GetAttributes(fullPath);
|
||||
var isDir = (attr & FileAttributes.Directory) == FileAttributes.Directory;
|
||||
|
||||
var openCommand = new OpenFileCommand(fullPath) { Name = Resources.Indexer_Command_OpenFile };
|
||||
if (isDir)
|
||||
{
|
||||
var directoryPage = new DirectoryPage(fullPath);
|
||||
if (browseByDefault == IncludeBrowseCommand.AsDefault)
|
||||
{
|
||||
// Swap the open file command into the context menu
|
||||
context.Add(new CommandContextItem(Command));
|
||||
Command = directoryPage;
|
||||
// AsDefault: browse dir first, then open in explorer
|
||||
commands.Add(new CommandContextItem(directoryPage));
|
||||
commands.Add(new CommandContextItem(openCommand));
|
||||
}
|
||||
else if (browseByDefault == IncludeBrowseCommand.Include)
|
||||
{
|
||||
context.Add(new CommandContextItem(directoryPage));
|
||||
// AsDefault: open in explorer first, then browse
|
||||
commands.Add(new CommandContextItem(openCommand));
|
||||
commands.Add(new CommandContextItem(directoryPage));
|
||||
}
|
||||
else if (browseByDefault == IncludeBrowseCommand.Exclude)
|
||||
{
|
||||
// AsDefault: Just open in explorer
|
||||
commands.Add(new CommandContextItem(openCommand));
|
||||
}
|
||||
}
|
||||
|
||||
IContextItem[] moreCommands = [
|
||||
..context,
|
||||
new CommandContextItem(new OpenWithCommand(indexerItem))];
|
||||
commands.Add(new CommandContextItem(new OpenWithCommand(fullPath)));
|
||||
commands.Add(new CommandContextItem(new ShowFileInFolderCommand(fullPath) { Name = Resources.Indexer_Command_ShowInFolder }));
|
||||
commands.Add(new CommandContextItem(new CopyPathCommand(fullPath) { Name = Resources.Indexer_Command_CopyPath }));
|
||||
commands.Add(new CommandContextItem(new OpenInConsoleCommand(fullPath)));
|
||||
commands.Add(new CommandContextItem(new OpenPropertiesCommand(fullPath)));
|
||||
|
||||
if (IsActionsFeatureEnabled && ApiInformation.IsApiContractPresent("Windows.AI.Actions.ActionsContract", 4))
|
||||
{
|
||||
var actionsListContextItem = new ActionsListContextItem(indexerItem.FullPath);
|
||||
var actionsListContextItem = new ActionsListContextItem(fullPath);
|
||||
if (actionsListContextItem.AnyActions())
|
||||
{
|
||||
moreCommands = [
|
||||
.. moreCommands,
|
||||
actionsListContextItem
|
||||
];
|
||||
commands.Add(actionsListContextItem);
|
||||
}
|
||||
}
|
||||
|
||||
MoreCommands = [
|
||||
.. moreCommands,
|
||||
new CommandContextItem(new ShowFileInFolderCommand(indexerItem.FullPath) { Name = Resources.Indexer_Command_ShowInFolder }),
|
||||
new CommandContextItem(new CopyPathCommand(indexerItem)),
|
||||
new CommandContextItem(new OpenInConsoleCommand(indexerItem)),
|
||||
new CommandContextItem(new OpenPropertiesCommand(indexerItem)),
|
||||
];
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
|
||||
if (Path.Exists(query))
|
||||
{
|
||||
// Exit 1: The query is a direct path to a file. Great! Return it.
|
||||
var item = new IndexerItem() { FullPath = query, FileName = Path.GetFileName(query) };
|
||||
var item = new IndexerItem(fullPath: query);
|
||||
var listItemForUs = new IndexerListItem(item, IncludeBrowseCommand.AsDefault);
|
||||
Command = listItemForUs.Command;
|
||||
MoreCommands = listItemForUs.MoreCommands;
|
||||
|
||||
@@ -10,14 +10,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
|
||||
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
|
||||
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Commands;
|
||||
using Microsoft.CmdPal.Common.Commands;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -41,16 +41,16 @@ internal sealed partial class ExploreListItem : ListItem
|
||||
}
|
||||
else
|
||||
{
|
||||
Command = new OpenFileCommand(indexerItem);
|
||||
Command = new OpenFileCommand(indexerItem.FullPath);
|
||||
}
|
||||
|
||||
MoreCommands = [
|
||||
..context,
|
||||
new CommandContextItem(new OpenWithCommand(indexerItem)),
|
||||
new CommandContextItem(new OpenWithCommand(indexerItem.FullPath)),
|
||||
new CommandContextItem(new ShowFileInFolderCommand(indexerItem.FullPath) { Name = Resources.Indexer_Command_ShowInFolder }),
|
||||
new CommandContextItem(new CopyPathCommand(indexerItem)),
|
||||
new CommandContextItem(new OpenInConsoleCommand(indexerItem)),
|
||||
new CommandContextItem(new OpenPropertiesCommand(indexerItem)),
|
||||
new CommandContextItem(new CopyPathCommand(indexerItem.FullPath)),
|
||||
new CommandContextItem(new OpenInConsoleCommand(indexerItem.FullPath)),
|
||||
new CommandContextItem(new OpenPropertiesCommand(indexerItem.FullPath)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,6 @@
|
||||
<ProjectPriFileName>Microsoft.CmdPal.Ext.Registry.pri</ProjectPriFileName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Microsoft.CmdPal.Ext.Registry.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.CmdPal.Ext.Registry.UnitTests")]
|
||||
@@ -15,10 +15,11 @@ namespace Microsoft.CmdPal.Ext.Shell;
|
||||
|
||||
internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDisposable
|
||||
{
|
||||
private readonly Action<string>? _addToHistory;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
private Task? _currentUpdateTask;
|
||||
|
||||
public FallbackExecuteItem(SettingsManager settings)
|
||||
public FallbackExecuteItem(SettingsManager settings, Action<string>? addToHistory)
|
||||
: base(
|
||||
new NoOpCommand() { Id = "com.microsoft.run.fallback" },
|
||||
Resources.shell_command_display_title)
|
||||
@@ -26,6 +27,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
||||
Title = string.Empty;
|
||||
Subtitle = Properties.Resources.generic_run_command;
|
||||
Icon = Icons.RunV2Icon; // Defined in Icons.cs and contains the execute command icon.
|
||||
_addToHistory = addToHistory;
|
||||
}
|
||||
|
||||
public override void UpdateQuery(string query)
|
||||
@@ -87,7 +89,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
||||
return;
|
||||
}
|
||||
|
||||
ShellListPage.ParseExecutableAndArgs(searchText, out var exe, out var args);
|
||||
ShellHelpers.ParseExecutableAndArgs(searchText, out var exe, out var args);
|
||||
|
||||
// Check for cancellation before file system operations
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -142,7 +144,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
||||
if (exeExists)
|
||||
{
|
||||
// TODO we need to probably get rid of the settings for this provider entirely
|
||||
var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath);
|
||||
var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath, _addToHistory);
|
||||
Title = exeItem.Title;
|
||||
Subtitle = exeItem.Subtitle;
|
||||
Icon = exeItem.Icon;
|
||||
@@ -151,7 +153,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
||||
}
|
||||
else if (pathIsDir)
|
||||
{
|
||||
var pathItem = new PathListItem(exe, query);
|
||||
var pathItem = new PathListItem(exe, query, _addToHistory);
|
||||
Title = pathItem.Title;
|
||||
Subtitle = pathItem.Subtitle;
|
||||
Icon = pathItem.Icon;
|
||||
@@ -160,7 +162,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
||||
}
|
||||
else if (System.Uri.TryCreate(searchText, UriKind.Absolute, out var uri))
|
||||
{
|
||||
Command = new OpenUrlCommand(searchText) { Result = CommandResult.Dismiss() };
|
||||
Command = new OpenUrlWithHistoryCommand(searchText, _addToHistory) { Result = CommandResult.Dismiss() };
|
||||
Title = searchText;
|
||||
}
|
||||
else
|
||||
@@ -189,7 +191,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
||||
return false;
|
||||
}
|
||||
|
||||
ShellListPage.ParseExecutableAndArgs(searchText, out var exe, out var args);
|
||||
ShellHelpers.ParseExecutableAndArgs(searchText, out var exe, out var args);
|
||||
var exeExists = ShellListPageHelpers.FileExistInPath(exe, out var fullExePath);
|
||||
var pathIsDir = Directory.Exists(exe);
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Shell.Commands;
|
||||
using Microsoft.CmdPal.Ext.Shell.Pages;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||
@@ -52,46 +54,83 @@ public class ShellListPageHelpers
|
||||
|
||||
internal static bool FileExistInPath(string filename, out string fullPath, CancellationToken? token = null)
|
||||
{
|
||||
fullPath = string.Empty;
|
||||
// TODO! remove this method and just use ShellHelpers.FileExistInPath directly
|
||||
return ShellHelpers.FileExistInPath(filename, out fullPath, token ?? CancellationToken.None);
|
||||
}
|
||||
|
||||
if (File.Exists(filename))
|
||||
internal static ListItem? ListItemForCommandString(string query, Action<string>? addToHistory)
|
||||
{
|
||||
var li = new ListItem();
|
||||
|
||||
var searchText = query.Trim();
|
||||
var expanded = Environment.ExpandEnvironmentVariables(searchText);
|
||||
searchText = expanded;
|
||||
if (string.IsNullOrEmpty(searchText) || string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
token?.ThrowIfCancellationRequested();
|
||||
fullPath = Path.GetFullPath(filename);
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
|
||||
ShellHelpers.ParseExecutableAndArgs(searchText, out var exe, out var args);
|
||||
|
||||
var exeExists = false;
|
||||
var pathIsDir = false;
|
||||
var fullExePath = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(200));
|
||||
|
||||
// Use Task.Run with timeout - this will actually timeout even if the sync operations don't respond to cancellation
|
||||
var pathResolutionTask = Task.Run(
|
||||
() =>
|
||||
{
|
||||
// Don't check cancellation token here - let the Task timeout handle it
|
||||
exeExists = ShellListPageHelpers.FileExistInPath(exe, out fullExePath);
|
||||
pathIsDir = Directory.Exists(expanded);
|
||||
},
|
||||
CancellationToken.None); // Use None here since we're handling timeout differently
|
||||
|
||||
// Wait for either completion or timeout
|
||||
pathResolutionTask.Wait(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
|
||||
if (exeExists)
|
||||
{
|
||||
// TODO we need to probably get rid of the settings for this provider entirely
|
||||
var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath, addToHistory);
|
||||
li.Command = exeItem.Command;
|
||||
li.Title = exeItem.Title;
|
||||
li.Subtitle = exeItem.Subtitle;
|
||||
li.Icon = exeItem.Icon;
|
||||
li.MoreCommands = exeItem.MoreCommands;
|
||||
}
|
||||
else if (pathIsDir)
|
||||
{
|
||||
var pathItem = new PathListItem(exe, query, addToHistory);
|
||||
li.Command = pathItem.Command;
|
||||
li.Title = pathItem.Title;
|
||||
li.Subtitle = pathItem.Subtitle;
|
||||
li.Icon = pathItem.Icon;
|
||||
li.MoreCommands = pathItem.MoreCommands;
|
||||
}
|
||||
else if (System.Uri.TryCreate(searchText, UriKind.Absolute, out var uri))
|
||||
{
|
||||
li.Command = new OpenUrlWithHistoryCommand(searchText) { Result = CommandResult.Dismiss() };
|
||||
li.Title = searchText;
|
||||
}
|
||||
else
|
||||
{
|
||||
var values = Environment.GetEnvironmentVariable("PATH");
|
||||
if (values != null)
|
||||
{
|
||||
foreach (var path in values.Split(';'))
|
||||
{
|
||||
var path1 = Path.Combine(path, filename);
|
||||
if (File.Exists(path1))
|
||||
{
|
||||
fullPath = Path.GetFullPath(path1);
|
||||
return true;
|
||||
}
|
||||
|
||||
token?.ThrowIfCancellationRequested();
|
||||
|
||||
var path2 = Path.Combine(path, filename + ".exe");
|
||||
if (File.Exists(path2))
|
||||
{
|
||||
fullPath = Path.GetFullPath(path2);
|
||||
return true;
|
||||
}
|
||||
|
||||
token?.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (li != null)
|
||||
{
|
||||
li.TextToSuggest = searchText;
|
||||
}
|
||||
|
||||
return li;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
<None Remove="Assets\Run.svg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Shell;
|
||||
|
||||
internal sealed partial class OpenUrlWithHistoryCommand : OpenUrlCommand
|
||||
{
|
||||
private readonly Action<string>? _addToHistory;
|
||||
private readonly string _url;
|
||||
|
||||
public OpenUrlWithHistoryCommand(string url, Action<string>? addToHistory = null)
|
||||
: base(url)
|
||||
{
|
||||
_addToHistory = addToHistory;
|
||||
_url = url;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
_addToHistory?.Invoke(_url);
|
||||
var result = base.Invoke();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,14 @@ using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Shell.Pages;
|
||||
|
||||
internal sealed partial class RunExeItem : ListItem
|
||||
{
|
||||
private readonly Lazy<IconInfo> _icon;
|
||||
private readonly Action<string>? _addToHistory;
|
||||
|
||||
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
|
||||
|
||||
@@ -22,7 +24,9 @@ internal sealed partial class RunExeItem : ListItem
|
||||
|
||||
private string _args = string.Empty;
|
||||
|
||||
public RunExeItem(string exe, string args, string fullExePath)
|
||||
private string FullString => string.IsNullOrEmpty(_args) ? Exe : $"{Exe} {_args}";
|
||||
|
||||
public RunExeItem(string exe, string args, string fullExePath, Action<string>? addToHistory)
|
||||
{
|
||||
FullExePath = fullExePath;
|
||||
Exe = exe;
|
||||
@@ -41,6 +45,8 @@ internal sealed partial class RunExeItem : ListItem
|
||||
return t.Result;
|
||||
});
|
||||
|
||||
_addToHistory = addToHistory;
|
||||
|
||||
UpdateArgs(args);
|
||||
|
||||
MoreCommands = [
|
||||
@@ -49,13 +55,13 @@ internal sealed partial class RunExeItem : ListItem
|
||||
{
|
||||
Name = Properties.Resources.cmd_run_as_administrator,
|
||||
Icon = Icons.AdminIcon,
|
||||
}),
|
||||
}) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Enter) },
|
||||
new CommandContextItem(
|
||||
new AnonymousCommand(RunAsOther)
|
||||
{
|
||||
Name = Properties.Resources.cmd_run_as_user,
|
||||
Icon = Icons.UserIcon,
|
||||
}),
|
||||
}) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.U) },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -89,16 +95,22 @@ internal sealed partial class RunExeItem : ListItem
|
||||
|
||||
public void Run()
|
||||
{
|
||||
_addToHistory?.Invoke(FullString);
|
||||
|
||||
ShellHelpers.OpenInShell(FullExePath, _args);
|
||||
}
|
||||
|
||||
public void RunAsAdmin()
|
||||
{
|
||||
_addToHistory?.Invoke(FullString);
|
||||
|
||||
ShellHelpers.OpenInShell(FullExePath, _args, runAs: ShellHelpers.ShellRunAsType.Administrator);
|
||||
}
|
||||
|
||||
public void RunAsOther()
|
||||
{
|
||||
_addToHistory?.Invoke(FullString);
|
||||
|
||||
ShellHelpers.OpenInShell(FullExePath, _args, runAs: ShellHelpers.ShellRunAsType.OtherUser);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Shell.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -20,21 +21,28 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
private readonly ShellListPageHelpers _helper;
|
||||
|
||||
private readonly List<ListItem> _topLevelItems = [];
|
||||
private readonly List<ListItem> _historyItems = [];
|
||||
private RunExeItem? _exeItem;
|
||||
private readonly Dictionary<string, ListItem> _historyItems = [];
|
||||
private readonly List<ListItem> _currentHistoryItems = [];
|
||||
|
||||
private readonly IRunHistoryService _historyService;
|
||||
|
||||
private ListItem? _exeItem;
|
||||
private List<ListItem> _pathItems = [];
|
||||
private ListItem? _uriItem;
|
||||
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
private Task? _currentSearchTask;
|
||||
|
||||
public ShellListPage(SettingsManager settingsManager, bool addBuiltins = false)
|
||||
private bool _loadedInitialHistory;
|
||||
|
||||
public ShellListPage(SettingsManager settingsManager, IRunHistoryService runHistoryService, bool addBuiltins = false)
|
||||
{
|
||||
Icon = Icons.RunV2Icon;
|
||||
Id = "com.microsoft.cmdpal.shell";
|
||||
Name = Resources.cmd_plugin_name;
|
||||
PlaceholderText = Resources.list_placeholder_text;
|
||||
_helper = new(settingsManager);
|
||||
_historyService = runHistoryService;
|
||||
|
||||
EmptyContent = new CommandItem()
|
||||
{
|
||||
@@ -68,8 +76,6 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = _cancellationTokenSource.Token;
|
||||
|
||||
IsLoading = true;
|
||||
|
||||
try
|
||||
{
|
||||
// Save the latest search task
|
||||
@@ -139,10 +145,14 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
_pathItems.Clear();
|
||||
_exeItem = null;
|
||||
_uriItem = null;
|
||||
|
||||
_currentHistoryItems.Clear();
|
||||
_currentHistoryItems.AddRange(_historyItems.Values);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ParseExecutableAndArgs(expanded, out var exe, out var args);
|
||||
ShellHelpers.ParseExecutableAndArgs(expanded, out var exe, out var args);
|
||||
|
||||
// Check for cancellation before file system operations
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -206,6 +216,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
&& (!exeExists || pathIsDir)
|
||||
&& couldResolvePath)
|
||||
{
|
||||
IsLoading = true;
|
||||
await CreatePathItemsAsync(expanded, searchText, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -231,18 +242,53 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
_uriItem = null;
|
||||
}
|
||||
|
||||
var histItemsNotInSearch =
|
||||
_historyItems
|
||||
.Where(kv => !kv.Key.Equals(newSearch, StringComparison.OrdinalIgnoreCase));
|
||||
if (_exeItem != null)
|
||||
{
|
||||
// If we have an exe item, we want to remove it from the history items
|
||||
histItemsNotInSearch = histItemsNotInSearch
|
||||
.Where(kv => !kv.Value.Title.Equals(_exeItem.Title, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (_uriItem != null)
|
||||
{
|
||||
// If we have an uri item, we want to remove it from the history items
|
||||
histItemsNotInSearch = histItemsNotInSearch
|
||||
.Where(kv => !kv.Value.Title.Equals(_uriItem.Title, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
// Filter the history items based on the search text
|
||||
var filterHistory = (string query, KeyValuePair<string, ListItem> pair) =>
|
||||
{
|
||||
// Fuzzy search on the key (command string)
|
||||
var score = StringMatcher.FuzzySearch(query, pair.Key).Score;
|
||||
return score;
|
||||
};
|
||||
|
||||
var filteredHistory =
|
||||
ListHelpers.FilterList<KeyValuePair<string, ListItem>>(
|
||||
histItemsNotInSearch,
|
||||
searchText,
|
||||
filterHistory)
|
||||
.Select(p => p.Value);
|
||||
|
||||
_currentHistoryItems.Clear();
|
||||
_currentHistoryItems.AddRange(filteredHistory);
|
||||
|
||||
// Final cancellation check
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
private static ListItem PathToListItem(string path, string originalPath, string args = "")
|
||||
private static ListItem PathToListItem(string path, string originalPath, string args = "", Action<string>? addToHistory = null)
|
||||
{
|
||||
var pathItem = new PathListItem(path, originalPath);
|
||||
var pathItem = new PathListItem(path, originalPath, addToHistory);
|
||||
|
||||
// Is this path an executable? If so, then make a RunExeItem
|
||||
if (IsExecutable(path))
|
||||
{
|
||||
var exeItem = new RunExeItem(Path.GetFileName(path), args, path);
|
||||
var exeItem = new RunExeItem(Path.GetFileName(path), args, path, addToHistory);
|
||||
|
||||
exeItem.MoreCommands = [
|
||||
.. exeItem.MoreCommands,
|
||||
@@ -255,36 +301,41 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
if (!_loadedInitialHistory)
|
||||
{
|
||||
LoadInitialHistory();
|
||||
}
|
||||
|
||||
var filteredTopLevel = ListHelpers.FilterList(_topLevelItems, SearchText);
|
||||
List<ListItem> uriItems = _uriItem != null ? [_uriItem] : [];
|
||||
List<ListItem> exeItems = _exeItem != null ? [_exeItem] : [];
|
||||
|
||||
return
|
||||
exeItems
|
||||
.Concat(filteredTopLevel)
|
||||
.Concat(_historyItems)
|
||||
.Concat(_currentHistoryItems)
|
||||
.Concat(_pathItems)
|
||||
.Concat(uriItems)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
internal static RunExeItem CreateExeItem(string exe, string args, string fullExePath)
|
||||
internal static ListItem CreateExeItem(string exe, string args, string fullExePath, Action<string>? addToHistory)
|
||||
{
|
||||
// PathToListItem will return a RunExeItem if it can find a executable.
|
||||
// It will ALSO add the file search commands to the RunExeItem.
|
||||
return PathToListItem(fullExePath, exe, args) as RunExeItem ??
|
||||
new RunExeItem(exe, args, fullExePath);
|
||||
return PathToListItem(fullExePath, exe, args, addToHistory);
|
||||
}
|
||||
|
||||
private void CreateAndAddExeItems(string exe, string args, string fullExePath)
|
||||
{
|
||||
// If we already have an exe item, and the exe is the same, we can just update it
|
||||
if (_exeItem != null && _exeItem.FullExePath.Equals(fullExePath, StringComparison.OrdinalIgnoreCase))
|
||||
if (_exeItem is RunExeItem exeItem && exeItem.FullExePath.Equals(fullExePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_exeItem.UpdateArgs(args);
|
||||
exeItem.UpdateArgs(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
_exeItem = CreateExeItem(exe, args, fullExePath);
|
||||
_exeItem = CreateExeItem(exe, args, fullExePath, AddToHistory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +344,8 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
// Is this path an executable?
|
||||
// check all the extensions in PATHEXT
|
||||
var extensions = Environment.GetEnvironmentVariable("PATHEXT")?.Split(';') ?? Array.Empty<string>();
|
||||
return extensions.Any(ext => string.Equals(Path.GetExtension(path), ext, StringComparison.OrdinalIgnoreCase));
|
||||
var extension = Path.GetExtension(path);
|
||||
return string.IsNullOrEmpty(extension) || extensions.Any(ext => string.Equals(extension, ext, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private async Task CreatePathItemsAsync(string searchPath, string originalPath, CancellationToken cancellationToken)
|
||||
@@ -387,46 +439,6 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ParseExecutableAndArgs(string input, out string executable, out string arguments)
|
||||
{
|
||||
input = input.Trim();
|
||||
executable = string.Empty;
|
||||
arguments = string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.StartsWith("\"", System.StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
// Find the closing quote
|
||||
var closingQuoteIndex = input.IndexOf('\"', 1);
|
||||
if (closingQuoteIndex > 0)
|
||||
{
|
||||
executable = input.Substring(1, closingQuoteIndex - 1);
|
||||
if (closingQuoteIndex + 1 < input.Length)
|
||||
{
|
||||
arguments = input.Substring(closingQuoteIndex + 1).TrimStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Executable ends at first space
|
||||
var firstSpaceIndex = input.IndexOf(' ');
|
||||
if (firstSpaceIndex > 0)
|
||||
{
|
||||
executable = input.Substring(0, firstSpaceIndex);
|
||||
arguments = input[(firstSpaceIndex + 1)..].TrimStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
executable = input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void CreateUriItems(string searchText)
|
||||
{
|
||||
if (!System.Uri.TryCreate(searchText, UriKind.Absolute, out var uri))
|
||||
@@ -442,6 +454,40 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
||||
};
|
||||
}
|
||||
|
||||
private void LoadInitialHistory()
|
||||
{
|
||||
var hist = _historyService.GetRunHistory();
|
||||
var histItems = hist
|
||||
.Select(h => (h, ShellListPageHelpers.ListItemForCommandString(h, AddToHistory)))
|
||||
.Where(tuple => tuple.Item2 != null)
|
||||
.Select(tuple => (tuple.h, tuple.Item2!))
|
||||
.ToList();
|
||||
_historyItems.Clear();
|
||||
|
||||
// Add all the history items to the _historyItems dictionary
|
||||
foreach (var (h, item) in histItems)
|
||||
{
|
||||
_historyItems[h] = item;
|
||||
}
|
||||
|
||||
_currentHistoryItems.Clear();
|
||||
_currentHistoryItems.AddRange(histItems.Select(tuple => tuple.Item2));
|
||||
|
||||
_loadedInitialHistory = true;
|
||||
}
|
||||
|
||||
internal void AddToHistory(string commandString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(commandString))
|
||||
{
|
||||
return; // Do not add empty or whitespace items
|
||||
}
|
||||
|
||||
_historyService.AddRunHistoryItem(commandString);
|
||||
LoadInitialHistory();
|
||||
DoUpdateSearchText(SearchText);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.CmdPal.Common.Commands;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Shell;
|
||||
|
||||
@@ -16,8 +18,8 @@ internal sealed partial class PathListItem : ListItem
|
||||
|
||||
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
|
||||
|
||||
public PathListItem(string path, string originalDir)
|
||||
: base(new OpenUrlCommand(path))
|
||||
public PathListItem(string path, string originalDir, Action<string>? addToHistory)
|
||||
: base(new OpenUrlWithHistoryCommand(path, addToHistory))
|
||||
{
|
||||
var fileName = Path.GetFileName(path);
|
||||
_isDirectory = Directory.Exists(path);
|
||||
@@ -46,17 +48,15 @@ internal sealed partial class PathListItem : ListItem
|
||||
}
|
||||
|
||||
TextToSuggest = suggestion;
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new CopyTextCommand(path) { Name = Properties.Resources.copy_path_command_name }) { }
|
||||
];
|
||||
|
||||
// MoreCommands = [
|
||||
// new CommandContextItem(new OpenWithCommand(indexerItem)),
|
||||
// new CommandContextItem(new ShowFileInFolderCommand(indexerItem.FullPath) { Name = Resources.Indexer_Command_ShowInFolder }),
|
||||
// new CommandContextItem(new CopyPathCommand(indexerItem)),
|
||||
// new CommandContextItem(new OpenInConsoleCommand(indexerItem)),
|
||||
// new CommandContextItem(new OpenPropertiesCommand(indexerItem)),
|
||||
// ];
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new OpenWithCommand(path)),
|
||||
new CommandContextItem(new ShowFileInFolderCommand(path)) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.E) },
|
||||
new CommandContextItem(new CopyPathCommand(path) { Name = Properties.Resources.copy_path_command_name }) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C) },
|
||||
new CommandContextItem(new OpenInConsoleCommand(path)) { RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.R) },
|
||||
new CommandContextItem(new OpenPropertiesCommand(path)),
|
||||
];
|
||||
|
||||
_icon = new Lazy<IconInfo>(() =>
|
||||
{
|
||||
var iconStream = ThumbnailHelper.GetThumbnail(path).Result;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Shell.Pages;
|
||||
using Microsoft.CmdPal.Ext.Shell.Properties;
|
||||
@@ -15,18 +16,24 @@ public partial class ShellCommandsProvider : CommandProvider
|
||||
private readonly CommandItem _shellPageItem;
|
||||
|
||||
private readonly SettingsManager _settingsManager = new();
|
||||
private readonly ShellListPage _shellListPage;
|
||||
private readonly FallbackCommandItem _fallbackItem;
|
||||
private readonly IRunHistoryService _historyService;
|
||||
|
||||
public ShellCommandsProvider()
|
||||
public ShellCommandsProvider(IRunHistoryService runHistoryService)
|
||||
{
|
||||
_historyService = runHistoryService;
|
||||
|
||||
Id = "Run";
|
||||
DisplayName = Resources.cmd_plugin_name;
|
||||
Icon = Icons.RunV2Icon;
|
||||
Settings = _settingsManager.Settings;
|
||||
|
||||
_fallbackItem = new FallbackExecuteItem(_settingsManager);
|
||||
_shellListPage = new ShellListPage(_settingsManager, _historyService);
|
||||
|
||||
_shellPageItem = new CommandItem(new ShellListPage(_settingsManager))
|
||||
_fallbackItem = new FallbackExecuteItem(_settingsManager, _shellListPage.AddToHistory);
|
||||
|
||||
_shellPageItem = new CommandItem(_shellListPage)
|
||||
{
|
||||
Icon = Icons.RunV2Icon,
|
||||
Title = Resources.shell_command_name,
|
||||
|
||||
@@ -33,9 +33,4 @@
|
||||
<CustomToolNamespace>Microsoft.CmdPal.Ext.System</CustomToolNamespace>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Microsoft.CmdPal.Ext.System.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.CmdPal.Ext.System.UnitTests")]
|
||||
@@ -11,12 +11,6 @@
|
||||
<ProjectPriFileName>Microsoft.CmdPal.Ext.TimeDate.pri</ProjectPriFileName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Microsoft.CmdPal.Ext.TimeDate.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.CmdPal.Ext.TimeDate.UnitTests")]
|
||||
@@ -46,9 +46,4 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Microsoft.CmdPal.Ext.WindowWalker.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.CmdPal.Ext.WindowWalker.UnitTests")]
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class CopyPathCommand : InvokableCommand
|
||||
{
|
||||
internal static IconInfo CopyPath { get; } = new("\uE8c8"); // Copy
|
||||
|
||||
private readonly string _path;
|
||||
|
||||
public CommandResult Result { get; set; } = CommandResult.KeepOpen();
|
||||
|
||||
public CopyPathCommand(string fullPath)
|
||||
{
|
||||
this._path = fullPath;
|
||||
this.Name = "Copy path";
|
||||
this.Icon = CopyPath;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
try
|
||||
{
|
||||
ClipboardHelper.SetText(_path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class OpenFileCommand : InvokableCommand
|
||||
{
|
||||
internal static IconInfo OpenFile { get; } = new("\uE8E5"); // OpenFile
|
||||
|
||||
private readonly string _fullPath;
|
||||
|
||||
public CommandResult Result { get; set; } = CommandResult.Dismiss();
|
||||
|
||||
public OpenFileCommand(string fullPath)
|
||||
{
|
||||
this._fullPath = fullPath;
|
||||
this.Name = "Open";
|
||||
this.Icon = OpenFile;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo.FileName = _fullPath;
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
ExtensionHost.LogMessage($"Unable to open {_fullPath}\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public sealed partial class OpenUrlCommand : InvokableCommand
|
||||
public partial class OpenUrlCommand : InvokableCommand
|
||||
{
|
||||
private readonly string _target;
|
||||
|
||||
@@ -43,7 +43,6 @@ public partial class ListHelpers
|
||||
}
|
||||
|
||||
public static IEnumerable<T> FilterList<T>(IEnumerable<T> items, string query, Func<string, T, int> scoreFunction)
|
||||
where T : class
|
||||
{
|
||||
var scores = items
|
||||
.Select(li => new Scored<T>() { Item = li, Score = scoreFunction(query, li) })
|
||||
|
||||
@@ -5,4 +5,4 @@ GetPackageFamilyNameFromToken
|
||||
CoRevertToSelf
|
||||
SHGetKnownFolderPath
|
||||
KNOWN_FOLDER_FLAG
|
||||
GetCurrentPackageId
|
||||
GetCurrentPackageId
|
||||
@@ -59,4 +59,101 @@ public static class ShellHelpers
|
||||
Administrator,
|
||||
OtherUser,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the input string to extract the executable and its arguments.
|
||||
/// </summary>
|
||||
public static void ParseExecutableAndArgs(string input, out string executable, out string arguments)
|
||||
{
|
||||
input = input.Trim();
|
||||
executable = string.Empty;
|
||||
arguments = string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.StartsWith("\"", System.StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
// Find the closing quote
|
||||
var closingQuoteIndex = input.IndexOf('\"', 1);
|
||||
if (closingQuoteIndex > 0)
|
||||
{
|
||||
executable = input.Substring(1, closingQuoteIndex - 1);
|
||||
if (closingQuoteIndex + 1 < input.Length)
|
||||
{
|
||||
arguments = input.Substring(closingQuoteIndex + 1).TrimStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Executable ends at first space
|
||||
var firstSpaceIndex = input.IndexOf(' ');
|
||||
if (firstSpaceIndex > 0)
|
||||
{
|
||||
executable = input.Substring(0, firstSpaceIndex);
|
||||
arguments = input[(firstSpaceIndex + 1)..].TrimStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
executable = input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a file exists somewhere in the PATH.
|
||||
/// If it exists, returns the full path to the file in the out parameter.
|
||||
/// If it does not exist, returns false and the out parameter is set to an empty string.
|
||||
/// <param name="filename">The name of the file to check.</param>
|
||||
/// <param name="fullPath">The full path to the file if it exists; otherwise an empty string.</param>
|
||||
/// <param name="token">An optional cancellation token to cancel the operation.</param>
|
||||
/// <returns>True if the file exists in the PATH; otherwise false.</returns>
|
||||
/// </summary>
|
||||
public static bool FileExistInPath(string filename, out string fullPath, CancellationToken? token = null)
|
||||
{
|
||||
fullPath = string.Empty;
|
||||
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
token?.ThrowIfCancellationRequested();
|
||||
fullPath = Path.GetFullPath(filename);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var values = Environment.GetEnvironmentVariable("PATH");
|
||||
if (values != null)
|
||||
{
|
||||
foreach (var path in values.Split(';'))
|
||||
{
|
||||
var path1 = Path.Combine(path, filename);
|
||||
if (File.Exists(path1))
|
||||
{
|
||||
fullPath = Path.GetFullPath(path1);
|
||||
return true;
|
||||
}
|
||||
|
||||
token?.ThrowIfCancellationRequested();
|
||||
|
||||
var path2 = Path.Combine(path, filename + ".exe");
|
||||
if (File.Exists(path2))
|
||||
{
|
||||
fullPath = Path.GetFullPath(path2);
|
||||
return true;
|
||||
}
|
||||
|
||||
token?.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.UITest;
|
||||
|
||||
namespace Microsoft.ColorPicker.UITests
|
||||
{
|
||||
public class ColorPickerUITest : UITestBase
|
||||
{
|
||||
public ColorPickerUITest()
|
||||
: base(PowerToysModule.Runner)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
## Color Picker
|
||||
* Enable the Color Picker in settings and ensure that the hotkey brings up Color Picker
|
||||
- [] when PowerToys is running unelevated on start-up
|
||||
- [] when PowerToys is running as admin on start-up
|
||||
- [] when PowerToys is restarted as admin, by clicking the restart as admin button in the settings
|
||||
- [] Change `Activate Color Picker shortcut` and check the new shortcut is working
|
||||
- [] Try all three `Activation behavior`s(`Color Picker with editor mode enabled`, `Editor`, `Color Picker only`)
|
||||
- [] Change `Color format for clipboard` and check if the correct format is copied from the Color picker
|
||||
- [] Try to copy color formats to the clipboard from the Editor
|
||||
- [] Check `Show color name` and verify if color name is shown in the Color picker
|
||||
- [] Enable one new format, disable one existing format, reorder enabled formats and check if settings are populated to the Editor
|
||||
- [] Select a color from the history in the Editor
|
||||
- [] Remove color from the history in the Editor
|
||||
- [] Open the Color Picker from the Editor
|
||||
- [] Open Adjust color from the Editor
|
||||
- [] Check Color Picker logs for errors
|
||||
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{6880CE86-5B71-4440-9795-79A325F95747}</ProjectGuid>
|
||||
<RootNamespace>Microsoft.ColorPicker.UITests</RootNamespace>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
|
||||
<!-- This is a UI test, so don't run as part of MSBuild -->
|
||||
<RunVSTest>false</RunVSTest>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\UITests-ColorPicker\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Appium.WebDriver" />
|
||||
<PackageReference Include="MSTest" />
|
||||
<PackageReference Include="System.Net.Http" />
|
||||
<PackageReference Include="System.Private.Uri" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -32,7 +32,7 @@
|
||||
<PreprocessorDefinitions>WIN32;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>$(OutDir)\..\..\WinUI3Apps\PowerRenameLib.lib;Pathcch.lib;comctl32.lib;shlwapi.lib;shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Pathcch.lib;comctl32.lib;shlwapi.lib;shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
@@ -60,6 +60,9 @@
|
||||
<ProjectReference Include="..\..\..\common\Themes\Themes.vcxproj">
|
||||
<Project>{98537082-0fdb-40de-abd8-0dc5a4269bab}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\lib\PowerRenameLib.vcxproj">
|
||||
<Project>{51920f1f-c28c-4adf-8660-4238766796c2}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
||||
236
src/settings-ui/UITest-Settings/OOBEUITests.cs
Normal file
236
src/settings-ui/UITest-Settings/OOBEUITests.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.Settings.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class OOBEUITests : UITestBase
|
||||
{
|
||||
// Constants for file paths and identifiers
|
||||
private const string LocalAppDataFolderPath = "%localappdata%\\Microsoft\\PowerToys";
|
||||
private const string LastVersionFilePath = "%localappdata%\\Microsoft\\PowerToys\\last_version.txt";
|
||||
|
||||
public OOBEUITests()
|
||||
: base(PowerToysModule.PowerToysSettings)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("OOBE.Basic.FirstStartTest")]
|
||||
[TestCategory("OOBE test #1")]
|
||||
public void TestOOBEFirstStart()
|
||||
{
|
||||
// Clean up previous PowerToys data to simulate first start
|
||||
// CleanPowerToysData();
|
||||
|
||||
// Start PowerToys and verify OOBE opens
|
||||
// StartPowerToysAndVerifyOOBEOpens();
|
||||
|
||||
// Navigate through all OOBE sections
|
||||
NavigateThroughOOBESections();
|
||||
|
||||
// Close OOBE
|
||||
CloseOOBE();
|
||||
|
||||
// Verify OOBE can be opened from Settings
|
||||
// OpenOOBEFromSettings();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
[TestMethod("OOBE.WhatsNew.Test")]
|
||||
[TestCategory("OOBE test #2")]
|
||||
public void TestOOBEWhatsNew()
|
||||
{
|
||||
// Modify version file to trigger What's New
|
||||
ModifyLastVersionFile();
|
||||
|
||||
// Start PowerToys and verify OOBE opens in What's New page
|
||||
StartPowerToysAndVerifyWhatsNewOpens();
|
||||
|
||||
// Close OOBE
|
||||
CloseOOBE();
|
||||
}
|
||||
*/
|
||||
|
||||
private void CleanPowerToysData()
|
||||
{
|
||||
this.ExitScopeExe();
|
||||
|
||||
// Exit PowerToys if it's running
|
||||
try
|
||||
{
|
||||
foreach (Process process in Process.GetProcessesByName("PowerToys"))
|
||||
{
|
||||
process.Kill();
|
||||
process.WaitForExit();
|
||||
}
|
||||
|
||||
// Delete PowerToys folder in LocalAppData
|
||||
string powerToysFolder = Environment.ExpandEnvironmentVariables(LocalAppDataFolderPath);
|
||||
if (Directory.Exists(powerToysFolder))
|
||||
{
|
||||
Directory.Delete(powerToysFolder, true);
|
||||
}
|
||||
|
||||
// Wait to ensure deletion is complete
|
||||
Task.Delay(1000).Wait();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Inconclusive($"Could not clean PowerToys data: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void StartPowerToysAndVerifyOOBEOpens()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Start PowerToys
|
||||
this.RestartScopeExe();
|
||||
|
||||
// Wait for OOBE window to appear
|
||||
Task.Delay(5000).Wait();
|
||||
|
||||
// Verify OOBE window opened
|
||||
Assert.IsTrue(this.Session.HasOne("Welcome to PowerToys"), "OOBE window should open with 'Welcome to PowerToys' title");
|
||||
|
||||
// Verify we're on the Overview page
|
||||
Assert.IsTrue(this.Has("Overview"), "OOBE should start on Overview page");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"Failed to start PowerToys and verify OOBE: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void NavigateThroughOOBESections()
|
||||
{
|
||||
// List of modules to test
|
||||
string[] modules = new string[]
|
||||
{
|
||||
"What's new",
|
||||
"Advanced Paste",
|
||||
};
|
||||
|
||||
this.Find<NavigationViewItem>("Welcome to PowerToys").Click();
|
||||
|
||||
foreach (string module in modules)
|
||||
{
|
||||
TestModule(module);
|
||||
}
|
||||
}
|
||||
|
||||
private void TestModule(string moduleName)
|
||||
{
|
||||
var oobeWindow = this.Find<Window>("Welcome to PowerToys");
|
||||
Assert.IsNotNull(oobeWindow);
|
||||
|
||||
/*
|
||||
- [] open the Settings for that module
|
||||
- [] verify the Settings work as expected (toggle some controls on/off etc.)
|
||||
- [] close the Settings
|
||||
- [] if it's available, test the `Launch module name` button
|
||||
*/
|
||||
oobeWindow.Find<Button>(By.Name("Open Settings")).Click();
|
||||
|
||||
// Find<NavigationViewItem>("What's new").Click();
|
||||
Task.Delay(1000).Wait();
|
||||
}
|
||||
|
||||
private void CloseOOBE()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Find the close button and click it
|
||||
this.Session.CloseMainWindow();
|
||||
Task.Delay(1000).Wait();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"Failed to close OOBE: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenOOBEFromSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Open PowerToys Settings
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
|
||||
// Navigate to General page
|
||||
this.Find<NavigationViewItem>("General").Click();
|
||||
Task.Delay(1000).Wait();
|
||||
|
||||
// Click on "Welcome to PowerToys" link
|
||||
this.Find<HyperlinkButton>("Welcome to PowerToys").Click();
|
||||
Task.Delay(2000).Wait();
|
||||
|
||||
// Verify OOBE opened
|
||||
Assert.IsTrue(this.Session.HasOne("Welcome to PowerToys"), "OOBE should open when clicking the link in Settings");
|
||||
|
||||
// Close OOBE
|
||||
this.Session.CloseMainWindow();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"Failed to open OOBE from Settings: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ModifyLastVersionFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create PowerToys folder if it doesn't exist
|
||||
string powerToysFolder = Environment.ExpandEnvironmentVariables(LocalAppDataFolderPath);
|
||||
if (!Directory.Exists(powerToysFolder))
|
||||
{
|
||||
Directory.CreateDirectory(powerToysFolder);
|
||||
}
|
||||
|
||||
// Write a different version to trigger What's New
|
||||
string versionFilePath = Environment.ExpandEnvironmentVariables(LastVersionFilePath);
|
||||
File.WriteAllText(versionFilePath, "0.0.1");
|
||||
|
||||
// Wait to ensure file is written
|
||||
Task.Delay(1000).Wait();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Inconclusive($"Could not modify version file: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void StartPowerToysAndVerifyWhatsNewOpens()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Start PowerToys
|
||||
this.RestartScopeExe();
|
||||
|
||||
// Wait for OOBE window to appear
|
||||
Task.Delay(5000).Wait();
|
||||
|
||||
// Verify OOBE window opened
|
||||
Assert.IsTrue(this.Session.HasOne("Welcome to PowerToys"), "OOBE window should open");
|
||||
|
||||
// Verify we're on the What's New page
|
||||
Assert.IsTrue(this.Has("What's new"), "OOBE should open on What's New page after version change");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"Failed to verify What's New page: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
149
src/settings-ui/UITest-Settings/SettingsTests.cs
Normal file
149
src/settings-ui/UITest-Settings/SettingsTests.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Windows.Devices.PointOfService.Provider;
|
||||
|
||||
namespace Microsoft.Settings.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class SettingsTests : UITestBase
|
||||
{
|
||||
private readonly string[] dashboardModuleList =
|
||||
{
|
||||
"Advanced Paste",
|
||||
"Always On Top",
|
||||
"Awake",
|
||||
"Color Picker",
|
||||
"Command Palette",
|
||||
"Environment Variables",
|
||||
"FancyZones",
|
||||
"File Locksmith",
|
||||
"Find My Mouse",
|
||||
"Hosts File Editor",
|
||||
"Image Resizer",
|
||||
"Keyboard Manager",
|
||||
"Mouse Highlighter",
|
||||
"Mouse Jump",
|
||||
"Mouse Pointer Crosshairs",
|
||||
"Mouse Without Borders",
|
||||
"New+",
|
||||
"Peek",
|
||||
"PowerRename",
|
||||
"PowerToys Run",
|
||||
"Quick Accent",
|
||||
"Registry Preview",
|
||||
"Screen Ruler",
|
||||
"Shortcut Guide",
|
||||
"Text Extractor",
|
||||
"Workspaces",
|
||||
"ZoomIt",
|
||||
|
||||
// "Crop And Lock", // this module cannot be found, why?
|
||||
};
|
||||
|
||||
private readonly string[] moduleProcess =
|
||||
{
|
||||
"PowerToys.AdvancedPaste",
|
||||
"PowerToys.Run",
|
||||
"PowerToys.AlwaysOnTop",
|
||||
"PowerToys.Awake",
|
||||
"PowerToys.ColorPickerUI",
|
||||
"PowerToys.Peek.UI",
|
||||
};
|
||||
|
||||
public SettingsTests()
|
||||
: base(PowerToysModule.PowerToysSettings, size: WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("PowerToys.Settings.ModulesOnAndOffTest")]
|
||||
[TestCategory("Settings Test #1")]
|
||||
public void TestAllmoduleOnAndOff()
|
||||
{
|
||||
DisableAllModules();
|
||||
Task.Delay(2000).Wait();
|
||||
|
||||
// module process won't be killed in debug mode settings UI!
|
||||
// Assert.IsTrue(CheckModulesDisabled(), "Some modules are not disabled.");
|
||||
EnableAllModules();
|
||||
Task.Delay(2000).Wait();
|
||||
|
||||
// Assert.IsTrue(CheckModulesEnabled(), "Some modules are not Enabled.");
|
||||
}
|
||||
|
||||
private void DisableAllModules()
|
||||
{
|
||||
Find<NavigationViewItem>("Dashboard").Click();
|
||||
|
||||
foreach (var moduleName in dashboardModuleList)
|
||||
{
|
||||
var moduleButton = Find<Button>(moduleName);
|
||||
Assert.IsNotNull(moduleButton);
|
||||
var toggle = moduleButton.Find<ToggleSwitch>("Enable module");
|
||||
Assert.IsNotNull(toggle);
|
||||
if (toggle.IsOn)
|
||||
{
|
||||
toggle.Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnableAllModules()
|
||||
{
|
||||
Find<NavigationViewItem>("Dashboard").Click();
|
||||
|
||||
foreach (var moduleName in dashboardModuleList)
|
||||
{
|
||||
// Scroll(direction: "Down");
|
||||
var moduleButton = Find<Button>(moduleName);
|
||||
Assert.IsNotNull(moduleButton);
|
||||
var toggle = moduleButton.Find<ToggleSwitch>("Enable module");
|
||||
Assert.IsNotNull(toggle);
|
||||
if (!toggle.IsOn)
|
||||
{
|
||||
toggle.Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckModulesDisabled()
|
||||
{
|
||||
Process[] runningProcesses = Process.GetProcesses();
|
||||
|
||||
foreach (var process in moduleProcess)
|
||||
{
|
||||
if (runningProcesses.Any(p => p.ProcessName.Equals(process, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CheckModulesEnabled()
|
||||
{
|
||||
Process[] runningProcesses = Process.GetProcesses();
|
||||
|
||||
foreach (var process in moduleProcess)
|
||||
{
|
||||
if (!runningProcesses.Any(p => p.ProcessName.Equals(process, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/settings-ui/UITest-Settings/SettingsTests.md
Normal file
41
src/settings-ui/UITest-Settings/SettingsTests.md
Normal file
@@ -0,0 +1,41 @@
|
||||
## [General Settings](tests-checklist-template-settings-section.md)
|
||||
|
||||
**Admin mode:**
|
||||
- [] restart PT and verify it runs as user
|
||||
- [] restart as admin and set "Always run as admin"
|
||||
- [] restart PT and verify it runs as admin
|
||||
* if it's not on, turn on "Run at startup"
|
||||
- [] reboot the machine and verify PT runs as admin (it should not prompt the UAC dialog)
|
||||
* turn Always run as admin" off
|
||||
- [] reboot the machine and verify it now runs as user
|
||||
|
||||
**Modules on/off:**
|
||||
- [x] turn off all the modules and verify all module are off
|
||||
- [] restart PT and verify that all module are still off in the settings page and they are actually inactive
|
||||
- [x] turn on all the module, all module are now working
|
||||
- [] restart PT and verify that all module are still on in the settings page and they are actually working
|
||||
|
||||
**Quick access tray icon flyout:**
|
||||
- [] Use left click on the system tray icon and verify the flyout appears.
|
||||
- [] Try to launch a module from the launch screen in the flyout.
|
||||
- [] Try disabling a module in the all apps screen in the flyout, make it a module that's launchable from the launch screen. Verify that the module is disabled and that it also disappeared from the launch screen in the flyout.
|
||||
- [] Open the main settings screen on a module page. Verify that when you disable/enable the module on the flyout, that the Settings page is updated too.
|
||||
|
||||
**Settings backup/restore:**
|
||||
- [] In the General tab, create a backup of the settings.
|
||||
- [] Change some settings in some PowerToys.
|
||||
- [] Restore the settings in the General tab and verify the Settings you've applied were reset.
|
||||
|
||||
## OOBE
|
||||
* Quit PowerToys
|
||||
* Delete %localappdata%\Microsoft\PowerToys
|
||||
- [] Start PowerToys and verify OOBE opens
|
||||
* Change version saved on `%localappdata%\Microsoft\PowerToys\last_version.txt`
|
||||
- [] Start PowerToys and verify OOBE opens in the "What's New" page
|
||||
* Visit each OOBE section and for each section:
|
||||
- [] open the Settings for that module
|
||||
- [] verify the Settings work as expected (toggle some controls on/off etc.)
|
||||
- [] close the Settings
|
||||
- [] if it's available, test the `Launch module name` button
|
||||
* Close OOBE
|
||||
- [x] Open the Settings and from the General page open OOBE using the `Welcome to PowerToys` link
|
||||
32
src/settings-ui/UITest-Settings/UITest-Settings.csproj
Normal file
32
src/settings-ui/UITest-Settings/UITest-Settings.csproj
Normal file
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{29B91A80-0590-4B1F-89B8-4F8812A7F116}</ProjectGuid>
|
||||
<RootNamespace>Microsoft.Settings.UITests</RootNamespace>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
|
||||
<!-- This is a UI test, so don't run as part of MSBuild -->
|
||||
<RunVSTest>false</RunVSTest>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\..\..\$(Platform)\$(Configuration)\tests\UITests-Settings\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Appium.WebDriver" />
|
||||
<PackageReference Include="MSTest" />
|
||||
<PackageReference Include="System.Net.Http" />
|
||||
<PackageReference Include="System.Private.Uri" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -81,7 +81,7 @@ function RunMSBuild {
|
||||
|
||||
$base = @(
|
||||
$Solution
|
||||
"/p:Platform=`"$Platform`""
|
||||
"/p:Platform=$Platform"
|
||||
"/p:Configuration=$Configuration"
|
||||
"/p:CIBuild=true"
|
||||
'/verbosity:normal'
|
||||
@@ -158,4 +158,4 @@ RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysInstaller /p:PerUser=$
|
||||
|
||||
RunMSBuild 'installer\PowerToysSetup.sln' "/m /t:PowerToysBootstrapper /p:PerUser=$PerUser"
|
||||
|
||||
Write-Host '[PIPELINE] Completed'
|
||||
Write-Host '[PIPELINE] Completed'
|
||||
|
||||
Reference in New Issue
Block a user