Compare commits

..

59 Commits

Author SHA1 Message Date
Leilei Zhang
bfa490cfde use variable 2025-07-31 11:07:46 +08:00
Leilei Zhang
b99e62af48 Merge branch 'main' of https://github.com/microsoft/PowerToys into leilzh/pathissue 2025-07-30 18:30:32 +08:00
Leilei Zhang
18b926c41d update md 2025-07-30 18:18:00 +08:00
Leilei Zhang
2eb99f96a3 remove choose install mode 2025-07-30 18:10:02 +08:00
Leilei Zhang
fb260bd098 update now 2025-07-30 18:02:59 +08:00
Leilei Zhang
f35f94bbaa update 2025-07-30 17:40:02 +08:00
Leilei Zhang
873a7a2bbe test update 2025-07-30 17:33:01 +08:00
Yu Leng
46d380c2b6 [CmdPal][UnitTest] Refactor system command unit test (#40874)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Ok... The AI generated and migrated ut's quality is very poor. We need
to refactor it.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #40875
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
2025-07-30 17:19:40 +08:00
Leilei Zhang
0a8b83e528 test default value 2025-07-30 17:04:09 +08:00
Leilei Zhang
7849079de4 update pipeline 2025-07-30 15:31:50 +08:00
Leilei Zhang
1a303301bb fix comments 2025-07-30 15:19:56 +08:00
Leilei Zhang
3206c0b073 add win 10 2025-07-30 12:10:23 +08:00
Leilei Zhang
82d031cf2a add comments 2025-07-30 11:39:44 +08:00
Leilei Zhang
5c7c8c3009 add buildid 2025-07-30 11:15:53 +08:00
Leilei Zhang
64a48fda97 update the indexer test 2025-07-30 10:36:07 +08:00
Leilei Zhang
75cbd2d577 fix ux 2025-07-29 18:11:24 +08:00
Jiří Polášek
decb947283 CmdPal: Replace Tapped events with generic ones (#40640)
## Summary of the Pull Request
Click event on WinUI buttons handle more than just click and is more
versatile that Tapped event. When you tap a Button with a finger or
stylus, or press a left mouse button while the pointer is over it, the
button raises the Click event. If a button has keyboard focus, pressing
the Enter key or the Spacebar key also raises the Click event.

This PR also replaces the right-tapped event on items on the list page
with context menu handling, allowing other input gestures (such as
Shift+F10) to also display the context menu.

And finally, it adds a button to the status messages badge so that the
flyout can be opened using the keyboard.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #40616 
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Tested on desktop with keyboard and mouse (no cats), and SB2 with touch
and pen. Input gestures seem to work as intended.

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-07-29 17:58:39 +08:00
Leilei Zhang
dd47beabd8 Merge branch 'main' of https://github.com/microsoft/PowerToys into leilzh/pathissue 2025-07-29 15:44:07 +08:00
Leilei Zhang
29a8bbc40a remove unused 2025-07-29 14:32:07 +08:00
Leilei Zhang
6607b60bd5 install xaml 2025-07-29 13:23:34 +08:00
Leilei Zhang
983591e4e0 use release 2025-07-29 12:31:38 +08:00
Leilei Zhang
0a2a3fea93 exit always 0 2025-07-29 11:58:15 +08:00
Kai Tao
801fad09ba Fix a settings crash due to incompatible property name (#40854)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
This pull request introduces a minor update to the `ZoomItProperties`
class in the `Settings.UI.Library` project. The change adds a new
property, `AnimateZoom`, with a JSON property name annotation.

*
[`src/settings-ui/Settings.UI.Library/ZoomItProperties.cs`](diffhunk://#diff-2cd3f90110c7ba387a449d246b4949c3f6cf7f746865f327dbb70f01feeb0cf1R81):
Added a new `BoolProperty` named `AnimateZoom` with a
`[JsonPropertyName("AnimnateZoom")]` attribute.
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Locally checked, no broken any more
2025-07-29 10:53:41 +08:00
Leilei Zhang
30d67ec078 use steps 2025-07-29 10:52:27 +08:00
Leilei Zhang
4c87a78b1a use windows poweshell 2025-07-29 10:19:29 +08:00
Mike Griese
5f2e446f3b cmdpal: move kb shortcut handling to PreviewKeyDown (#40777)
This lets things like C-S-c work in the text box, and in the context
menu too

Closes #40174
2025-07-28 20:17:27 -05:00
Mike Griese
3a0487f74a cmdpal: Add "file" context items to the run items too (#40768)
After #39955, the "exe" items from the shell commands only ever have the
"Run{as admin, as other user}" commands. This adds the rest of the
"file" commands - copy path, open in explorer, etc.

This shuffles around some commands into the toolkit and common commands
project to make this easier.

<img width="814" height="505" alt="image"
src="https://github.com/user-attachments/assets/36ae2c75-d4d6-4762-98ec-796986f39c20"
/>
2025-07-28 20:03:49 -05:00
Mike Griese
6dc2d14e13 CmdPal: A different approach to bookmarking scripts, exes (try 2) (#40758)
_⚠️ targets #40427_ 

This is a different approach to #39059 that I was thinking about like a
month ago. It builds on the work from the rejuv'd run page (#39955) to
process the bookmark as an exe/path/url automatically.

I need to cross-check this with #39059 - I haven't cached that back in
since I got back from leave. I remember thinking that I wanted to try
this approach, but wasn't sure if it was right. More than anything, I
want to get it off my local PC and out for discussion

* We don't need to manually store the type anymore. 
* breaking change: paths with a space do need to be wrapped in spaces

closes #38700

----

I accidentally destroyed #40430 with a fat-finger merge from #40427 into
it. This resurrects that PR
2025-07-28 18:52:25 -05:00
Jiří Polášek
7bd9d973cf CmdPal: Sync access to TopLevelCommandManager from UpdateCommandsForProvider (#40752)
## Summary of the Pull Request

Fixes unsynchronized access to `LoadTopLevelCommands` in
`TopLevelCommandManager.UpdateCommandsForProvider`, which previously led
to `InvalidOperationException: Collection was modified`.

Addressing this also uncovered another issue: overlapping invocations of
`ReloadAllCommandsAsync` were causing duplication of items in the main
list -- so I'm fixing that as well.

## PR Checklist

- [x] Closes
    - Fixes #38194 
    - Partially solves #40776
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:**
- [x] **Localization:** none
- [x] **Dev docs:** none
- [x] **New binaries:** nope
- [x] **Documentation updated:** no need

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
Tested with bookmarks.
2025-07-28 18:51:57 -05:00
Mike Griese
abc812e579 CmdPal: Fix paths to dirs on the Run fallback (#40850)
We were being too clever with `\`; and yet simultaneously not clever
enough.
* When we saw `c:\users`, we'd treat that as a path with a Title
`users\`
* but when we saw `c:\users\`, we'd fail to find a file name, and the
just treat the name as `\`. That was dumb.
* And we'd add trailing `\`'s even if there already was one.
* But then if the user typed `c:\users`, we would immediately start
enumerating children of that dir, which didn't really feel right

This PR fixes all of that.

Closes #40797
2025-07-28 18:50:33 -05:00
Mike Griese
325b1a1441 CmdPal: Remove vestigial try/catch (#40815)
This was added in #38040 but appears to be vestigial now.

RE: #40113
2025-07-28 18:47:18 -05:00
Mike Griese
8829bbac16 CmdPal: Move the OpenContextMenuMessage into the UI project (#40791)
I just blindly moved all the messages. But _this_ one really makes more
sense as a UI message. It's got framework elements. It us used to
actually open a UI element. The whole thing is very UI specific.

re: #40113
2025-07-28 18:46:16 -05:00
Mike Griese
480a2db0cd CmdPal: Bump our package version to 0.4 (#40852)
title

also adds our pdb to the nuget package.
2025-07-28 18:45:16 -05:00
Mike Griese
db9d7a8804 CmdPal: fix handling form submits (#40847)
Yea this was real dumb.

I removed the `HandleCommandResultMessage` handler from `ShellPage`, and
never put it on `ShellViewModel`. Just first-grade kind of mistake.

Closes #40776
Regressed in #40479
re: #40113
2025-07-28 18:42:55 -05:00
Michael Jolley
c16cd4c96f Fixed issue with primary/secondary commands (#40849)
Closes #40822

These are not the classes you are looking for.

Issue was we were comparing to classes rather than interfaces and WinRT
no likey.
2025-07-28 18:26:32 -05:00
Jiří Polášek
4785af2425 CmdPal: Handle exceptions when enqueuing callbacks to UI thread in IconCacheService (#40716)
## Summary of the Pull Request

Handle exceptions thrown in TryEnqueue callbacks so they don’t crash the
app (as they cannot be caught by the global exception handler). Any
exceptions are now returned to the caller for handling. Additionally, a
failure to enqueue the operation onto the dispatcher will also result in
an exception.

This is not a breaking change, as exceptions only propagate within the
class and do not affect external callers.

Ref: #38260

## PR Checklist

- [ ] **Closes:** #38260 
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-07-28 16:41:27 -05:00
Jiří Polášek
f81802430c CmdPal: Handle CommandItem Title changes properly and raise notification every time it changes (#40513)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #39167 
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-07-28 16:40:29 -05:00
Jiří Polášek
4489677b64 CmdPal: Add error handling to extension disposal (#40825)
## Summary of the Pull Request

Ensure that errors encountered while sending the extension disposal
signal are handled gracefully. If an error occurs when disposing of a
particular extension, continue signaling the remaining extensions rather
than halting the entire process. This prevents a single failure from
interrupting the disposal chain and improves overall robustness.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-07-28 16:18:04 -05:00
Jessica Dene Earley-Cha
6242401b40 add AutomationNotification for screen readers (#40761)
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ x ] **Closes:** #38392
- [ x ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx


## Detailed Description of the Pull Request / Additional comments

Add AutomationNotification to ItemsList_SelectionChanged so that when
user uses keyboard navigation, it sends the title to be read by the
screen reader


## Validation Steps Performed


https://github.com/user-attachments/assets/34a11e55-18ce-440f-97d8-e6ea60c57f78

Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-07-28 09:56:01 -05:00
VictorNoxx
c10f2c54ba Update thirdPartyRunPlugins.md (#40790)
```markdown
# PowerToys Run: Add Cursor AI Plugin to Third-Party Plugins List

## Summary of the Pull Request

This PR adds the "Open With Cursor" plugin to the `thirdPartyRunPlugins.md` documentation. The plugin enables users to quickly open Visual Studio and VS Code recent workspaces directly in Cursor AI editor through PowerToys Run launcher.

The plugin provides:
- Quick access to recent VS/VSCode workspaces
- Integration with Cursor AI editor
- PowerToys Run launcher compatibility
- Support for various workspace types (local, WSL, SSH, remote)

## PR Checklist

- [ ] **Closes:** N/A (Documentation update)
- [x] **Communication:** This is a documentation update to list an existing third-party plugin
- [ ] **Tests:** N/A (Documentation change only)
- [ ] **Localization:** N/A (English documentation only)
- [x] **Dev docs:** Updated thirdPartyRunPlugins.md with new plugin entry
- [ ] **New binaries:** N/A (Third-party plugin, not included in PowerToys distribution)
- [ ] **Documentation updated:** This PR updates the documentation

## Detailed Description of the Pull Request / Additional comments
```

**Added entry to thirdPartyRunPlugins.md:**

| [Open With
Cursor](https://github.com/VictorNoxx/PowerToys-Run-Cursor/) |
[VictorNoxx](https://github.com/VictorNoxx) | Open Visual Studio, VS
Code recents with Cursor AI |


**Plugin Details:**
- **Repository:** https://github.com/VictorNoxx/PowerToys-Run-Cursor/
- **Author:** [@VictorNoxx](https://github.com/VictorNoxx)
- **License:** MIT
- **Functionality:** Integrates with PowerToys Run to open recent Visual
Studio and VS Code workspaces directly in Cursor AI editor
- **Inspiration:** Based on the community request from [Issue
#3547](https://github.com/microsoft/PowerToys/issues/3547) and inspired
by [@davidegiacometti's Visual Studio
plugin](https://github.com/davidegiacometti/PowerToys-Run-VisualStudio)

**Technical Implementation:**
- Uses `vswhere.exe` for Visual Studio instance detection
- Parses workspace configuration files and recent project lists
- Direct command-line integration with Cursor AI
- Supports multiple workspace types and remote development scenarios

This plugin fills a gap for developers using Cursor AI who want quick
access to their recent projects without manually navigating through
folders or opening multiple applications.

## Validation Steps Performed

- [x] Verified the plugin repository exists and is publicly accessible
- [x] Confirmed the plugin has proper documentation and README
- [x] Tested the markdown formatting in the documentation
- [x] Verified all links are working correctly
- [x] Confirmed the plugin description accurately reflects functionality
- [x] Checked that the entry follows the same format as other entries in
the list
```
2025-07-28 09:55:26 -05:00
Leilei Zhang
f12d983025 install terminal 2025-07-28 22:54:34 +08:00
Jiří Polášek
498fe75c4a CmdPal: Avoid reentrancy issues when loading more items (#40715)
## Summary of the Pull Request
When checking the HasMoreItems flag, COM can start a nested message
pump, which allows a reentrant call on the XAML UI and causes a fast
fail. This change moves the check off the UI thread to prevent
reentrancy, but the loading flag is set before we know for sure that
there is something to load.

This update also introduces a change: if LoadMore fails, we clear the
loading flag immediately.

## PR Checklist

- [x] **Closes:** #40707 
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed
2025-07-28 09:06:23 -05:00
Leilei Zhang
369babc0f8 update install steps 2025-07-28 21:53:12 +08:00
Michael Jolley
114c3972be CmdPal: Filtering out pinned apps on search (#40785)
Closes #40781 

Filters out TopLevelCommands whose Id matches an app coming from the
`AllAppsCommandProvider.Page.GetItems()`.

Hate adding processing there, but without adding some type of `bool
HideMeOnSearch` to something low enough (like ICommandItem), I don't see
another way to distinguish these.
2025-07-28 08:45:08 -05:00
Leilei Zhang
6834fa24af common out the stange type behavior 2025-07-28 21:36:26 +08:00
Mike Griese
858081ec78 CmdPal: try to fix the context menu crash, again. (#40814)
Cherry-pick of 782ee47. That is probably over-aggressive, but it fixes
it.

Closes  #40633
previously: #40744

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
2025-07-28 08:32:08 -05:00
Mike Griese
81a7b81927 CmdPal: fix content on extension settings (#40794)
Regressed in one of the #40113 prs.

The Core.VM.PageViewModelFactory didn't actually know how to make a
ContentPage, because Core can't handle a FormContent.

But really, the CommandSettingsViewModel shouldn't have ever been in the
.Core namespace, nor should it have used the ContentPageViewModel.

To prevent future mistakes like this,
* I got rid of `Core.ViewModels/PageViewModelFactory`, cause we didn't
need it.
* I made `ContentPageViewModel` abstract, to prevent you from trying to
instantiate it

Closes #40778
2025-07-28 06:44:25 -05:00
Leilei Zhang
14f18f4b95 change cmdpal start logic 2025-07-28 19:17:47 +08:00
Leilei Zhang
4bf2342a09 update find 2025-07-28 17:50:26 +08:00
Leilei Zhang
20243c4b99 install termianl 2025-07-28 17:02:27 +08:00
Leilei Zhang
a19127bf49 check termianl 2025-07-28 13:25:33 +08:00
Leilei Zhang
aeaa5297f9 fix 2025-07-28 12:15:26 +08:00
Leilei Zhang
cd45bad9ce install terminal 2025-07-28 11:39:12 +08:00
Leilei Zhang
2ae636a860 refine find window 2025-07-28 11:34:34 +08:00
Leilei Zhang
3bf8a63dd8 update setText 2025-07-27 11:24:21 +08:00
Brian James
dba7be2619 [CmdPal] Replaced ellipsis button with 'More' label + shortcut textblock (#39838)
This PR improves the command bar UI in the command palette.
- Replaced ellipses "..." with a "More" label (can be changed to
something else. Maybe "Actions" or "Commands")
- Added a textblock for Ctrl + K shortcut
- Removed tooltip that showed Ctrl + K shortcut when hovering over
previous "..."

Special Note:
- The InfoBar.Severity binding was temporarily commented out because of
a built-time error even though 'State' property is present in the
ViewModel. Happy to revisit this if the team can help/confirm the
intended binding context/behavior

Before change:

![image](https://github.com/user-attachments/assets/5bcb171b-7c09-4fce-a39e-38c5ac8988e3)

After change:

![added_cmdpal_label](https://github.com/user-attachments/assets/38d1ccd8-3d39-42d2-9c15-79028d2018e5)

Closes #39501

---------

Co-authored-by: Michael Jolley <mike@baldbeardedbuilder.com>
2025-07-25 15:03:16 -05:00
Leilei Zhang
11b6e15869 update cmdpal start options 2025-07-25 22:35:12 +08:00
Leilei Zhang
f59063d285 update the path 2025-07-25 19:20:11 +08:00
Leilei Zhang
83703d2cf2 fix path issue 2025-07-25 18:12:19 +08:00
275 changed files with 3426 additions and 2586 deletions

View File

@@ -288,3 +288,6 @@ CACHEWRITE
MRUCMPPROC
MRUINFO
REGSTR
# Misc Win32 APIs and PInvokes
INVOKEIDLIST

View File

@@ -123,7 +123,7 @@ jobs:
displayName: Stage UI Test Build Outputs
inputs:
sourceFolder: '$(Build.SourcesDirectory)'
contents: '$(BuildPlatform)/$(BuildConfiguration)/**/*'
contents: '**/$(BuildPlatform)/$(BuildConfiguration)/tests/**/*'
targetFolder: '$(JobOutputDirectory)\$(BuildPlatform)\$(BuildConfiguration)'
- publish: $(JobOutputDirectory)

View File

@@ -11,12 +11,14 @@ parameters:
- name: useLatestWebView2
type: boolean
default: false
- name: useLatestOfficialBuild
type: boolean
default: true
- name: useCurrentBranchBuild
type: boolean
default: false
- name: buildSource
type: string
default: "latestMainOfficialBuild"
displayName: "Build Source"
- name: specificBuildId
type: string
default: "xxxx"
displayName: "Build ID (for specific builds)"
- name: uiTestModules
type: object
default: []
@@ -43,6 +45,7 @@ jobs:
BuildConfiguration: ${{ parameters.configuration }}
SrcPath: $(Build.Repository.LocalPath)
TestArtifactsName: build-${{ variables.BuildPlatform }}-${{ parameters.configuration }}${{ parameters.inputArtifactStem }}
isBuildNow: ${{ eq(parameters.buildSource, 'buildNow') }}
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
${{ if ne(parameters.platform, 'ARM64') }}:
@@ -113,16 +116,17 @@ jobs:
& '$(build.sourcesdirectory)\.pipelines\InstallWinAppDriver.ps1'
displayName: Download and install WinAppDriver
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- ${{ if not(variables.isBuildNow) }}:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'specific'
project: 'Dart'
definition: '76541'
buildVersionToDownload: 'latestFromBranch'
${{ if eq(parameters.useCurrentBranchBuild, true) }}:
branchName: '$(Build.SourceBranch)'
${{ if eq(parameters.buildSource, 'specificBuildId') }}:
buildVersionToDownload: 'specific'
buildId: '${{ parameters.specificBuildId }}'
${{ else }}:
buildVersionToDownload: 'latestFromBranch'
branchName: 'refs/heads/main'
artifactName: 'build-$(BuildPlatform)-Release'
targetPath: '$(Build.ArtifactStagingDirectory)'
@@ -133,7 +137,7 @@ jobs:
patterns: |
**/PowerToysSetup*.exe
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- ${{ if not(variables.isBuildNow) }}:
- ${{ if eq(parameters.installMode, 'peruser') }}:
- pwsh: |-
& "$(build.sourcesdirectory)\.pipelines\installPowerToys.ps1" -InstallMode "PerUser"
@@ -169,7 +173,7 @@ jobs:
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}
useInstallerForTest: ${{ not(variables.isBuildNow) }}
- ${{ if ne(length(parameters.uiTestModules), 0) }}:
- ${{ each module in parameters.uiTestModules }}:
@@ -191,4 +195,4 @@ jobs:
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'
useInstallerForTest: ${{ parameters.useLatestOfficialBuild }}
useInstallerForTest: ${{ not(variables.isBuildNow) }}

View File

@@ -3,6 +3,8 @@ variables:
value: false
- name: EnablePipelineCache
value: true
- name: isBuildNow
value: ${{ eq(parameters.buildSource, 'buildNow') }}
parameters:
- name: buildPlatforms
@@ -19,22 +21,25 @@ parameters:
- name: useLatestWebView2
type: boolean
default: false
- name: useLatestOfficialBuild
type: boolean
default: true
- name: testBothInstallModes
type: boolean
default: true
- name: useCurrentBranchBuild
type: boolean
default: false
- name: buildSource
type: string
default: "latestMainOfficialBuild"
displayName: "Build Source"
values:
- latestMainOfficialBuild
- buildNow
- specificBuildId
- name: specificBuildId
type: string
default: 'xxxx'
displayName: "Build ID (only used when Build Source = specificBuildId)"
- name: uiTestModules
type: object
default: []
stages:
- ${{ each platform in parameters.buildPlatforms }}:
- ${{ if eq(parameters.useLatestOfficialBuild, false) }}:
- ${{ if variables.isBuildNow }}:
- stage: Build_${{ platform }}
displayName: Build ${{ platform }}
dependsOn: []
@@ -58,7 +63,7 @@ stages:
useVSPreview: ${{ parameters.useVSPreview }}
timeoutInMinutes: 90
- ${{ if eq(parameters.useLatestOfficialBuild, true) }}:
- ${{ if not(variables.isBuildNow) }}:
- stage: BuildUITests_${{ platform }}
displayName: Build UI Tests Only
dependsOn: []
@@ -79,7 +84,7 @@ stages:
- ${{ if eq(platform, 'x64') }}:
- stage: Test_x64Win10
displayName: Test x64Win10
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
${{ if not(variables.isBuildNow) }}:
dependsOn:
- BuildUITests_${{ platform }}
${{ else }}:
@@ -91,19 +96,19 @@ stages:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test (when both modes are enabled)
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
# Additional per-user installation test
- ${{ if not(variables.isBuildNow) }}:
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'
@@ -111,7 +116,7 @@ stages:
- ${{ if eq(platform, 'x64') }}:
- stage: Test_x64Win11
displayName: Test x64Win11
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
${{ if not(variables.isBuildNow) }}:
dependsOn:
- BuildUITests_${{ platform }}
${{ else }}:
@@ -123,19 +128,19 @@ stages:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test (when both modes are enabled)
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
# Additional per-user installation test
- ${{ if not(variables.isBuildNow) }}:
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'
@@ -143,7 +148,7 @@ stages:
- ${{ if ne(platform, 'x64') }}:
- stage: Test_${{ platform }}
displayName: Test ${{ platform }}
${{ if eq(parameters.useLatestOfficialBuild, true) }}:
${{ if not(variables.isBuildNow) }}:
dependsOn:
- BuildUITests_${{ platform }}
${{ else }}:
@@ -155,19 +160,19 @@ stages:
platform: ${{ platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
# Additional per-user installation test (when both modes are enabled)
- ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}:
# Additional per-user installation test
- ${{ if not(variables.isBuildNow) }}:
- template: job-test-project.yml
parameters:
platform: ${{ platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }}
useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }}
buildSource: ${{ parameters.buildSource }}
specificBuildId: ${{ parameters.specificBuildId }}
uiTestModules: ${{ parameters.uiTestModules }}
installMode: 'peruser'
jobSuffix: '_PerUser'
jobSuffix: '_PerUser'

View File

@@ -461,6 +461,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.Common", "src\modules\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.FilePreviewer", "src\modules\peek\Peek.FilePreviewer\Peek.FilePreviewer.csproj", "{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peek.UITests", "src\modules\peek\Peek.UITests\Peek.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MarkdownPreviewHandlerCpp", "src\modules\previewpane\MarkdownPreviewHandlerCpp\MarkdownPreviewHandlerCpp.vcxproj", "{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GcodePreviewHandlerCpp", "src\modules\previewpane\GcodePreviewHandlerCpp\GcodePreviewHandlerCpp.vcxproj", "{5A5DD09D-723A-44D3-8F2B-293584C3D731}"
@@ -784,6 +786,8 @@ 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", "{E816D7B0-4688-4ECB-97CC-3D8E798F3829}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -1852,6 +1856,14 @@ Global
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|ARM64.Build.0 = Release|ARM64
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.ActiveCfg = Release|x64
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.Build.0 = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|ARM64.ActiveCfg = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|ARM64.Build.0 = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|x64.ActiveCfg = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|x64.Build.0 = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|ARM64.ActiveCfg = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|ARM64.Build.0 = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|x64.ActiveCfg = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|x64.Build.0 = Release|x64
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|ARM64.ActiveCfg = Debug|ARM64
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|ARM64.Build.0 = Debug|ARM64
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|x64.ActiveCfg = Debug|x64
@@ -2830,6 +2842,14 @@ Global
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.ActiveCfg = Release|x64
{E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.Build.0 = Release|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.ActiveCfg = Debug|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.Build.0 = Debug|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|x64.ActiveCfg = Debug|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|x64.Build.0 = Debug|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.ActiveCfg = Release|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2988,6 +3008,7 @@ Global
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{ED9A1AC6-AEB0-4569-A6E9-E1696182B545} = {2F305555-C296-497E-AC20-5FA1B237996A}
{5A5DD09D-723A-44D3-8F2B-293584C3D731} = {2F305555-C296-497E-AC20-5FA1B237996A}
{B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9} = {2F305555-C296-497E-AC20-5FA1B237996A}
@@ -3117,11 +3138,6 @@ Global
{D9BD324E-1D80-44AA-8E7B-73EB00944434} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{8EF25507-2575-4ADE-BF7E-D23376903AB8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
{070AC093-C9F2-20AD-0BCD-F318FC2761EA} = {B1234567-1234-1234-1234-123456789ABC}
{E816D7AC-4688-4ECB-97CC-3D8E798F3825} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AD-4688-4ECB-97CC-3D8E798F3826} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AE-4688-4ECB-97CC-3D8E798F3827} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{2C318EC3-BA86-4372-B1BC-DB0F33C208B2} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{BFFB607F-7C78-434B-86B9-DA4C8196A1B5} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
{66E1534A-1587-42B2-912F-45C994D32904} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
@@ -3139,6 +3155,12 @@ Global
{806BF185-8B89-5BE1-9AA1-DA5BC9487DB9} = {264B412F-DB8B-4CF8-A74B-96998B183045}
{F93C2817-C846-4259-84D8-B39A6B57C8DE} = {3527BF37-DFC5-4309-A032-29278CA21328}
{8131151D-B0E9-4E18-84A5-E5F946C4480A} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
{E816D7AC-4688-4ECB-97CC-3D8E798F3825} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AD-4688-4ECB-97CC-3D8E798F3826} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AE-4688-4ECB-97CC-3D8E798F3827} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -22,23 +22,23 @@ The PowerToys UI test pipeline provides flexible options for building and testin
### Pipeline Options
- **useLatestOfficialBuild**: When checked, downloads the latest official PowerToys build and installs it for testing. This skips the full solution build and only builds UI test projects.
- **buildSource**: Select the build type for testing:
- `latestMainOfficialBuild`: Downloads and uses the latest official PowerToys build from main branch
- `buildNow`: Builds PowerToys from current source code and uses it for testing
- `specificBuildId`: Downloads a specific PowerToys build using the build ID specified in `specificBuildId` parameter
- **useCurrentBranchBuild**: When checked along with `useLatestOfficialBuild`, downloads the official build from the current branch instead of main.
**Default value**: `latestMainOfficialBuild`
**Default value**: `false` (downloads from main branch)
- **specificBuildId**: When `buildSource` is set to `specificBuildId`, specify the exact PowerToys build ID to download and test against.
**Default value**: `"xxxx"` (placeholder, enter actual build ID when using specificBuildId option)
**When to use this**:
- **Default scenario**: The pipeline tests against the latest signed PowerToys build from the `main` branch, regardless of which branch your test code changes are from
- **Custom branch testing**: Only specify `true` when:
- Your branch has produced its own signed PowerToys build via the official build pipeline
- You want to test against that specific branch's PowerToys build instead of main
- You are testing PowerToys functionality changes that are only available in your branch's build
- Testing against a specific known build for reproducibility
- Regression testing against a particular build version
- Validating fixes in a specific build before release
**Important notes**:
- The test pipeline itself runs from your specified branch, but by default tests against the main branch's PowerToys build
- Not all branches have signed builds available - only use this if you're certain your branch has a signed build
- If enabled but no build exists for your branch, the pipeline may fail or fall back to main
**Usage**: Enter the build ID number (e.g., `12345`) to download that specific build. Only used when `buildSource` is set to `specificBuildId`.
- **uiTestModules**: Specify which UI test modules to build and run. This parameter controls both the `.csproj` projects to build and the `.dll` test assemblies to execute. Examples:
- `['UITests-FancyZones']` - Only FancyZones UI tests
@@ -50,19 +50,19 @@ The PowerToys UI test pipeline provides flexible options for building and testin
### Build Modes
1. **Official Build + Selective Testing** (`useLatestOfficialBuild = true`)
- Downloads and installs official PowerToys build
- Builds only specified UI test projects
- Runs specified UI tests against installed PowerToys
- Controlled by `uiTestModules` parameter
1. **Official Build Testing** (`buildSource = latestMainOfficialBuild` or `specificBuildId`)
- Downloads and installs official PowerToys build (latest from main or specific build ID)
- Builds only UI test projects (all or specific based on `uiTestModules`)
- Runs UI tests against installed PowerToys
- Tests both machine-level and per-user installation modes automatically
2. **Full Build + Testing** (`useLatestOfficialBuild = false`)
- Builds entire PowerToys solution
2. **Current Source Build Testing** (`buildSource = buildNow`)
- Builds entire PowerToys solution from current source code
- Builds UI test projects (all or specific based on `uiTestModules`)
- Runs UI tests (all or specific based on `uiTestModules`)
- Uses freshly built PowerToys for testing
- Runs UI tests against freshly built PowerToys
- Uses artifacts from current pipeline build
> **Note**: Both modes support the `uiTestModules` parameter to control which specific UI test modules to build and run.
> **Note**: All modes support the `uiTestModules` parameter to control which specific UI test modules to build and run. Both machine-level and per-user installation modes are tested automatically when using official builds.
### Pipeline Access
- Pipeline: https://microsoft.visualstudio.com/Dart/_build?definitionId=161438&_a=summary

View File

@@ -49,6 +49,7 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [Definition](https://github.com/ruslanlap/PowerToysRun-Definition) | [ruslanlap](https://github.com/ruslanlap) | Lookup word definitions, phonetics, and synonyms directly in PowerToys Run. |
| [Hotkeys](https://github.com/ruslanlap/PowerToysRun-Hotkeys) | [ruslanlap](https://github.com/ruslanlap) | Create, manage, and trigger custom keyboard shortcuts directly from PowerToys Run. |
| [RandomGen](https://github.com/ruslanlap/PowerToysRun-RandomGen) | [ruslanlap](https://github.com/ruslanlap) | 🎲 Generate random data instantly with a single keystroke. Perfect for developers, testers, designers, and anyone who needs quick access to random data. Features include secure passwords, PINs, names, business data, dates, numbers, GUIDs, color codes, and more. Especially useful for designers who need random color codes and placeholder content. |
| [Open With Cursor](https://github.com/VictorNoxx/PowerToys-Run-Cursor/) | [VictorNoxx](https://github.com/VictorNoxx) | Open Visual Studio, VS Code recents with Cursor AI |
## Extending software plugins

View File

@@ -2,6 +2,8 @@
// 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.Threading.Tasks;
namespace Microsoft.PowerToys.UITest
{
/// <summary>
@@ -25,8 +27,9 @@ namespace Microsoft.PowerToys.UITest
/// </summary>
/// <param name="value">The text to set.</param>
/// <param name="clearText">A value indicating whether to clear the text before setting it. Default value is true</param>
/// <param name="charDelayMS">Delay in milliseconds between each character. Default is 0 (no delay).</param>
/// <returns>The current TextBox instance.</returns>
public TextBox SetText(string value, bool clearText = true)
public TextBox SetText(string value, bool clearText = true, int charDelayMS = 0)
{
if (clearText)
{
@@ -39,10 +42,36 @@ namespace Microsoft.PowerToys.UITest
Task.Delay(500).Wait();
}
PerformAction((actions, windowElement) =>
// TODO: CmdPal bug when inputting text, characters are swallowed too quickly.
// This should be fixed within CmdPal itself.
// Temporary workaround: introduce a delay between character inputs to avoid the issue
if (charDelayMS > 0 || EnvironmentConfig.IsInPipeline)
{
windowElement.SendKeys(value);
});
// Send text character by character with delay (if specified or in pipeline)
PerformAction((actions, windowElement) =>
{
foreach (char c in value)
{
windowElement.SendKeys(c.ToString());
if (charDelayMS > 0)
{
Task.Delay(charDelayMS).Wait();
}
else if (EnvironmentConfig.IsInPipeline)
{
Task.Delay(50).Wait();
}
}
});
}
else
{
// No character delay - send all text at once (original behavior)
PerformAction((actions, windowElement) =>
{
windowElement.SendKeys(value);
});
}
return this;
}

View File

@@ -0,0 +1,45 @@
// 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;
namespace Microsoft.PowerToys.UITest
{
/// <summary>
/// Centralized configuration for all environment variables used in UI tests.
/// </summary>
public static class EnvironmentConfig
{
private static readonly Lazy<bool> _isInPipeline = new(() =>
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform")));
private static readonly Lazy<bool> _useInstallerForTest = new(() =>
{
string? envValue = Environment.GetEnvironmentVariable("useInstallerForTest") ??
Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
return !string.IsNullOrEmpty(envValue) && bool.TryParse(envValue, out bool result) && result;
});
private static readonly Lazy<string?> _platform = new(() =>
Environment.GetEnvironmentVariable("platform"));
/// <summary>
/// Gets a value indicating whether the tests are running in a CI/CD pipeline.
/// Determined by the presence of the "platform" environment variable.
/// </summary>
public static bool IsInPipeline => _isInPipeline.Value;
/// <summary>
/// Gets a value indicating whether to use installer paths for testing.
/// Checks both "useInstallerForTest" and "USEINSTALLERFORTEST" environment variables.
/// </summary>
public static bool UseInstallerForTest => _useInstallerForTest.Value;
/// <summary>
/// Gets the platform name from the environment variable.
/// Typically used in CI/CD pipelines to identify the build platform.
/// </summary>
public static string? Platform => _platform.Value;
}
}

View File

@@ -92,9 +92,7 @@ namespace Microsoft.PowerToys.UITest
private ModuleConfigData()
{
// Check if we should use installer paths from environment variable
string? useInstallerForTestEnv =
Environment.GetEnvironmentVariable("useInstallerForTest") ?? Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
UseInstallerForTest = !string.IsNullOrEmpty(useInstallerForTestEnv) && bool.TryParse(useInstallerForTestEnv, out bool result) && result;
UseInstallerForTest = EnvironmentConfig.UseInstallerForTest;
// Module information including executable name, window name, and optional subdirectory
ModuleInfo = new Dictionary<PowerToysModule, ModuleInfo>

View File

@@ -5,6 +5,7 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
@@ -37,6 +38,9 @@ namespace Microsoft.PowerToys.UITest
private PowerToysModule scope;
private string[]? commandLineArgs;
/// <summary>
/// Gets a value indicating whether to use installer paths for testing.
/// </summary>
private bool UseInstallerForTest { get; }
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
@@ -45,9 +49,7 @@ namespace Microsoft.PowerToys.UITest
this.scope = scope;
this.commandLineArgs = commandLineArgs;
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
string? useInstallerForTestEnv =
Environment.GetEnvironmentVariable("useInstallerForTest") ?? Environment.GetEnvironmentVariable("USEINSTALLERFORTEST");
UseInstallerForTest = !string.IsNullOrEmpty(useInstallerForTestEnv) && bool.TryParse(useInstallerForTestEnv, out bool result) && result;
UseInstallerForTest = EnvironmentConfig.UseInstallerForTest;
this.locationPath = UseInstallerForTest ? string.Empty : Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
CheckWinAppDriverAndRoot();
@@ -136,6 +138,10 @@ namespace Microsoft.PowerToys.UITest
{
TryLaunchPowerToysSettings(opts);
}
else if (scope == PowerToysModule.CommandPalette && UseInstallerForTest)
{
TryLaunchCommandPalette(opts);
}
else
{
opts.AddAdditionalCapability("app", appPath);
@@ -163,48 +169,77 @@ namespace Microsoft.PowerToys.UITest
private void TryLaunchPowerToysSettings(AppiumOptions opts)
{
CheckWinAppDriverAndRoot();
var runnerProcessInfo = new ProcessStartInfo
try
{
FileName = locationPath + runnerPath,
Verb = "runas",
Arguments = "--open-settings",
};
ExitExe(runnerProcessInfo.FileName);
runner = Process.Start(runnerProcessInfo);
Thread.Sleep(5000);
// Exit CmdPal UI before launching new process if use installer for test
ExitExeByName("Microsoft.CmdPal.UI");
if (root != null)
{
const int maxRetries = 5;
const int delayMs = 5000;
var windowName = "PowerToys Settings";
for (int attempt = 1; attempt <= maxRetries; attempt++)
var runnerProcessInfo = new ProcessStartInfo
{
var settingsWindow = ApiHelper.FindDesktopWindowHandler(
[windowName, AdministratorPrefix + windowName]);
FileName = locationPath + runnerPath,
Verb = "runas",
Arguments = "--open-settings",
};
if (settingsWindow.Count > 0)
{
var hexHwnd = settingsWindow[0].HWnd.ToString("x");
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
return;
}
ExitExe(runnerProcessInfo.FileName);
runner = Process.Start(runnerProcessInfo);
if (attempt < maxRetries)
{
Thread.Sleep(delayMs);
}
else
{
throw new TimeoutException("Failed to find PowerToys Settings window after multiple attempts.");
}
WaitForWindowAndSetCapability(opts, "PowerToys Settings", 5000, 5);
// Exit CmdPal UI before launching new process if use installer for test
ExitExeByName("Microsoft.CmdPal.UI");
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to launch PowerToys Settings: {ex.Message}", ex);
}
}
private void TryLaunchCommandPalette(AppiumOptions opts)
{
try
{
// Exit any existing CmdPal UI process
ExitExeByName("Microsoft.CmdPal.UI");
var processStartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c start shell:appsFolder\\Microsoft.CommandPalette_8wekyb3d8bbwe!App",
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
};
var process = Process.Start(processStartInfo);
process?.WaitForExit();
WaitForWindowAndSetCapability(opts, "Command Palette", 5000, 10);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to launch Command Palette: {ex.Message}", ex);
}
}
private void WaitForWindowAndSetCapability(AppiumOptions opts, string windowName, int delayMs, int maxRetries)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
var window = ApiHelper.FindDesktopWindowHandler(
[windowName, AdministratorPrefix + windowName]);
if (window.Count > 0)
{
var hexHwnd = window[0].HWnd.ToString("x");
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
return;
}
if (attempt < maxRetries)
{
Thread.Sleep(delayMs);
}
else
{
throw new TimeoutException($"Failed to find {windowName} window after multiple attempts.");
}
}
}

View File

@@ -5,6 +5,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
@@ -20,6 +21,9 @@ namespace Microsoft.PowerToys.UITest
public required Session Session { get; set; }
/// <summary>
/// Gets a value indicating whether the tests are running in a CI/CD pipeline.
/// </summary>
public bool IsInPipeline { get; }
public string? ScreenshotDirectory { get; set; }
@@ -34,8 +38,8 @@ namespace Microsoft.PowerToys.UITest
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified, string[]? commandLineArgs = null)
{
this.IsInPipeline = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform"));
Console.WriteLine($"Running tests on platform: {Environment.GetEnvironmentVariable("platform")}");
this.IsInPipeline = EnvironmentConfig.IsInPipeline;
Console.WriteLine($"Running tests on platform: {EnvironmentConfig.Platform}");
if (IsInPipeline)
{
NativeMethods.ChangeDisplayResolution(1920, 1080);
@@ -56,6 +60,7 @@ namespace Microsoft.PowerToys.UITest
[TestInitialize]
public void TestInit()
{
KeyboardHelper.SendKeys(Key.Win, Key.M);
CloseOtherApplications();
if (IsInPipeline)
{
@@ -247,6 +252,174 @@ namespace Microsoft.PowerToys.UITest
return this.Session.Has<Element>(name, timeoutMS, global);
}
/// <summary>
/// Finds an element using partial name matching (contains).
/// Useful for finding windows with variable titles like "filename.txt - Notepad" or "filename - Notepad".
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="partialName">Part of the name to search for.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByPartialName<T>(string partialName, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return Session.Find<T>(By.XPath($"//*[contains(@Name, '{partialName}')]"), timeoutMS, global);
}
/// <summary>
/// Finds an element using partial name matching (contains).
/// </summary>
/// <param name="partialName">Part of the name to search for.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByPartialName(string partialName, int timeoutMS = 5000, bool global = false)
{
return FindByPartialName<Element>(partialName, timeoutMS, global);
}
/// <summary>
/// Base method for finding elements by selector and filtering by name pattern.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="selector">The selector to find initial candidates.</param>
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <param name="errorMessage">Custom error message when no element is found.</param>
/// <returns>The found element.</returns>
private T FindByNamePattern<T>(By selector, string namePattern, int timeoutMS = 5000, bool global = false, string? errorMessage = null)
where T : Element, new()
{
var elements = Session.FindAll<T>(selector, timeoutMS, global);
var regex = new Regex(namePattern, RegexOptions.IgnoreCase);
foreach (var element in elements)
{
var name = element.GetAttribute("Name");
if (!string.IsNullOrEmpty(name) && regex.IsMatch(name))
{
return element;
}
}
throw new NoSuchElementException(errorMessage ?? $"No element found matching pattern: {namePattern}");
}
/// <summary>
/// Finds an element using regular expression pattern matching.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="pattern">Regular expression pattern to match against the Name attribute.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByPattern<T>(string pattern, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return FindByNamePattern<T>(By.XPath("//*[@Name]"), pattern, timeoutMS, global, $"No element found matching pattern: {pattern}");
}
/// <summary>
/// Finds an element using regular expression pattern matching.
/// </summary>
/// <param name="pattern">Regular expression pattern to match against the Name attribute.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByPattern(string pattern, int timeoutMS = 5000, bool global = false)
{
return FindByPattern<Element>(pattern, timeoutMS, global);
}
/// <summary>
/// Finds an element by ClassName only.
/// Returns the first element found with the specified ClassName.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByClassName<T>(string className, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return Session.Find<T>(By.ClassName(className), timeoutMS, global);
}
/// <summary>
/// Finds an element by ClassName only.
/// Returns the first element found with the specified ClassName.
/// </summary>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByClassName(string className, int timeoutMS = 5000, bool global = false)
{
return FindByClassName<Element>(className, timeoutMS, global);
}
/// <summary>
/// Finds an element by ClassName and matches its Name attribute using regex pattern matching.
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected T FindByClassNameAndNamePattern<T>(string className, string namePattern, int timeoutMS = 5000, bool global = false)
where T : Element, new()
{
return FindByNamePattern<T>(By.ClassName(className), namePattern, timeoutMS, global, $"No element with ClassName '{className}' found matching name pattern: {namePattern}");
}
/// <summary>
/// Finds an element by ClassName and matches its Name attribute using regex pattern matching.
/// </summary>
/// <param name="className">The ClassName to search for (e.g., "Notepad", "CabinetWClass").</param>
/// <param name="namePattern">Pattern to match against the Name attribute. Supports regex patterns.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found element.</returns>
protected Element FindByClassNameAndNamePattern(string className, string namePattern, int timeoutMS = 5000, bool global = false)
{
return FindByClassNameAndNamePattern<Element>(className, namePattern, timeoutMS, global);
}
/// <summary>
/// Finds a Notepad window regardless of whether the file extension is shown in the title.
/// Handles both "filename.txt - Notepad" and "filename - Notepad" formats.
/// Uses ClassName to efficiently find Notepad windows first, then matches the filename.
/// </summary>
/// <param name="baseFileName">The base filename without extension (e.g., "test" for "test.txt").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found Notepad window element.</returns>
protected Element FindNotepadWindow(string baseFileName, int timeoutMS = 5000, bool global = false)
{
string pattern = $@"^{Regex.Escape(baseFileName)}(\.\w+)?(\s*-\s*|\s+)Notepad$";
return FindByClassNameAndNamePattern("Notepad", pattern, timeoutMS, global);
}
/// <summary>
/// Finds an Explorer window regardless of the folder or file name display format.
/// Handles various Explorer window title formats like "FolderName", "FileName", "FolderName - File Explorer", etc.
/// Uses ClassName to efficiently find Explorer windows first, then matches the folder or file name.
/// </summary>
/// <param name="folderName">The folder or file name to search for (e.g., "Documents", "Desktop", "test.txt").</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found Explorer window element.</returns>
protected Element FindExplorerWindow(string folderName, int timeoutMS = 5000, bool global = false)
{
string pattern = $@"^{Regex.Escape(folderName)}(\s*-\s*(File\s+Explorer|Windows\s+Explorer))?$";
return FindByClassNameAndNamePattern("CabinetWClass", pattern, timeoutMS, global);
}
/// <summary>
/// Finds an Explorer window by partial folder path.
/// Useful when the full path might be displayed in the title.
/// </summary>
/// <param name="partialPath">Part of the folder path to search for.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
/// <returns>The found Explorer window element.</returns>
protected Element FindExplorerByPartialPath(string partialPath, int timeoutMS = 5000, bool global = false)
{
return FindByPartialName(partialPath, timeoutMS, global);
}
/// <summary>
/// Finds all elements by selector.
/// Shortcut for this.Session.FindAll<T>(by, timeoutMS)

View File

@@ -27,10 +27,8 @@ namespace Microsoft.PowerToys.UITest
[RequiresUnreferencedCode("This method uses reflection which may not be compatible with trimming.")]
public static void AreEqual(TestContext? testContext, Element element, string scenarioSubname = "")
{
var pipelinePlatform = Environment.GetEnvironmentVariable("platform");
// Perform visual validation only in the pipeline
if (string.IsNullOrEmpty(pipelinePlatform))
if (!EnvironmentConfig.IsInPipeline)
{
Console.WriteLine("Skip visual validation in the local run.");
return;
@@ -55,11 +53,11 @@ namespace Microsoft.PowerToys.UITest
if (string.IsNullOrWhiteSpace(scenarioSubname))
{
scenarioSubname = string.Join("_", callerClassName, callerName, pipelinePlatform);
scenarioSubname = string.Join("_", callerClassName, callerName, EnvironmentConfig.Platform);
}
else
{
scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim(), pipelinePlatform);
scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim(), EnvironmentConfig.Platform);
}
var baselineImageResourceName = callerMethod!.DeclaringType!.Assembly.GetManifestResourceNames().Where(name => name.Contains(scenarioSubname)).FirstOrDefault();

View File

@@ -1,583 +1,209 @@
# editorconfig: http://editorconfig.org/
# Help developers standardize spaces, tabs, encoding, end-line characters across editors
# Rules in this file were initially inferred by Visual Studio IntelliCode from the Template Studio codebase.
# You can modify the rules from these initially generated values to suit your own policies.
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference.
# top-most .editorconfig file
root = true
# defaults for all files
[*]
charset = utf-8
end_of_line = crlf
tab_width = 4
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.cs]
file_header_template = Copyright (c) Microsoft Corporation\r\nThe Microsoft Corporation licenses this file to you under the MIT license.\r\nSee the LICENSE file in the project root for more information.
# markdown, diff overrides
# two trailing spaces are required for <br/> and hard line-breaks in markdown files
# see: (https://daringfireball.net/projects/markdown/syntax#p) and (http://spec.commonmark.org/0.27/#hard-line-break)
[*.{md,diff}]
trim_trailing_whitespace = false
#Core editorconfig formatting - indentation
[*.{md,xml,xsd,gprops,man,natvis}]
indent_size = 2
#use soft tabs (spaces) for indentation
indent_style = space
# manifest validation tool requires BOM
[*.man]
charset = utf-8-bom
#Formatting - new line options
# XML-based MSBuild and Visual Studio files
[*.{props,targets,settings,*proj,vcxitems,filters,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
# Exceptions to the above *proj wildcard
[*.vdproj]
indent_size = 4
# Visual Studio uses hard tabs for SLN files, so don't fight it
[*.sln]
indent_style = tab
# Visual Studio removes the last empty line, so don't fight it
[*.{vcxproj}]
insert_final_newline = false
# YAML overrides
[*.{yml,yaml}]
indent_size = 2
# package.json overrides
# Updating package.json with NPM will revert indentation to 2 spaces so to
# reduce churn, let's align to NPM and specify indent size 2
[package.json]
indent_size = 2
# HTML5 content (*.html, *.js) included in the AppX package should properly encoded with the UTF-8 byte order mark (BOM) as this is a Store requirement.
[*.{html,js,css}]
charset = utf-8-bom
# JSON formats
[{*.{json,testlist,testpasses,testenv,pluginlist},testmd.definition}]
indent_size = 2
[*.{rc,rc2}]
trim_trailing_whitespace = true
insert_final_newline = true
[*.{c,w}]
trim_trailing_whitespace = true
insert_final_newline = true
[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli,idl}]
trim_trailing_whitespace = true
insert_final_newline = true
cpp_indent_braces = false
cpp_indent_multi_line_relative_to = innermost_parenthesis
cpp_indent_within_parentheses = indent
cpp_indent_preserve_within_parentheses = true
cpp_indent_case_contents = true
cpp_indent_case_labels = false
cpp_indent_case_contents_when_block = false
cpp_indent_lambda_braces_when_parameter = false
cpp_indent_goto_labels = one_left
cpp_indent_preprocessor = leftmost_column
cpp_indent_access_specifiers = false
cpp_indent_namespace_contents = true
cpp_indent_preserve_comments = false
cpp_new_line_before_open_brace_namespace = ignore
cpp_new_line_before_open_brace_type = ignore
cpp_new_line_before_open_brace_function = new_line
cpp_new_line_before_open_brace_block = new_line
cpp_new_line_before_open_brace_lambda = new_line
cpp_new_line_scope_braces_on_separate_lines = true
cpp_new_line_close_brace_same_line_empty_type = false
cpp_new_line_close_brace_same_line_empty_function = false
cpp_new_line_before_catch = true
cpp_new_line_before_else = true
cpp_new_line_before_while_in_do_while = true
cpp_space_before_function_open_parenthesis = remove
cpp_space_within_parameter_list_parentheses = false
cpp_space_between_empty_parameter_list_parentheses = false
cpp_space_after_keywords_in_control_flow_statements = true
cpp_space_within_control_flow_statement_parentheses = false
cpp_space_before_lambda_open_parenthesis = false
cpp_space_within_cast_parentheses = false
cpp_space_after_cast_close_parenthesis = false
cpp_space_within_expression_parentheses = false
cpp_space_before_block_open_brace = true
cpp_space_between_empty_braces = false
cpp_space_before_initializer_list_open_brace = false
cpp_space_within_initializer_list_braces = true
cpp_space_preserve_in_initializer_list = true
cpp_space_before_open_square_bracket = false
cpp_space_within_square_brackets = false
cpp_space_before_empty_square_brackets = false
cpp_space_between_empty_square_brackets = false
cpp_space_group_square_brackets = true
cpp_space_within_lambda_brackets = false
cpp_space_between_empty_lambda_brackets = false
cpp_space_before_comma = false
cpp_space_after_comma = true
cpp_space_remove_around_member_operators = true
cpp_space_before_inheritance_colon = true
cpp_space_before_constructor_colon = true
cpp_space_remove_before_semicolon = true
cpp_space_after_semicolon = true
cpp_space_remove_around_unary_operator = true
cpp_space_around_binary_operator = insert
cpp_space_around_assignment_operator = insert
cpp_space_pointer_reference_alignment = left
cpp_space_around_ternary_operator = insert
cpp_wrap_preserve_blocks = one_liners
# C# overrides
# Rules explanation: https://kent-boogaart.com/blog/editorconfig-reference-for-c-developers
[*.cs]
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
# Indent settings
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_case_contents_when_block = false
csharp_indent_labels = one_less_than_current
# Newline settings
csharp_new_line_before_catch = true
#place else statements on a new line
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
#require braces to be on a new line for lambdas, methods, control_blocks, types, properties, and accessors (also known as "Allman" style)
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
# Braces settings
csharp_prefer_braces = when_multiline:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
#Formatting - organize using options
# Space settings
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Code-block preferences
csharp_prefer_simple_using_statement = true:silent
csharp_style_namespace_declarations = file_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_primary_constructors = true:silent
csharp_style_prefer_top_level_statements = false:silent
# Expression-level preferences
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:silent
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_style_prefer_range_operator = true:silent
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:silent
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# Suggest more modern language features when available
csharp_style_conditional_delegate_call = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_prefer_switch_expression = true:suggestion
csharp_prefer_static_anonymous_function = true:suggestion
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
csharp_style_expression_bodied_indexers = when_on_single_line:suggestion
csharp_style_expression_bodied_properties = when_on_single_line:suggestion
csharp_style_expression_bodied_lambdas = when_on_single_line:silent
csharp_style_expression_bodied_constructors = when_on_single_line:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = when_on_single_line:silent
csharp_style_expression_bodied_operators = when_on_single_line:silent
# Don't suggest "var" when type is apparent, as "new()" is better
csharp_style_var_when_type_is_apparent = false:silent
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent
# Modifier preferences
csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
csharp_style_prefer_readonly_struct = true:suggestion
csharp_style_prefer_readonly_struct_member = true:suggestion
### dotnet ###
# Sort using directives with System.* appearing first
#sort System.* using directives alphabetically, and place them before other usings
dotnet_sort_system_directives_first = true
# Suggest more modern language features when available
dotnet_style_coalesce_expression = true:silent
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_object_initializer = true:suggestion
#Formatting - spacing options
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
#require NO space between a cast and the value
csharp_space_after_cast = false
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_after_colon_in_inheritance_clause = true
#require a space after a keyword in a control flow statement such as a for loop
csharp_space_after_keywords_in_control_flow_statements = true
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_before_colon_in_inheritance_clause = true
#remove space within empty argument list parentheses
csharp_space_between_method_call_empty_parameter_list_parentheses = false
#remove space between method call name and opening parenthesis
csharp_space_between_method_call_name_and_opening_parenthesis = false
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
csharp_space_between_method_call_parameter_list_parentheses = false
#remove space within empty parameter list parentheses for a method declaration
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
csharp_space_between_method_declaration_parameter_list_parentheses = false
#Formatting - wrapping options
#leave code block on separate lines
csharp_preserve_single_line_blocks = true
#Style - Code block preferences
#prefer curly braces even for one line of code
csharp_prefer_braces = true:suggestion
#Style - expression bodied member options
#prefer expression bodies for accessors
csharp_style_expression_bodied_accessors = true:warning
#prefer block bodies for constructors
csharp_style_expression_bodied_constructors = false:suggestion
#prefer expression bodies for methods
csharp_style_expression_bodied_methods = when_on_single_line:silent
#prefer expression-bodied members for properties
csharp_style_expression_bodied_properties = true:warning
#Style - expression level options
#prefer out variables to be declared before the method call
csharp_style_inlined_variable_declaration = false:suggestion
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_member_access = true:suggestion
# Other style
#Style - Expression-level preferences
#prefer default over default(T)
csharp_prefer_simple_default_expression = true:suggestion
#prefer objects to be initialized using object initializers when possible
dotnet_style_object_initializer = true:suggestion
#Style - implicit and explicit types
#prefer var over explicit type in all cases, unless overridden by another code style rule
csharp_style_var_elsewhere = true:suggestion
#prefer var is used to declare variables with built-in system types such as int
csharp_style_var_for_built_in_types = true:suggestion
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
csharp_style_var_when_type_is_apparent = true:suggestion
#Style - language keyword and framework type options
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
#Style - Language rules
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
csharp_style_var_for_built_in_types = true:warning
#Style - modifier options
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
#Style - Modifier preferences
#when this rule is set to a list of modifiers, prefer the specified ordering.
csharp_preferred_modifier_order = public,private,protected,internal,static,async,readonly,override,sealed,abstract,virtual:warning
dotnet_style_readonly_field = true:warning
#Style - Pattern matching
#prefer pattern matching instead of is expression with type casts
csharp_style_pattern_matching_over_as_with_null_check = true:warning
#Style - qualification options
#prefer events not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_event = false:suggestion
#prefer fields not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_field = false:suggestion
#prefer methods not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_method = false:suggestion
#prefer properties not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_property = false:suggestion
csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:warning
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
[*.{cs,vb}]
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_auto_properties = false:silent
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_collection_expression = never
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:silent
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
dotnet_code_quality_unused_parameters = all:suggestion
[*.{cs,vb}]
# Field preferences
dotnet_style_readonly_field = true:suggestion
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
# Avoid "this."
dotnet_style_qualification_for_event = false:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_property = false:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
#Style - Unnecessary code rules
csharp_style_unused_value_assignment_preference = discard_variable:warning
#### Naming styles ####
# Naming rules
dotnet_naming_rule.const_variables_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.const_variables_should_be_pascal_case.symbols = const_variables
dotnet_naming_rule.const_variables_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.static_readonly_field_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.static_readonly_field_should_be_pascal_case.symbols = static_readonly_field
dotnet_naming_rule.static_readonly_field_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.fields__locals__params_should_be_camelcase.severity = suggestion
dotnet_naming_rule.fields__locals__params_should_be_camelcase.symbols = fields__locals__params
dotnet_naming_rule.fields__locals__params_should_be_camelcase.style = camelcase
dotnet_naming_rule.type_parameter_should_be_type_parameter.severity = suggestion
dotnet_naming_rule.type_parameter_should_be_type_parameter.symbols = type_parameter
dotnet_naming_rule.type_parameter_should_be_type_parameter.style = type_parameter
dotnet_naming_rule.all_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.all_should_be_pascal_case.symbols = all
dotnet_naming_rule.all_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.fields__locals__params.applicable_kinds = field, parameter, local
dotnet_naming_symbols.fields__locals__params.applicable_accessibilities = *
dotnet_naming_symbols.fields__locals__params.required_modifiers =
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.type_parameter.applicable_kinds = type_parameter
dotnet_naming_symbols.type_parameter.applicable_accessibilities = *
dotnet_naming_symbols.type_parameter.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.const_variables.applicable_kinds = field, local
dotnet_naming_symbols.const_variables.applicable_accessibilities = *
dotnet_naming_symbols.const_variables.required_modifiers = const
dotnet_naming_symbols.all.applicable_kinds = *
dotnet_naming_symbols.all.applicable_accessibilities = *
dotnet_naming_symbols.all.required_modifiers =
dotnet_naming_symbols.static_readonly_field.applicable_kinds = field
dotnet_naming_symbols.static_readonly_field.applicable_accessibilities = *
dotnet_naming_symbols.static_readonly_field.required_modifiers = readonly, static
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.camelcase.required_prefix =
dotnet_naming_style.camelcase.required_suffix =
dotnet_naming_style.camelcase.word_separator =
dotnet_naming_style.camelcase.capitalization = camel_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_naming_style.type_parameter.required_prefix = T
dotnet_naming_style.type_parameter.required_suffix =
dotnet_naming_style.type_parameter.word_separator =
dotnet_naming_style.type_parameter.capitalization = pascal_case
### Dotnet diagnostics ###
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/categories
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-options#severity-level
# The rule for assigning severity is:
# error (build break) - this is always incorrect code; e.g., recursively assigning a property to itself
# warning (build break) - this is possibly correct, but too error-prone to be used without calling it out; e.g., testing for NaN correctly, or hidden perf costs
# suggestion (IDE squiggles and IDE message) - this violates (or is very very very likely to violate) a CoreGuideline
# silent (no squiggles or message, but refactoring may be avaliable) - this violates a CoreGuideline, but with a non-neglagable but not large false-positive rate
# none (no user notification) - this violates a CoreGuideline, but with high false-positive rate; is inconsistent with the CoreGuidelines; or is otherwise incorrect
# Default diagnostics
# https://github.com/dotnet/roslyn-analyzers/blob/main/src/NetAnalyzers/Core/AnalyzerReleases.Shipped.md
# Note that in this list "info" = suggestion, "hidden" = silent, and "disabled" = none in terms of the severity field:
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-options
# We can only enable the "Default" set of diagnostics automatically and not the "Recommended" set,
# as we'd get warnings for non-breaking issues, so we have to manually adjust a bunch of severities.
# "hidden" issues still may appear in IDE refactorings, so a rule must be disabled with "none" explicitly if not needed.
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/design-warnings
dotnet_diagnostic.CA1001.severity = warning
dotnet_diagnostic.CA1003.severity = suggestion
dotnet_diagnostic.CA1005.severity = suggestion
dotnet_diagnostic.CA1008.severity = suggestion
dotnet_diagnostic.CA1010.severity = suggestion
dotnet_diagnostic.CA1012.severity = warning
dotnet_diagnostic.CA1016.severity = none
dotnet_diagnostic.CA1019.severity = suggestion
dotnet_diagnostic.CA1024.severity = suggestion
dotnet_diagnostic.CA1027.severity = suggestion
dotnet_diagnostic.CA1028.severity = suggestion
dotnet_diagnostic.CA1030.severity = suggestion
dotnet_diagnostic.CA1032.severity = suggestion
dotnet_diagnostic.CA1033.severity = suggestion
dotnet_diagnostic.CA1034.severity = silent
dotnet_diagnostic.CA1036.severity = suggestion
dotnet_diagnostic.CA1040.severity = suggestion
dotnet_diagnostic.CA1052.severity = suggestion
dotnet_diagnostic.CA1054.severity = suggestion
dotnet_diagnostic.CA1055.severity = suggestion
dotnet_diagnostic.CA1056.severity = suggestion
dotnet_diagnostic.CA1058.severity = warning
dotnet_diagnostic.CA1060.severity = silent
dotnet_diagnostic.CA1063.severity = suggestion
dotnet_diagnostic.CA1064.severity = suggestion
dotnet_diagnostic.CA1065.severity = suggestion
dotnet_diagnostic.CA1066.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/globalization-warnings
dotnet_diagnostic.CA1303.severity = suggestion
dotnet_diagnostic.CA1307.severity = suggestion
dotnet_diagnostic.CA1308.severity = suggestion
dotnet_diagnostic.CA1309.severity = suggestion
dotnet_diagnostic.CA1310.severity = suggestion
dotnet_diagnostic.CA1311.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/interoperability-warnings
dotnet_diagnostic.CA1419.severity = warning
dotnet_diagnostic.CA1421.severity = warning
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/maintainability-warnings
dotnet_diagnostic.CA1501.severity = suggestion
dotnet_diagnostic.CA1502.severity = suggestion
dotnet_diagnostic.CA1505.severity = suggestion
dotnet_diagnostic.CA1506.severity = suggestion
dotnet_diagnostic.CA1508.severity = suggestion
dotnet_diagnostic.CA1509.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/naming-warnings
dotnet_diagnostic.CA1700.severity = suggestion
dotnet_diagnostic.CA1707.severity = suggestion
dotnet_diagnostic.CA1710.severity = suggestion
dotnet_diagnostic.CA1711.severity = suggestion
dotnet_diagnostic.CA1712.severity = suggestion
dotnet_diagnostic.CA1713.severity = suggestion
dotnet_diagnostic.CA1715.severity = suggestion
dotnet_diagnostic.CA1716.severity = warning
dotnet_diagnostic.CA1721.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/performance-warnings
dotnet_diagnostic.CA1802.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion
dotnet_diagnostic.CA1806.severity = warning
# Flag any accidental use of static Equals without checking the return value, scenarios like "StringAssert.Equals" are falling through to a method
# that doesn't do what the caller expects.
dotnet_code_quality.CA1806.additional_use_results_methods = Equals
dotnet_diagnostic.CA1810.severity = suggestion
dotnet_diagnostic.CA1812.severity = suggestion
dotnet_diagnostic.CA1813.severity = suggestion
dotnet_diagnostic.CA1815.severity = suggestion
dotnet_diagnostic.CA1820.severity = suggestion
dotnet_diagnostic.CA1823.severity = suggestion
dotnet_diagnostic.CA1838.severity = suggestion
dotnet_diagnostic.CA1849.severity = suggestion
dotnet_diagnostic.CA1851.severity = suggestion
dotnet_diagnostic.CA1852.severity = suggestion
dotnet_diagnostic.CA1867.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/reliability-warnings
dotnet_diagnostic.CA2000.severity = warning
dotnet_diagnostic.CA2002.severity = warning
dotnet_diagnostic.CA2008.severity = warning
dotnet_diagnostic.CA2011.severity = warning
dotnet_diagnostic.CA2012.severity = warning
dotnet_diagnostic.CA2019.severity = warning
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/security-warnings
dotnet_diagnostic.CA2100.severity = warning
dotnet_diagnostic.CA2119.severity = warning
dotnet_diagnostic.CA2153.severity = warning
dotnet_diagnostic.CA2300.severity = warning
dotnet_diagnostic.CA2301.severity = warning
dotnet_diagnostic.CA2302.severity = warning
dotnet_diagnostic.CA2305.severity = warning
dotnet_diagnostic.CA2310.severity = warning
dotnet_diagnostic.CA2311.severity = warning
dotnet_diagnostic.CA2312.severity = warning
dotnet_diagnostic.CA2315.severity = warning
dotnet_diagnostic.CA2321.severity = warning
dotnet_diagnostic.CA2322.severity = warning
dotnet_diagnostic.CA2326.severity = warning
dotnet_diagnostic.CA2327.severity = warning
dotnet_diagnostic.CA2328.severity = warning
dotnet_diagnostic.CA2329.severity = warning
dotnet_diagnostic.CA2330.severity = warning
dotnet_diagnostic.CA2350.severity = warning
dotnet_diagnostic.CA2351.severity = warning
dotnet_diagnostic.CA2352.severity = warning
dotnet_diagnostic.CA2353.severity = warning
dotnet_diagnostic.CA2354.severity = warning
dotnet_diagnostic.CA2355.severity = warning
dotnet_diagnostic.CA2356.severity = warning
dotnet_diagnostic.CA2361.severity = warning
dotnet_diagnostic.CA2362.severity = warning
dotnet_diagnostic.CA3001.severity = warning
dotnet_diagnostic.CA3002.severity = warning
dotnet_diagnostic.CA3003.severity = warning
dotnet_diagnostic.CA3004.severity = warning
dotnet_diagnostic.CA3005.severity = warning
dotnet_diagnostic.CA3006.severity = warning
dotnet_diagnostic.CA3007.severity = warning
dotnet_diagnostic.CA3008.severity = warning
dotnet_diagnostic.CA3009.severity = warning
dotnet_diagnostic.CA3010.severity = warning
dotnet_diagnostic.CA3011.severity = warning
dotnet_diagnostic.CA3012.severity = warning
dotnet_diagnostic.CA3061.severity = warning
dotnet_diagnostic.CA3075.severity = warning
dotnet_diagnostic.CA3076.severity = warning
dotnet_diagnostic.CA3077.severity = warning
dotnet_diagnostic.CA3147.severity = warning
dotnet_diagnostic.CA5350.severity = warning
dotnet_diagnostic.CA5351.severity = warning
dotnet_diagnostic.CA5358.severity = warning
dotnet_diagnostic.CA5359.severity = warning
dotnet_diagnostic.CA5360.severity = warning
dotnet_diagnostic.CA5361.severity = warning
dotnet_diagnostic.CA5362.severity = warning
dotnet_diagnostic.CA5363.severity = warning
dotnet_diagnostic.CA5364.severity = warning
dotnet_diagnostic.CA5365.severity = warning
dotnet_diagnostic.CA5366.severity = warning
dotnet_diagnostic.CA5367.severity = warning
dotnet_diagnostic.CA5368.severity = warning
dotnet_diagnostic.CA5369.severity = warning
dotnet_diagnostic.CA5370.severity = warning
dotnet_diagnostic.CA5371.severity = warning
dotnet_diagnostic.CA5372.severity = warning
dotnet_diagnostic.CA5373.severity = warning
dotnet_diagnostic.CA5374.severity = warning
dotnet_diagnostic.CA5375.severity = warning
dotnet_diagnostic.CA5376.severity = warning
dotnet_diagnostic.CA5377.severity = warning
dotnet_diagnostic.CA5378.severity = warning
dotnet_diagnostic.CA5379.severity = warning
dotnet_diagnostic.CA5380.severity = warning
dotnet_diagnostic.CA5381.severity = warning
dotnet_diagnostic.CA5382.severity = warning
dotnet_diagnostic.CA5383.severity = warning
dotnet_diagnostic.CA5384.severity = warning
dotnet_diagnostic.CA5385.severity = warning
dotnet_diagnostic.CA5386.severity = warning
dotnet_diagnostic.CA5387.severity = warning
dotnet_diagnostic.CA5388.severity = warning
dotnet_diagnostic.CA5389.severity = warning
dotnet_diagnostic.CA5390.severity = warning
dotnet_diagnostic.CA5391.severity = warning
dotnet_diagnostic.CA5392.severity = silent
dotnet_diagnostic.CA5393.severity = warning
dotnet_diagnostic.CA5394.severity = warning
dotnet_diagnostic.CA5395.severity = warning
dotnet_diagnostic.CA5396.severity = warning
dotnet_diagnostic.CA5397.severity = warning
dotnet_diagnostic.CA5398.severity = warning
dotnet_diagnostic.CA5399.severity = warning
dotnet_diagnostic.CA5400.severity = warning
dotnet_diagnostic.CA5401.severity = warning
dotnet_diagnostic.CA5402.severity = warning
dotnet_diagnostic.CA5403.severity = warning
dotnet_diagnostic.CA5404.severity = warning
dotnet_diagnostic.CA5405.severity = warning
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/usage-warnings
dotnet_diagnostic.CA2201.severity = warning
dotnet_diagnostic.CA2207.severity = suggestion
dotnet_diagnostic.CA2211.severity = warning
dotnet_diagnostic.CA2213.severity = warning
dotnet_diagnostic.CA2215.severity = suggestion
dotnet_diagnostic.CA2217.severity = warning
dotnet_diagnostic.CA2219.severity = warning
dotnet_diagnostic.CA2226.severity = suggestion
dotnet_diagnostic.CA2234.severity = suggestion
dotnet_diagnostic.CA2242.severity = warning
dotnet_diagnostic.CA2243.severity = suggestion
dotnet_diagnostic.CA2245.severity = warning
dotnet_diagnostic.CA2251.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/
# required by CELA
dotnet_diagnostic.IDE0073.severity = suggestion
# Diagnostic configuration
# CS8305: Type is for evaluation purposes only and is subject to change or removal in future updates.
dotnet_diagnostic.CS8305.severity = suggestion

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,46 @@
// 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.Threading;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Thread-safe boolean implementation using atomic operations
/// </summary>
public struct InterlockedBoolean(bool initialValue = false)
{
private int _value = initialValue ? 1 : 0;
/// <summary>
/// Gets or sets the boolean value atomically
/// </summary>
public bool Value
{
get => Volatile.Read(ref _value) == 1;
set => Interlocked.Exchange(ref _value, value ? 1 : 0);
}
/// <summary>
/// Atomically sets the value to true
/// </summary>
/// <returns>True if the value was previously false, false if it was already true</returns>
public bool Set()
{
return Interlocked.Exchange(ref _value, 1) == 0;
}
/// <summary>
/// Atomically sets the value to false
/// </summary>
/// <returns>True if the value was previously true, false if it was already false</returns>
public bool Clear()
{
return Interlocked.Exchange(ref _value, 0) == 1;
}
public override int GetHashCode() => Value.GetHashCode();
public override string ToString() => Value.ToString();
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -13,10 +13,10 @@ public static partial class NativeEventWaiter
{
public static void WaitForEventLoop(string eventName, Action callback)
{
DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread();
Thread t = new Thread(() =>
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
var t = new Thread(() =>
{
EventWaitHandle eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
while (true)
{
if (eventHandle.WaitOne())
@@ -24,10 +24,9 @@ public static partial class NativeEventWaiter
dispatcherQueue.TryEnqueue(() => callback());
}
}
})
{
IsBackground = true,
};
});
t.IsBackground = true;
t.Start();
}
}

View File

@@ -0,0 +1,139 @@
// 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.Threading;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// An async gate that ensures only one operation runs at a time.
/// If ExecuteAsync is called while already executing, it cancels the current execution
/// and starts the operation again (superseding behavior).
/// </summary>
public class SupersedingAsyncGate : IDisposable
{
private readonly Func<CancellationToken, Task> _action;
private readonly Lock _lock = new();
private int _callId;
private TaskCompletionSource<bool>? _currentTcs;
private CancellationTokenSource? _currentCancellationSource;
private Task? _executingTask;
public SupersedingAsyncGate(Func<CancellationToken, Task> action)
{
ArgumentNullException.ThrowIfNull(action);
_action = action;
}
/// <summary>
/// Executes the configured action. If another execution is running, this call will
/// cancel the current execution and restart the operation.
/// </summary>
/// <param name="cancellationToken">Optional external cancellation token</param>
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
TaskCompletionSource<bool> tcs;
lock (_lock)
{
_currentCancellationSource?.Cancel();
_currentTcs?.TrySetException(new OperationCanceledException("Superseded by newer call"));
tcs = new();
_currentTcs = tcs;
_callId++;
var shouldStartExecution = _executingTask is null;
if (shouldStartExecution)
{
_executingTask = Task.Run(ExecuteLoop, CancellationToken.None);
}
}
await using var ctr = cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken));
await tcs.Task;
}
private async Task ExecuteLoop()
{
try
{
while (true)
{
TaskCompletionSource<bool>? currentTcs;
CancellationTokenSource? currentCts;
int currentCallId;
lock (_lock)
{
currentTcs = _currentTcs;
currentCallId = _callId;
if (currentTcs is null)
{
break;
}
_currentCancellationSource?.Dispose();
_currentCancellationSource = new();
currentCts = _currentCancellationSource;
}
try
{
await _action(currentCts.Token);
CompleteIfCurrent(currentTcs, currentCallId, static t => t.TrySetResult(true));
}
catch (OperationCanceledException)
{
CompleteIfCurrent(currentTcs, currentCallId, tcs => tcs.SetCanceled(currentCts.Token));
}
catch (Exception ex)
{
CompleteIfCurrent(currentTcs, currentCallId, tcs => tcs.TrySetException(ex));
}
}
}
finally
{
lock (_lock)
{
_currentTcs = null;
_currentCancellationSource?.Dispose();
_currentCancellationSource = null;
_executingTask = null;
}
}
}
private void CompleteIfCurrent(
TaskCompletionSource<bool> candidate,
int id,
Action<TaskCompletionSource<bool>> complete)
{
lock (_lock)
{
if (_currentTcs == candidate && _callId == id)
{
complete(candidate);
_currentTcs = null;
}
}
}
public void Dispose()
{
lock (_lock)
{
_currentCancellationSource?.Cancel();
_currentCancellationSource?.Dispose();
_currentTcs?.TrySetException(new ObjectDisposedException(nameof(SupersedingAsyncGate)));
_currentTcs = null;
}
GC.SuppressFinalize(this);
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -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>

View File

@@ -9,3 +9,7 @@ GetWindowRect
GetMonitorInfo
SetWindowPos
MonitorFromWindow
SHOW_WINDOW_CMD
ShellExecuteEx
SEE_MASK_INVOKEIDLIST

View 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);
}
}
}
}

View File

@@ -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>

View File

@@ -55,7 +55,7 @@ public interface IExtensionWrapper
/// <summary>
/// Gets the Unique Id for the extension
/// </summary>
string ExtensionUniqueId { get; }
public string ExtensionUniqueId { get; }
/// <summary>
/// Checks whether we have a reference to the extension process and we are able to call methods on the interface.

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -79,7 +79,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
{
try
{
StatusMessageViewModel? vm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
var vm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
if (vm != null)
{
StatusMessages.Remove(vm);
@@ -96,7 +96,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
public void ProcessLogMessage(ILogMessage message)
{
LogMessageViewModel vm = new LogMessageViewModel(message, _globalLogPageContext);
var vm = new LogMessageViewModel(message, _globalLogPageContext);
vm.SafeInitializePropertiesSynchronous();
Task.Factory.StartNew(
@@ -112,7 +112,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
public void ProcessStatusMessage(IStatusMessage message, StatusContext context)
{
// If this message is already in the list of messages, just bring it to the top
StatusMessageViewModel? oldVm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
var oldVm = StatusMessages.Where(messageVM => messageVM.Model.Unsafe == message).FirstOrDefault();
if (oldVm != null)
{
Task.Factory.StartNew(
@@ -127,7 +127,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
return;
}
StatusMessageViewModel vm = new StatusMessageViewModel(message, new(_globalLogPageContext));
var vm = new StatusMessageViewModel(message, new(_globalLogPageContext));
vm.SafeInitializePropertiesSynchronous();
Task.Factory.StartNew(

View File

@@ -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.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
@@ -125,12 +126,12 @@ public partial class CommandBarViewModel : ObservableObject,
public ContextKeybindingResult CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
{
Dictionary<CommandPalette.Extensions.KeyChord, CommandContextItemViewModel>? keybindings = SelectedItem?.Keybindings();
var keybindings = SelectedItem?.Keybindings();
if (keybindings != null)
{
// Does the pressed key match any of the keybindings?
CommandPalette.Extensions.KeyChord pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
if (keybindings.TryGetValue(pressedKeyChord, out CommandContextItemViewModel? matchedItem))
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
if (keybindings.TryGetValue(pressedKeyChord, out var matchedItem))
{
return matchedItem != null ? PerformCommand(matchedItem) : ContextKeybindingResult.Unhandled;
}

View File

@@ -28,7 +28,7 @@ public partial class CommandContextItemViewModel(ICommandContextItem contextItem
base.InitializeProperties();
ICommandContextItem? contextItem = Model.Unsafe;
var contextItem = Model.Unsafe;
if (contextItem == null)
{
return; // throw?

View File

@@ -97,7 +97,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
return;
}
ICommandItem? model = _commandItemModel.Unsafe;
var model = _commandItemModel.Unsafe;
if (model == null)
{
return;
@@ -125,7 +125,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
FastInitializeProperties();
}
ICommandItem? model = _commandItemModel.Unsafe;
var model = _commandItemModel.Unsafe;
if (model == null)
{
return;
@@ -133,7 +133,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
Command.InitializeProperties();
IIconInfo listIcon = model.Icon;
var listIcon = model.Icon;
if (listIcon != null)
{
_listItemIcon = new(listIcon);
@@ -169,25 +169,25 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
InitializeProperties();
}
ICommandItem? model = _commandItemModel.Unsafe;
var model = _commandItemModel.Unsafe;
if (model == null)
{
return;
}
IContextItem[] more = model.MoreCommands;
var more = model.MoreCommands;
if (more != null)
{
MoreCommands = more
.Select<IContextItem, IContextItemViewModel>(item =>
.Select(item =>
{
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext);
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
}
else
{
return new SeparatorContextItemViewModel();
return new SeparatorContextItemViewModel() as IContextItemViewModel;
}
})
.ToList();
@@ -297,7 +297,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
protected virtual void FetchProperty(string propertyName)
{
ICommandItem? model = this._commandItemModel.Unsafe;
var model = this._commandItemModel.Unsafe;
if (model == null)
{
return; // throw?
@@ -313,6 +313,10 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
Command = new(model.Command, PageContext);
Command.InitializeProperties();
// Extensions based on Command Palette SDK < 0.3 CommandItem class won't notify when Title changes because Command
// or Command.Name change. This is a workaround to ensure that the Title is always up-to-date for extensions with old SDK.
_itemTitle = model.Title;
UpdateProperty(nameof(Name));
UpdateProperty(nameof(Title));
UpdateProperty(nameof(Icon));
@@ -332,19 +336,19 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
break;
case nameof(model.MoreCommands):
IContextItem[] more = model.MoreCommands;
var more = model.MoreCommands;
if (more != null)
{
List<IContextItemViewModel> newContextMenu = more
.Select<IContextItem, IContextItemViewModel>(item =>
var newContextMenu = more
.Select(item =>
{
if (item is CommandContextItem contextItem)
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext);
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
}
else
{
return new SeparatorContextItemViewModel();
return new SeparatorContextItemViewModel() as IContextItemViewModel;
}
})
.ToList();
@@ -381,10 +385,18 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
private void Command_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
string? propertyName = e.PropertyName;
var propertyName = e.PropertyName;
switch (propertyName)
{
case nameof(Command.Name):
// Extensions based on Command Palette SDK < 0.3 CommandItem class won't notify when Title changes because Command
// or Command.Name change. This is a workaround to ensure that the Title is always up-to-date for extensions with old SDK.
var model = _commandItemModel.Unsafe;
if (model != null)
{
_itemTitle = model.Title;
}
UpdateProperty(nameof(Title));
UpdateProperty(nameof(Name));
break;
@@ -415,7 +427,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
Command.PropertyChanged -= Command_PropertyChanged;
Command.SafeCleanup();
ICommandItem? model = _commandItemModel.Unsafe;
var model = _commandItemModel.Unsafe;
if (model != null)
{
model.PropChanged -= Model_PropChanged;

View File

@@ -43,7 +43,7 @@ public partial class CommandViewModel : ExtensionObjectViewModel
return;
}
ICommand? model = Model.Unsafe;
var model = Model.Unsafe;
if (model == null)
{
return;
@@ -66,13 +66,13 @@ public partial class CommandViewModel : ExtensionObjectViewModel
FastInitializeProperties();
}
ICommand? model = Model.Unsafe;
var model = Model.Unsafe;
if (model == null)
{
return;
}
IIconInfo ico = model.Icon;
var ico = model.Icon;
if (ico != null)
{
Icon = new(ico);
@@ -97,7 +97,7 @@ public partial class CommandViewModel : ExtensionObjectViewModel
protected void FetchProperty(string propertyName)
{
ICommand? model = Model.Unsafe;
var model = Model.Unsafe;
if (model == null)
{
return; // throw?
@@ -109,7 +109,7 @@ public partial class CommandViewModel : ExtensionObjectViewModel
Name = model.Name;
break;
case nameof(Icon):
IIconInfo iconInfo = model.Icon;
var iconInfo = model.Icon;
Icon = new(iconInfo);
Icon.InitializeProperties();
break;
@@ -124,7 +124,7 @@ public partial class CommandViewModel : ExtensionObjectViewModel
Icon = new(null); // necessary?
ICommand? model = Model.Unsafe;
var model = Model.Unsafe;
if (model != null)
{
model.PropChanged -= Model_PropChanged;

View File

@@ -24,7 +24,7 @@ public partial class ConfirmResultViewModel(IConfirmationArgs _args, WeakReferen
public override void InitializeProperties()
{
IConfirmationArgs? model = Model.Unsafe;
var model = Model.Unsafe;
if (model == null)
{
return;

View File

@@ -14,7 +14,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
private readonly ExtensionObject<IContentPage> _model;
@@ -62,11 +62,11 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
List<ContentViewModel> newContent = [];
try
{
IContent[] newItems = _model.Unsafe!.GetContent();
var newItems = _model.Unsafe!.GetContent();
foreach (IContent? item in newItems)
foreach (var item in newItems)
{
ContentViewModel? viewModel = ViewModelFromContent(item, PageContext);
var viewModel = ViewModelFromContent(item, PageContext);
if (viewModel != null)
{
viewModel.InitializeProperties();
@@ -80,7 +80,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
throw;
}
bool oneContent = newContent.Count == 1;
var oneContent = newContent.Count == 1;
newContent.ForEach(c => c.OnlyControlOnPage = oneContent);
// Now, back to a UI thread to update the observable collection
@@ -103,7 +103,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
base.InitializeProperties();
IContentPage? model = _model.Unsafe;
var model = _model.Unsafe;
if (model == null)
{
return; // throw?
@@ -113,7 +113,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
.ToList()
.Select<IContextItem, IContextItemViewModel>(item =>
{
if (item is CommandContextItem contextItem)
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext);
}
@@ -132,7 +132,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
contextItem.InitializeProperties();
});
IDetails extensionDetails = model.Details;
var extensionDetails = model.Details;
if (extensionDetails != null)
{
Details = new(extensionDetails, PageContext);
@@ -155,7 +155,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
base.FetchProperty(propertyName);
IContentPage? model = this._model.Unsafe;
var model = this._model.Unsafe;
if (model == null)
{
return; // throw?
@@ -165,14 +165,14 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
case nameof(Commands):
IContextItem[] more = model.Commands;
var more = model.Commands;
if (more != null)
{
List<IContextItemViewModel> newContextMenu = more
var newContextMenu = more
.ToList()
.Select(item =>
{
if (item is CommandContextItem contextItem)
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
}
@@ -215,7 +215,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
break;
case nameof(Details):
IDetails extensionDetails = model.Details;
var extensionDetails = model.Details;
Details = extensionDetails != null ? new(extensionDetails, PageContext) : null;
UpdateDetails();
break;
@@ -277,14 +277,14 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
Commands.Clear();
foreach (ContentViewModel item in Content)
foreach (var item in Content)
{
item.SafeCleanup();
}
Content.Clear();
IContentPage? model = _model.Unsafe;
var model = _model.Unsafe;
if (model != null)
{
model.ItemsChanged -= Model_ItemsChanged;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -8,13 +8,13 @@ using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Diagnostics.Utilities;
using Windows.System;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ContextMenuViewModel : ObservableObject,
IRecipient<UpdateCommandBarMessage>,
IRecipient<OpenContextMenuMessage>
IRecipient<UpdateCommandBarMessage>
{
public ICommandBarContext? SelectedItem
{
@@ -42,7 +42,6 @@ public partial class ContextMenuViewModel : ObservableObject,
public ContextMenuViewModel()
{
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
WeakReferenceMessenger.Default.Register<OpenContextMenuMessage>(this);
}
public void Receive(UpdateCommandBarMessage message)
@@ -50,16 +49,6 @@ public partial class ContextMenuViewModel : ObservableObject,
SelectedItem = message.ViewModel;
}
public void Receive(OpenContextMenuMessage message)
{
FilterOnTop = message.ContextMenuFilterLocation == ContextMenuFilterLocation.Top;
ResetContextMenu();
OnPropertyChanging(nameof(FilterOnTop));
OnPropertyChanged(nameof(FilterOnTop));
}
public void UpdateContextItems()
{
if (SelectedItem != null)
@@ -98,11 +87,11 @@ public partial class ContextMenuViewModel : ObservableObject,
return;
}
IEnumerable<CommandContextItemViewModel> commands = CurrentContextMenu
var commands = CurrentContextMenu
.OfType<CommandContextItemViewModel>()
.Where(c => c.ShouldBeVisible);
IEnumerable<CommandContextItemViewModel> newResults = ListHelpers.FilterList<CommandContextItemViewModel>(commands, searchText, ScoreContextCommand);
var newResults = ListHelpers.FilterList<CommandContextItemViewModel>(commands, searchText, ScoreContextCommand);
ListHelpers.InPlaceUpdateList(FilteredItems, newResults);
}
@@ -118,9 +107,9 @@ public partial class ContextMenuViewModel : ObservableObject,
return 0;
}
MatchResult nameMatch = StringMatcher.FuzzySearch(query, item.Title);
var nameMatch = StringMatcher.FuzzySearch(query, item.Title);
MatchResult descriptionMatch = StringMatcher.FuzzySearch(query, item.Subtitle);
var descriptionMatch = StringMatcher.FuzzySearch(query, item.Subtitle);
return new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, 0 }.Max();
}
@@ -150,12 +139,12 @@ public partial class ContextMenuViewModel : ObservableObject,
public ContextKeybindingResult? CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
{
Dictionary<KeyChord, CommandContextItemViewModel> keybindings = Keybindings();
var keybindings = Keybindings();
if (keybindings != null)
{
// Does the pressed key match any of the keybindings?
KeyChord pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
if (keybindings.TryGetValue(pressedKeyChord, out CommandContextItemViewModel? item))
var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
if (keybindings.TryGetValue(pressedKeyChord, out var item))
{
return InvokeCommand(item);
}
@@ -191,7 +180,7 @@ public partial class ContextMenuViewModel : ObservableObject,
ListHelpers.InPlaceUpdateList(FilteredItems, [.. CurrentContextMenu!]);
}
private void ResetContextMenu()
public void ResetContextMenu()
{
while (ContextMenuStack.Count > 1)
{

View File

@@ -21,7 +21,7 @@ public partial class DetailsCommandsViewModel(
public override void InitializeProperties()
{
base.InitializeProperties();
IDetailsCommands? model = _dataModel.Unsafe;
var model = _dataModel.Unsafe;
if (model == null)
{
return;
@@ -31,7 +31,7 @@ public partial class DetailsCommandsViewModel(
.Commands?
.Select(c =>
{
CommandViewModel vm = new CommandViewModel(c, PageContext);
var vm = new CommandViewModel(c, PageContext);
vm.InitializeProperties();
return vm;
})

View File

@@ -2,6 +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 Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class DetailsDataViewModel(IPageContext context) : ExtensionObjectViewModel(context)

View File

@@ -15,7 +15,7 @@ public abstract partial class DetailsElementViewModel(IDetailsElement _detailsEl
public override void InitializeProperties()
{
IDetailsElement? model = _model.Unsafe;
var model = _model.Unsafe;
if (model == null)
{
return;

View File

@@ -25,7 +25,7 @@ public partial class DetailsLinkViewModel(
public override void InitializeProperties()
{
base.InitializeProperties();
IDetailsLink? model = _dataModel.Unsafe;
var model = _dataModel.Unsafe;
if (model == null)
{
return;

View File

@@ -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.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -10,6 +11,9 @@ public partial class DetailsSeparatorViewModel(
IDetailsElement _detailsElement,
WeakReference<IPageContext> context) : DetailsElementViewModel(_detailsElement, context)
{
private readonly ExtensionObject<IDetailsSeparator> _dataModel =
new(_detailsElement.Data as IDetailsSeparator);
public override void InitializeProperties()
{
base.InitializeProperties();

View File

@@ -21,7 +21,7 @@ public partial class DetailsTagsViewModel(
public override void InitializeProperties()
{
base.InitializeProperties();
IDetailsTags? model = _dataModel.Unsafe;
var model = _dataModel.Unsafe;
if (model == null)
{
return;
@@ -31,7 +31,7 @@ public partial class DetailsTagsViewModel(
.Tags?
.Select(t =>
{
TagViewModel vm = new TagViewModel(t, PageContext);
var vm = new TagViewModel(t, PageContext);
vm.InitializeProperties();
return vm;
})

View File

@@ -25,7 +25,7 @@ public partial class DetailsViewModel(IDetails _details, WeakReference<IPageCont
public override void InitializeProperties()
{
IDetails? model = _detailsModel.Unsafe;
var model = _detailsModel.Unsafe;
if (model == null)
{
return;
@@ -40,10 +40,10 @@ public partial class DetailsViewModel(IDetails _details, WeakReference<IPageCont
UpdateProperty(nameof(Body));
UpdateProperty(nameof(HeroImage));
IDetailsElement[] meta = model.Metadata;
var meta = model.Metadata;
if (meta != null)
{
foreach (IDetailsElement? element in meta)
foreach (var element in meta)
{
DetailsElementViewModel? vm = element.Data switch
{

View File

@@ -11,20 +11,20 @@ public abstract partial class ExtensionObjectViewModel : ObservableObject
{
public WeakReference<IPageContext> PageContext { get; set; }
protected ExtensionObjectViewModel(IPageContext? context)
public ExtensionObjectViewModel(IPageContext? context)
{
IPageContext realContext = context ?? (this is IPageContext c ? c : throw new ArgumentException("You need to pass in an IErrorContext"));
var realContext = context ?? (this is IPageContext c ? c : throw new ArgumentException("You need to pass in an IErrorContext"));
PageContext = new(realContext);
}
protected ExtensionObjectViewModel(WeakReference<IPageContext> context)
public ExtensionObjectViewModel(WeakReference<IPageContext> context)
{
PageContext = context;
}
public virtual async Task InitializePropertiesAsync()
public async virtual Task InitializePropertiesAsync()
{
Task t = new Task(() =>
var t = new Task(() =>
{
SafeInitializePropertiesSynchronous();
});
@@ -53,7 +53,7 @@ public abstract partial class ExtensionObjectViewModel : ObservableObject
protected void ShowException(Exception ex, string? extensionHint = null)
{
if (PageContext.TryGetTarget(out IPageContext? pageContext))
if (PageContext.TryGetTarget(out var pageContext))
{
pageContext.ShowException(ex, extensionHint);
}
@@ -61,7 +61,7 @@ public abstract partial class ExtensionObjectViewModel : ObservableObject
protected void DoOnUiThread(Action action)
{
if (PageContext.TryGetTarget(out IPageContext? pageContext))
if (PageContext.TryGetTarget(out var pageContext))
{
Task.Factory.StartNew(
action,

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -1,7 +1,13 @@
// Copyright (c) Microsoft Corporation
// 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.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.Core.ViewModels;
public interface IContextItemViewModel

View File

@@ -35,7 +35,7 @@ public partial class IconDataViewModel : ObservableObject, IIconData
// Unsafe, needs to be called on BG thread
public void InitializeProperties()
{
IIconData? model = _model.Unsafe;
var model = _model.Unsafe;
if (model == null)
{
return;

View File

@@ -42,7 +42,7 @@ public partial class IconInfoViewModel : ObservableObject, IIconInfo
// Unsafe, needs to be called on BG thread
public void InitializeProperties()
{
IIconInfo? model = _model.Unsafe;
var model = _model.Unsafe;
if (model == null)
{
return;

View File

@@ -39,7 +39,7 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
// This sets IsInitialized = true
base.InitializeProperties();
IListItem? li = Model.Unsafe;
var li = Model.Unsafe;
if (li == null)
{
return; // throw?
@@ -49,7 +49,7 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
TextToSuggest = li.TextToSuggest;
Section = li.Section ?? string.Empty;
IDetails extensionDetails = li.Details;
var extensionDetails = li.Details;
if (extensionDetails != null)
{
Details = new(extensionDetails, PageContext);
@@ -66,7 +66,7 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
{
base.FetchProperty(propertyName);
IListItem? model = this.Model.Unsafe;
var model = this.Model.Unsafe;
if (model == null)
{
return; // throw?
@@ -84,7 +84,7 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
this.Section = model.Section ?? string.Empty;
break;
case nameof(Details):
IDetails extensionDetails = model.Details;
var extensionDetails = model.Details;
Details = extensionDetails != null ? new(extensionDetails, PageContext) : null;
Details?.InitializeProperties();
UpdateProperty(nameof(Details));
@@ -107,9 +107,9 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
private void UpdateTags(ITag[]? newTagsFromModel)
{
List<TagViewModel> newTags = newTagsFromModel?.Select(t =>
var newTags = newTagsFromModel?.Select(t =>
{
TagViewModel vm = new TagViewModel(t, PageContext);
var vm = new TagViewModel(t, PageContext);
vm.InitializeProperties();
return vm;
})
@@ -135,7 +135,7 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
Tags?.ForEach(t => t.SafeCleanup());
Details?.SafeCleanup();
IListItem? model = Model.Unsafe;
var model = Model.Unsafe;
if (model != null)
{
// We don't need to revoke the PropChanged event handler here,

View File

@@ -6,6 +6,7 @@ using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
@@ -31,7 +32,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
private readonly Lock _listLock = new();
private bool _isLoading;
private InterlockedBoolean _isLoading;
private bool _isFetching;
public event TypedEventHandler<ListViewModel, object>? ItemsUpdated;
@@ -121,7 +122,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
ItemsUpdated?.Invoke(this, EventArgs.Empty);
UpdateEmptyContent();
_isLoading = false;
_isLoading.Clear();
}
}
@@ -134,7 +135,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
try
{
IListItem[] newItems = _model.Unsafe!.GetItems();
var newItems = _model.Unsafe!.GetItems();
// Collect all the items into new viewmodels
Collection<ListItemViewModel> newViewModels = [];
@@ -142,7 +143,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
// TODO we can probably further optimize this by also keeping a
// HashSet of every ExtensionObject we currently have, and only
// building new viewmodels for the ones we haven't already built.
foreach (IListItem? item in newItems)
foreach (var item in newItems)
{
ListItemViewModel viewModel = new(item, new(this));
@@ -153,8 +154,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
}
IEnumerable<ListItemViewModel> firstTwenty = newViewModels.Take(20);
foreach (ListItemViewModel? item in firstTwenty)
var firstTwenty = newViewModels.Take(20);
foreach (var item in firstTwenty)
{
item?.SafeInitializeProperties();
}
@@ -221,7 +222,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
ItemsUpdated?.Invoke(this, EventArgs.Empty);
_isLoading = false;
_isLoading.Clear();
});
}
@@ -236,7 +237,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
iterable = Items.ToArray();
}
foreach (ListItemViewModel item in iterable)
foreach (var item in iterable)
{
ct.ThrowIfCancellationRequested();
@@ -269,8 +270,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
return 1;
}
MatchResult nameMatch = StringMatcher.FuzzySearch(query, listItem.Title);
MatchResult descriptionMatch = StringMatcher.FuzzySearch(query, listItem.Subtitle);
var nameMatch = StringMatcher.FuzzySearch(query, listItem.Title);
var descriptionMatch = StringMatcher.FuzzySearch(query, listItem.Subtitle);
return new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, 0 }.Max();
}
@@ -283,7 +284,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
// Similarly stolen from ListHelpers.FilterList
public static IEnumerable<ListItemViewModel> FilterList(IEnumerable<ListItemViewModel> items, string query)
{
IOrderedEnumerable<ScoredListItemViewModel> scores = items
var scores = items
.Where(i => !i.IsInErrorState)
.Select(li => new ScoredListItemViewModel() { ViewModel = li, Score = ScoreListItem(query, li) })
.Where(score => score.Score > 0)
@@ -381,7 +382,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
private void SelectedItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
ListItemViewModel? item = _lastSelectedItem;
var item = _lastSelectedItem;
if (item == null)
{
return;
@@ -436,7 +437,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
{
base.InitializeProperties();
IListPage? model = _model.Unsafe;
var model = _model.Unsafe;
if (model == null)
{
return; // throw?
@@ -463,34 +464,51 @@ public partial class ListViewModel : PageViewModel, IDisposable
public void LoadMoreIfNeeded()
{
IListPage? model = this._model.Unsafe;
var model = this._model.Unsafe;
if (model == null)
{
return;
}
if (model.HasMoreItems && !_isLoading)
if (!_isLoading.Set())
{
_isLoading = true;
_ = Task.Run(() =>
return;
// NOTE: May miss newly available items until next scroll if model
// state changes between our check and this reset
}
_ = Task.Run(() =>
{
// Execute all COM calls on background thread to avoid reentrancy issues with UI
// with the UI thread when COM starts inner message pump
try
{
try
if (model.HasMoreItems)
{
model.LoadMore();
// _isLoading flag will be set as a result of LoadMore,
// which must raise ItemsChanged to end the loading.
}
catch (Exception ex)
else
{
ShowException(ex, model.Name);
_isLoading.Clear();
}
});
}
}
catch (Exception ex)
{
_isLoading.Clear();
ShowException(ex, model.Name);
}
});
}
protected override void FetchProperty(string propertyName)
{
base.FetchProperty(propertyName);
IListPage? model = this._model.Unsafe;
var model = this._model.Unsafe;
if (model == null)
{
return; // throw?
@@ -555,13 +573,13 @@ public partial class ListViewModel : PageViewModel, IDisposable
lock (_listLock)
{
foreach (ListItemViewModel item in Items)
foreach (var item in Items)
{
item.SafeCleanup();
}
Items.Clear();
foreach (ListItemViewModel item in FilteredItems)
foreach (var item in FilteredItems)
{
item.SafeCleanup();
}
@@ -569,7 +587,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
FilteredItems.Clear();
}
IListPage? model = _model.Unsafe;
var model = _model.Unsafe;
if (model != null)
{
model.ItemsChanged -= Model_ItemsChanged;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -21,7 +21,7 @@ public partial class LogMessageViewModel : ExtensionObjectViewModel
public override void InitializeProperties()
{
ILogMessage? model = _model.Unsafe;
var model = _model.Unsafe;
if (model == null)
{
return; // throw?

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -2,6 +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 Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record HideDetailsMessage()

View File

@@ -2,6 +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 Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record LaunchUriMessage(Uri Uri)

View File

@@ -2,6 +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 Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ShowDetailsMessage(DetailsViewModel Details)

View File

@@ -16,11 +16,11 @@ public record UpdateCommandBarMessage(ICommandBarContext? ViewModel)
public interface IContextMenuContext : INotifyPropertyChanged
{
IEnumerable<IContextItemViewModel> MoreCommands { get; }
public IEnumerable<IContextItemViewModel> MoreCommands { get; }
bool HasMoreCommands { get; }
public bool HasMoreCommands { get; }
List<IContextItemViewModel> AllCommands { get; }
public List<IContextItemViewModel> AllCommands { get; }
/// <summary>
/// Generates a mapping of key -> command item for this particular item's
@@ -30,7 +30,7 @@ public interface IContextMenuContext : INotifyPropertyChanged
/// </summary>
/// <returns>a dictionary of KeyChord -> Context commands, for all commands
/// that have a shortcut key set.</returns>
Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
public Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
{
return MoreCommands
.OfType<CommandContextItemViewModel>()
@@ -48,9 +48,9 @@ public interface IContextMenuContext : INotifyPropertyChanged
// the two things with sub-commands.
public interface ICommandBarContext : IContextMenuContext
{
string SecondaryCommandName { get; }
public string SecondaryCommandName { get; }
CommandItemViewModel? PrimaryCommand { get; }
public CommandItemViewModel? PrimaryCommand { get; }
CommandItemViewModel? SecondaryCommand { get; }
public CommandItemViewModel? SecondaryCommand { get; }
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -88,7 +88,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
{
if (ExtensionHost.StatusMessages.Any())
{
StatusMessageViewModel last = ExtensionHost.StatusMessages.Last();
var last = ExtensionHost.StatusMessages.Last();
MostRecentStatusMessage = last;
}
else
@@ -131,7 +131,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
public override void InitializeProperties()
{
IPage? page = _pageModel.Unsafe;
var page = _pageModel.Unsafe;
if (page == null)
{
return; // throw?
@@ -157,7 +157,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
{
try
{
string propName = args.PropertyName;
var propName = args.PropertyName;
FetchProperty(propName);
}
catch (Exception ex)
@@ -176,13 +176,13 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
protected virtual void FetchProperty(string propertyName)
{
IPage? model = this._pageModel.Unsafe;
var model = this._pageModel.Unsafe;
if (model == null)
{
return; // throw?
}
bool updateProperty = true;
var updateProperty = true;
switch (propertyName)
{
case nameof(Name):
@@ -239,7 +239,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
ExtensionHost.StatusMessages.CollectionChanged -= StatusMessages_CollectionChanged;
IPage? model = _pageModel.Unsafe;
var model = _pageModel.Unsafe;
if (model != null)
{
model.PropChanged -= Model_PropChanged;

View File

@@ -1,27 +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.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public class PageViewModelFactory : IPageViewModelFactoryService
{
private readonly TaskScheduler _scheduler;
public PageViewModelFactory(TaskScheduler scheduler)
{
_scheduler = scheduler;
}
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host)
{
return page switch
{
IListPage listPage => new ListViewModel(listPage, _scheduler, host) { IsNested = nested },
IContentPage contentPage => new ContentPageViewModel(contentPage, _scheduler, host),
_ => null,
};
}
}

View File

@@ -23,7 +23,7 @@ public partial class ProgressViewModel : ExtensionObjectViewModel
public override void InitializeProperties()
{
IProgressState? model = Model.Unsafe;
var model = Model.Unsafe;
if (model == null)
{
return; // throw?
@@ -49,7 +49,7 @@ public partial class ProgressViewModel : ExtensionObjectViewModel
protected virtual void FetchProperty(string propertyName)
{
IProgressState? model = this.Model.Unsafe;
var model = this.Model.Unsafe;
if (model == null)
{
return; // throw?

View File

@@ -1,7 +1,8 @@
// Copyright (c) Microsoft Corporation
// 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.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;

View File

@@ -13,7 +13,8 @@ using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ShellViewModel : ObservableObject,
IRecipient<PerformCommandMessage>
IRecipient<PerformCommandMessage>,
IRecipient<HandleCommandResultMessage>
{
private readonly IRootPageService _rootPageService;
private readonly IAppHostService _appHostService;
@@ -38,7 +39,7 @@ public partial class ShellViewModel : ObservableObject,
get => _currentPage;
set
{
PageViewModel oldValue = _currentPage;
var oldValue = _currentPage;
if (SetProperty(ref _currentPage, value))
{
if (oldValue is IDisposable disposable)
@@ -60,7 +61,7 @@ public partial class ShellViewModel : ObservableObject,
private bool _isNested;
public bool IsNested => _isNested;
public bool IsNested { get => _isNested; }
public ShellViewModel(
TaskScheduler scheduler,
@@ -77,6 +78,7 @@ public partial class ShellViewModel : ObservableObject,
// Register to receive messages
WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
WeakReferenceMessenger.Default.Register<HandleCommandResultMessage>(this);
}
[RelayCommand]
@@ -182,13 +184,13 @@ public partial class ShellViewModel : ObservableObject,
private void PerformCommand(PerformCommandMessage message)
{
ICommand? command = message.Command.Unsafe;
var command = message.Command.Unsafe;
if (command == null)
{
return;
}
AppExtensionHost host = _appHostService.GetHostForCommand(message.Context, CurrentPage.ExtensionHost);
var host = _appHostService.GetHostForCommand(message.Context, CurrentPage.ExtensionHost);
_rootPageService.OnPerformCommand(message.Context, !CurrentPage.IsNested, host);
@@ -198,11 +200,11 @@ public partial class ShellViewModel : ObservableObject,
{
Logger.LogDebug($"Navigating to page");
bool isMainPage = command == _rootPage;
var isMainPage = command == _rootPage;
_isNested = !isMainPage;
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
PageViewModel? pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
if (pageViewModel == null)
{
Logger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
@@ -259,7 +261,7 @@ public partial class ShellViewModel : ObservableObject,
// Call out to extension process.
// * May fail!
// * May never return!
ICommandResult result = invokable.Invoke(message.Context);
var result = invokable.Invoke(message.Context);
// But if it did succeed, we need to handle the result.
UnsafeHandleCommandResult(result);
@@ -284,66 +286,66 @@ public partial class ShellViewModel : ObservableObject,
return;
}
CommandResultKind kind = result.Kind;
var kind = result.Kind;
Logger.LogDebug($"handling {kind.ToString()}");
WeakReferenceMessenger.Default.Send<CmdPalInvokeResultMessage>(new(kind));
switch (kind)
{
case CommandResultKind.Dismiss:
{
// Reset the palette to the main page and dismiss
GoHome(withAnimation: false, focusSearch: false);
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
{
// Reset the palette to the main page and dismiss
GoHome(withAnimation: false, focusSearch: false);
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.GoHome:
{
// Go back to the main page, but keep it open
GoHome();
break;
}
{
// Go back to the main page, but keep it open
GoHome();
break;
}
case CommandResultKind.GoBack:
{
GoBack();
break;
}
{
GoBack();
break;
}
case CommandResultKind.Hide:
{
// Keep this page open, but hide the palette.
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
{
// Keep this page open, but hide the palette.
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.KeepOpen:
{
// Do nothing.
break;
}
{
// Do nothing.
break;
}
case CommandResultKind.Confirm:
{
if (result.Args is IConfirmationArgs a)
{
WeakReferenceMessenger.Default.Send<ShowConfirmationMessage>(new(a));
}
if (result.Args is IConfirmationArgs a)
{
WeakReferenceMessenger.Default.Send<ShowConfirmationMessage>(new(a));
}
break;
}
break;
}
case CommandResultKind.ShowToast:
{
if (result.Args is IToastArgs a)
{
WeakReferenceMessenger.Default.Send<ShowToastMessage>(new(a.Message));
UnsafeHandleCommandResult(a.Result);
}
if (result.Args is IToastArgs a)
{
WeakReferenceMessenger.Default.Send<ShowToastMessage>(new(a.Message));
UnsafeHandleCommandResult(a.Result);
}
break;
}
break;
}
}
}
@@ -358,6 +360,11 @@ public partial class ShellViewModel : ObservableObject,
WeakReferenceMessenger.Default.Send<GoBackMessage>(new(withAnimation, focusSearch));
}
public void Receive(HandleCommandResultMessage message)
{
UnsafeHandleCommandResult(message.Result.Unsafe);
}
private void OnUIThread(Action action)
{
_ = Task.Factory.StartNew(

View File

@@ -27,7 +27,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
public override void InitializeProperties()
{
IStatusMessage? model = Model.Unsafe;
var model = Model.Unsafe;
if (model == null)
{
return; // throw?
@@ -35,7 +35,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
Message = model.Message;
State = model.State;
IProgressState modelProgress = model.Progress;
var modelProgress = model.Progress;
if (modelProgress != null)
{
Progress = new(modelProgress, this.PageContext);
@@ -60,7 +60,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
protected virtual void FetchProperty(string propertyName)
{
IStatusMessage? model = this.Model.Unsafe;
var model = this.Model.Unsafe;
if (model == null)
{
return; // throw?
@@ -75,7 +75,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
this.State = model.State;
break;
case nameof(Progress):
IProgressState modelProgress = model.Progress;
var modelProgress = model.Progress;
if (modelProgress != null)
{
Progress = new(modelProgress, this.PageContext);

View File

@@ -27,7 +27,7 @@ public partial class TagViewModel(ITag _tag, WeakReference<IPageContext> context
public override void InitializeProperties()
{
ITag? model = _tagModel.Unsafe;
var model = _tagModel.Unsafe;
if (model == null)
{
return;

View File

@@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels;

View File

@@ -30,11 +30,11 @@ public partial class AliasManager : ObservableObject
public bool CheckAlias(string searchText)
{
if (_aliases.TryGetValue(searchText, out CommandAlias? alias))
if (_aliases.TryGetValue(searchText, out var alias))
{
try
{
TopLevelViewModel? topLevelCommand = _topLevelCommandManager.LookupCommand(alias.CommandId);
var topLevelCommand = _topLevelCommandManager.LookupCommand(alias.CommandId);
if (topLevelCommand != null)
{
WeakReferenceMessenger.Default.Send<ClearSearchMessage>();
@@ -89,7 +89,7 @@ public partial class AliasManager : ObservableObject
// If we already have _this exact alias_, do nothing
if (newAlias != null &&
_aliases.TryGetValue(newAlias.SearchPrefix, out CommandAlias? existingAlias))
_aliases.TryGetValue(newAlias.SearchPrefix, out var existingAlias))
{
if (existingAlias.CommandId == commandId)
{
@@ -99,7 +99,7 @@ public partial class AliasManager : ObservableObject
// Look for the old alias, and remove it
List<CommandAlias> toRemove = [];
foreach (KeyValuePair<string, CommandAlias> kv in _aliases)
foreach (var kv in _aliases)
{
if (kv.Value.CommandId == commandId)
{
@@ -107,7 +107,7 @@ public partial class AliasManager : ObservableObject
}
}
foreach (CommandAlias alias in toRemove)
foreach (var alias in toRemove)
{
// REMEMBER, SearchPrefix is what we use as keys
_aliases.Remove(alias.SearchPrefix);

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -51,9 +51,9 @@ public partial class AppStateModel : ObservableObject
try
{
// Read the JSON content from the file
string jsonContent = File.ReadAllText(FilePath);
var jsonContent = File.ReadAllText(FilePath);
AppStateModel? loaded = JsonSerializer.Deserialize<AppStateModel>(jsonContent, JsonSerializationContext.Default.AppStateModel);
var loaded = JsonSerializer.Deserialize<AppStateModel>(jsonContent, JsonSerializationContext.Default.AppStateModel);
Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse");
@@ -77,23 +77,23 @@ public partial class AppStateModel : ObservableObject
try
{
// Serialize the main dictionary to JSON and save it to the file
string settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.AppStateModel);
var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.AppStateModel);
// Is it valid JSON?
if (JsonNode.Parse(settingsJson) is JsonObject newSettings)
{
// Now, read the existing content from the file
string oldContent = File.Exists(FilePath) ? File.ReadAllText(FilePath) : "{}";
var oldContent = File.Exists(FilePath) ? File.ReadAllText(FilePath) : "{}";
// Is it valid JSON?
if (JsonNode.Parse(oldContent) is JsonObject savedSettings)
{
foreach (KeyValuePair<string, JsonNode?> item in newSettings)
foreach (var item in newSettings)
{
savedSettings[item.Key] = item.Value?.DeepClone();
}
string serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel.Options);
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel.Options);
File.WriteAllText(FilePath, serialized);
// TODO: Instead of just raising the event here, we should
@@ -119,7 +119,7 @@ public partial class AppStateModel : ObservableObject
internal static string StateJsonPath()
{
string directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
Directory.CreateDirectory(directory);
// now, the settings is just next to the exe

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -31,8 +31,8 @@ public class CommandAlias
public static CommandAlias FromSearchText(string text, string commandId)
{
bool trailingSpace = text.EndsWith(' ');
string realAlias = trailingSpace ? text.Substring(0, text.Length - 1) : text;
var trailingSpace = text.EndsWith(' ');
var realAlias = trailingSpace ? text.Substring(0, text.Length - 1) : text;
return new CommandAlias(realAlias, commandId, !trailingSpace);
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -43,7 +43,13 @@ public sealed class CommandProviderWrapper
public bool IsActive { get; private set; }
public string ProviderId => string.IsNullOrEmpty(Extension?.ExtensionUniqueId) ? Id : Extension.ExtensionUniqueId;
public string ProviderId
{
get
{
return string.IsNullOrEmpty(Extension?.ExtensionUniqueId) ? Id : Extension.ExtensionUniqueId;
}
}
public CommandProviderWrapper(ICommandProvider provider, TaskScheduler mainThread)
{
@@ -81,8 +87,8 @@ public sealed class CommandProviderWrapper
throw new ArgumentException("You forgot to start the extension. This is a CmdPal error - we need to make sure to call StartExtensionAsync");
}
IExtension? extensionImpl = extension.GetExtensionObject();
object? providerObject = extensionImpl?.GetProvider(ProviderType.Commands);
var extensionImpl = extension.GetExtensionObject();
var providerObject = extensionImpl?.GetProvider(ProviderType.Commands);
if (providerObject is not ICommandProvider provider)
{
throw new ArgumentException("extension didn't actually implement ICommandProvider");
@@ -92,7 +98,7 @@ public sealed class CommandProviderWrapper
try
{
ICommandProvider model = _commandProvider.Unsafe!;
var model = _commandProvider.Unsafe!;
// Hook the extension back into us
model.InitializeWithHost(ExtensionHost);
@@ -125,7 +131,7 @@ public sealed class CommandProviderWrapper
return;
}
SettingsModel settings = serviceProvider.GetService<SettingsModel>()!;
var settings = serviceProvider.GetService<SettingsModel>()!;
IsActive = GetProviderSettings(settings).IsEnabled;
if (!IsActive)
@@ -138,7 +144,7 @@ public sealed class CommandProviderWrapper
try
{
ICommandProvider model = _commandProvider.Unsafe!;
var model = _commandProvider.Unsafe!;
Task<ICommandItem[]> t = new(model.TopLevelCommands);
t.Start();
@@ -171,29 +177,28 @@ public sealed class CommandProviderWrapper
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
{
SettingsModel settings = serviceProvider.GetService<SettingsModel>()!;
ProviderSettings providerSettings = GetProviderSettings(settings);
var settings = serviceProvider.GetService<SettingsModel>()!;
var providerSettings = GetProviderSettings(settings);
TopLevelViewModel MakeAndAdd(ICommandItem? i, bool fallback)
Func<ICommandItem?, bool, TopLevelViewModel> makeAndAdd = (ICommandItem? i, bool fallback) =>
{
CommandItemViewModel commandItemViewModel = new(new(i), pageContext);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, providerSettings, serviceProvider);
topLevelViewModel.InitializeProperties();
return topLevelViewModel;
}
};
if (commands != null)
{
TopLevelItems = commands
.Select(c => MakeAndAdd(c, false))
.Select(c => makeAndAdd(c, false))
.ToArray();
}
if (fallbacks != null)
{
FallbackItems = fallbacks
.Select(c => MakeAndAdd(c, true))
.Select(c => makeAndAdd(c, true))
.ToArray();
}
}

View File

@@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation
// 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 ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread)
{
@@ -23,15 +23,15 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
private void UnsafeInitializeProperties()
{
ICommandSettings? model = _model.Unsafe;
var model = _model.Unsafe;
if (model == null)
{
return;
}
if (model.SettingsPage is IContentPage page)
if (model.SettingsPage != null)
{
SettingsPage = new(page, mainThread, provider.ExtensionHost);
SettingsPage = new CommandPaletteContentPageViewModel(model.SettingsPage, mainThread, provider.ExtensionHost);
SettingsPage.InitializeProperties();
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -13,13 +13,13 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
{
public CreatedExtensionForm(string name, string displayName, string path)
{
static string SerializeString(string? s) => JsonSerializer.Serialize(s, JsonSerializationContext.Default.String);
var serializeString = (string? s) => JsonSerializer.Serialize(s, JsonSerializationContext.Default.String);
TemplateJson = CardTemplate;
DataJson = $$"""
{
"name": {{SerializeString(name)}},
"directory": {{SerializeString(path)}},
"displayName": {{SerializeString(displayName)}}
"name": {{serializeString(name)}},
"directory": {{serializeString(path)}},
"displayName": {{serializeString(displayName)}}
}
""";
_name = name;
@@ -29,13 +29,13 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
public override ICommandResult SubmitForm(string inputs, string data)
{
JsonObject? dataInput = JsonNode.Parse(data)?.AsObject();
var dataInput = JsonNode.Parse(data)?.AsObject();
if (dataInput == null)
{
return CommandResult.KeepOpen();
}
string verb = dataInput["x"]?.AsValue()?.ToString() ?? string.Empty;
var verb = dataInput["x"]?.AsValue()?.ToString() ?? string.Empty;
return verb switch
{
"sln" => OpenSolution(),
@@ -45,23 +45,23 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
};
}
private CommandResult OpenSolution()
private ICommandResult OpenSolution()
{
string[] parts = [_path, _name, $"{_name}.sln"];
string pathToSolution = Path.Combine(parts);
var pathToSolution = Path.Combine(parts);
ShellHelpers.OpenInShell(pathToSolution);
return CommandResult.Hide();
}
private CommandResult OpenDirectory()
private ICommandResult OpenDirectory()
{
string[] parts = [_path, _name];
string pathToDir = Path.Combine(parts);
var pathToDir = Path.Combine(parts);
ShellHelpers.OpenInShell(pathToDir);
return CommandResult.Hide();
}
private CommandResult CreateNew()
private ICommandResult CreateNew()
{
RaiseFormSubmit(null);
return CommandResult.KeepOpen();

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -1,7 +1,8 @@
// Copyright (c) Microsoft Corporation
// 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 Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -25,11 +25,11 @@ public partial class LogMessagesPage : ListPage
{
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
{
foreach (object? item in e.NewItems)
foreach (var item in e.NewItems)
{
if (item is LogMessageViewModel logMessageViewModel)
{
ListItem li = new ListItem(new NoOpCommand())
var li = new ListItem(new NoOpCommand())
{
Title = logMessageViewModel.Message,
};

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -40,7 +40,7 @@ public partial class MainListPage : DynamicListPage,
// The all apps page will kick off a BG thread to start loading apps.
// We just want to know when it is done.
AllAppsPage allApps = AllAppsCommandProvider.Page;
var allApps = AllAppsCommandProvider.Page;
allApps.PropChanged += (s, p) =>
{
if (p.PropertyName == nameof(allApps.IsLoading))
@@ -52,7 +52,7 @@ public partial class MainListPage : DynamicListPage,
WeakReferenceMessenger.Default.Register<ClearSearchMessage>(this);
WeakReferenceMessenger.Default.Register<UpdateFallbackItemsMessage>(this);
SettingsModel settings = _serviceProvider.GetService<SettingsModel>()!;
var settings = _serviceProvider.GetService<SettingsModel>()!;
settings.SettingsChanged += SettingsChangedHandler;
HotReloadSettings(settings);
@@ -86,7 +86,7 @@ public partial class MainListPage : DynamicListPage,
{
try
{
string currentSearchText = SearchText;
var currentSearchText = SearchText;
UpdateSearchText(currentSearchText, currentSearchText);
}
catch (Exception e)
@@ -122,14 +122,14 @@ public partial class MainListPage : DynamicListPage,
// Handle changes to the filter text here
if (!string.IsNullOrEmpty(SearchText))
{
AliasManager aliases = _serviceProvider.GetService<AliasManager>()!;
var aliases = _serviceProvider.GetService<AliasManager>()!;
if (aliases.CheckAlias(newSearch))
{
return;
}
}
System.Collections.ObjectModel.ObservableCollection<TopLevelViewModel> commands = _tlcManager.TopLevelCommands;
var commands = _tlcManager.TopLevelCommands;
lock (commands)
{
UpdateFallbacks(newSearch, commands.ToImmutableArray());
@@ -164,6 +164,11 @@ public partial class MainListPage : DynamicListPage,
if (_includeApps)
{
IEnumerable<IListItem> apps = AllAppsCommandProvider.Page.GetItems();
var appIds = apps.Select(app => app.Command.Id).ToArray();
// Remove any top level pinned apps and use the apps from AllAppsCommandProvider.Page.GetItems()
// since they contain details.
_filteredItems = _filteredItems.Where(item => item.Command is not AppCommand);
_filteredItems = _filteredItems.Concat(apps);
}
}
@@ -179,11 +184,11 @@ public partial class MainListPage : DynamicListPage,
// fire and forget
_ = Task.Run(() =>
{
bool needsToUpdate = false;
var needsToUpdate = false;
foreach (TopLevelViewModel command in commands)
foreach (var command in commands)
{
bool changedVisibility = command.SafeUpdateFallbackTextSynchronous(newSearch);
var changedVisibility = command.SafeUpdateFallbackTextSynchronous(newSearch);
needsToUpdate = needsToUpdate || changedVisibility;
}
@@ -196,8 +201,8 @@ public partial class MainListPage : DynamicListPage,
private bool ActuallyLoading()
{
TopLevelCommandManager tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
AllAppsPage allApps = AllAppsCommandProvider.Page;
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
var allApps = AllAppsCommandProvider.Page;
return allApps.IsLoading || tlcManager.IsLoading;
}
@@ -206,26 +211,26 @@ public partial class MainListPage : DynamicListPage,
// _always_ show up first.
private int ScoreTopLevelItem(string query, IListItem topLevelOrAppItem)
{
string title = topLevelOrAppItem.Title;
var title = topLevelOrAppItem.Title;
if (string.IsNullOrWhiteSpace(title))
{
return 0;
}
bool isWhiteSpace = string.IsNullOrWhiteSpace(query);
var isWhiteSpace = string.IsNullOrWhiteSpace(query);
bool isFallback = false;
bool isAliasSubstringMatch = false;
bool isAliasMatch = false;
string id = IdForTopLevelOrAppItem(topLevelOrAppItem);
var isFallback = false;
var isAliasSubstringMatch = false;
var isAliasMatch = false;
var id = IdForTopLevelOrAppItem(topLevelOrAppItem);
string extensionDisplayName = string.Empty;
var extensionDisplayName = string.Empty;
if (topLevelOrAppItem is TopLevelViewModel topLevel)
{
isFallback = topLevel.IsFallback;
if (topLevel.HasAlias)
{
string alias = topLevel.AliasText;
var alias = topLevel.AliasText;
isAliasMatch = alias == query;
isAliasSubstringMatch = isAliasMatch || alias.StartsWith(query, StringComparison.CurrentCultureIgnoreCase);
}
@@ -243,48 +248,48 @@ public partial class MainListPage : DynamicListPage,
// Title:
// * whitespace query: 1 point
// * otherwise full weight match
int nameMatch = isWhiteSpace ?
var nameMatch = isWhiteSpace ?
(title.Contains(query) ? 1 : 0) :
StringMatcher.FuzzySearch(query, title).Score;
// Subtitle:
// * whitespace query: 1/2 point
// * otherwise ~half weight match. Minus a bit, because subtitles tend to be longer
double descriptionMatch = isWhiteSpace ?
var descriptionMatch = isWhiteSpace ?
(topLevelOrAppItem.Subtitle.Contains(query) ? .5 : 0) :
(StringMatcher.FuzzySearch(query, topLevelOrAppItem.Subtitle).Score - 4) / 2.0;
// Extension title: despite not being visible, give the extension name itself some weight
// * whitespace query: 0 points
// * otherwise more weight than a subtitle, but not much
double extensionTitleMatch = isWhiteSpace ? 0 : StringMatcher.FuzzySearch(query, extensionDisplayName).Score / 1.5;
var extensionTitleMatch = isWhiteSpace ? 0 : StringMatcher.FuzzySearch(query, extensionDisplayName).Score / 1.5;
double[] scores = new[]
var scores = new[]
{
nameMatch,
descriptionMatch,
isFallback ? 1 : 0, // Always give fallbacks a chance
};
double max = scores.Max();
var max = scores.Max();
// _Add_ the extension name. This will bubble items that match both
// title and extension name up above ones that just match title.
// e.g. "git" will up-weight "GitHub searches" from the GitHub extension
// above "git" from "whatever"
max += extensionTitleMatch;
max = max + extensionTitleMatch;
double matchSomething = max
var matchSomething = max
+ (isAliasMatch ? 9001 : (isAliasSubstringMatch ? 1 : 0));
// If we matched title, subtitle, or alias (something real), then
// here we add the recent command weight boost
//
// Otherwise something like `x` will still match everything you've run before
double finalScore = matchSomething;
var finalScore = matchSomething;
if (matchSomething > 0)
{
RecentCommandsManager history = _serviceProvider.GetService<AppStateModel>()!.RecentCommands;
int recentWeightBoost = history.GetCommandHistoryWeight(id);
var history = _serviceProvider.GetService<AppStateModel>()!.RecentCommands;
var recentWeightBoost = history.GetCommandHistoryWeight(id);
finalScore += recentWeightBoost;
}
@@ -293,9 +298,9 @@ public partial class MainListPage : DynamicListPage,
public void UpdateHistory(IListItem topLevelOrAppItem)
{
string id = IdForTopLevelOrAppItem(topLevelOrAppItem);
AppStateModel state = _serviceProvider.GetService<AppStateModel>()!;
RecentCommandsManager history = state.RecentCommands;
var id = IdForTopLevelOrAppItem(topLevelOrAppItem);
var state = _serviceProvider.GetService<AppStateModel>()!;
var history = state.RecentCommands;
history.AddHistoryItem(id);
AppStateModel.SaveState(state);
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -97,15 +97,15 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase
public override CommandResult SubmitForm(string payload)
{
JsonObject? formInput = JsonNode.Parse(payload)?.AsObject();
var formInput = JsonNode.Parse(payload)?.AsObject();
if (formInput == null)
{
return CommandResult.KeepOpen();
}
string extensionName = formInput["ExtensionName"]?.AsValue()?.ToString() ?? string.Empty;
string displayName = formInput["DisplayName"]?.AsValue()?.ToString() ?? string.Empty;
string outputPath = formInput["OutputPath"]?.AsValue()?.ToString() ?? string.Empty;
var extensionName = formInput["ExtensionName"]?.AsValue()?.ToString() ?? string.Empty;
var displayName = formInput["DisplayName"]?.AsValue()?.ToString() ?? string.Empty;
var outputPath = formInput["OutputPath"]?.AsValue()?.ToString() ?? string.Empty;
_creatingMessage.State = MessageState.Info;
_creatingMessage.Message = _creatingText;
@@ -133,10 +133,10 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase
private void CreateExtension(string extensionName, string newDisplayName, string outputPath)
{
string newGuid = Guid.NewGuid().ToString();
var newGuid = Guid.NewGuid().ToString();
// Unzip `template.zip` to a temp dir:
string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
// Does the output path exist?
if (!Directory.Exists(outputPath))
@@ -144,13 +144,13 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase
Directory.CreateDirectory(outputPath);
}
string assetsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory.ToString(), "Microsoft.CmdPal.UI.ViewModels\\Assets\\template.zip");
var assetsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory.ToString(), "Microsoft.CmdPal.UI.ViewModels\\Assets\\template.zip");
ZipFile.ExtractToDirectory(assetsPath, tempDir);
string[] files = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories);
foreach (string file in files)
var files = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
string text = File.ReadAllText(file);
var text = File.ReadAllText(file);
// Replace all the instances of `FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF` with a new random guid:
text = text.Replace("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", newGuid);
@@ -162,9 +162,9 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase
text = text.Replace("TemplateDisplayName", newDisplayName);
// We're going to write the file to the same relative location in the output path
string relativePath = Path.GetRelativePath(tempDir, file);
var relativePath = Path.GetRelativePath(tempDir, file);
string newFileName = Path.Combine(outputPath, relativePath);
var newFileName = Path.Combine(outputPath, relativePath);
// if the file name had `TemplateCmdPalExtension` in it, replace it with `extensionName`
newFileName = newFileName.Replace("TemplateCmdPalExtension", extensionName);

View File

@@ -1,7 +1,13 @@
// Copyright (c) Microsoft Corporation
// 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.Diagnostics.CodeAnalysis;
using System.IO.Compression;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -44,8 +44,8 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
try
{
AdaptiveCardTemplate template = new AdaptiveCardTemplate(templateJson);
string cardJson = template.Expand(dataJson);
var template = new AdaptiveCardTemplate(templateJson);
var cardJson = template.Expand(dataJson);
card = AdaptiveCard.FromJsonString(cardJson);
return true;
}
@@ -59,7 +59,7 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
public override void InitializeProperties()
{
IFormContent? model = _formModel.Unsafe;
var model = _formModel.Unsafe;
if (model is null)
{
return;
@@ -69,14 +69,14 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
StateJson = model.StateJson;
DataJson = model.DataJson;
if (TryBuildCard(TemplateJson, DataJson, out AdaptiveCardParseResult? builtCard, out Exception? renderingError))
if (TryBuildCard(TemplateJson, DataJson, out var builtCard, out var renderingError))
{
Card = builtCard;
UpdateProperty(nameof(Card));
return;
}
string errorPayload = $$"""
var errorPayload = $$"""
{
"error_message": {{Serialize(renderingError!.Message)}},
"error_stack": {{Serialize(renderingError.StackTrace)}},
@@ -86,7 +86,7 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
}
""";
if (TryBuildCard(ErrorCardJson, errorPayload, out AdaptiveCardParseResult? errorCard, out Exception? _))
if (TryBuildCard(ErrorCardJson, errorPayload, out var errorCard, out var _))
{
Card = errorCard;
UpdateProperty(nameof(Card));
@@ -107,17 +107,17 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
if (action is AdaptiveSubmitAction or AdaptiveExecuteAction)
{
// Get the data and inputs
string dataString = (action as AdaptiveSubmitAction)?.DataJson.Stringify() ?? string.Empty;
string inputString = inputs.Stringify();
var dataString = (action as AdaptiveSubmitAction)?.DataJson.Stringify() ?? string.Empty;
var inputString = inputs.Stringify();
_ = Task.Run(() =>
{
try
{
IFormContent model = _formModel.Unsafe!;
var model = _formModel.Unsafe!;
if (model != null)
{
ICommandResult result = model.SubmitForm(inputString, dataString);
var result = model.SubmitForm(inputString, dataString);
WeakReferenceMessenger.Default.Send<HandleCommandResultMessage>(new(new(result)));
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -19,7 +19,7 @@ public partial class ContentMarkdownViewModel(IMarkdownContent _markdown, WeakRe
public override void InitializeProperties()
{
IMarkdownContent? model = Model.Unsafe;
var model = Model.Unsafe;
if (model == null)
{
return;
@@ -35,7 +35,7 @@ public partial class ContentMarkdownViewModel(IMarkdownContent _markdown, WeakRe
{
try
{
string propName = args.PropertyName;
var propName = args.PropertyName;
FetchProperty(propName);
}
catch (Exception ex)
@@ -46,7 +46,7 @@ public partial class ContentMarkdownViewModel(IMarkdownContent _markdown, WeakRe
protected void FetchProperty(string propertyName)
{
IMarkdownContent? model = Model.Unsafe;
var model = Model.Unsafe;
if (model == null)
{
return; // throw?
@@ -65,7 +65,7 @@ public partial class ContentMarkdownViewModel(IMarkdownContent _markdown, WeakRe
protected override void UnsafeCleanup()
{
base.UnsafeCleanup();
IMarkdownContent? model = Model.Unsafe;
var model = Model.Unsafe;
if (model != null)
{
model.PropChanged -= Model_PropChanged;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -29,13 +29,13 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
public override void InitializeProperties()
{
ITreeContent? model = Model.Unsafe;
var model = Model.Unsafe;
if (model == null)
{
return;
}
IContent root = model.RootContent;
var root = model.RootContent;
if (root != null)
{
RootContent = ViewModelFromContent(root, PageContext);
@@ -70,7 +70,7 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
{
try
{
string propName = args.PropertyName;
var propName = args.PropertyName;
FetchProperty(propName);
}
catch (Exception ex)
@@ -81,7 +81,7 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
protected void FetchProperty(string propertyName)
{
ITreeContent? model = Model.Unsafe;
var model = Model.Unsafe;
if (model == null)
{
return; // throw?
@@ -90,7 +90,7 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
switch (propertyName)
{
case nameof(RootContent):
IContent? root = model.RootContent;
var root = model.RootContent;
if (root != null)
{
RootContent = ViewModelFromContent(root, PageContext);
@@ -114,11 +114,11 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
List<ContentViewModel> newContent = [];
try
{
IContent[] newItems = Model.Unsafe!.GetChildren();
var newItems = Model.Unsafe!.GetChildren();
foreach (IContent? item in newItems)
foreach (var item in newItems)
{
ContentViewModel? viewModel = ViewModelFromContent(item, PageContext);
var viewModel = ViewModelFromContent(item, PageContext);
if (viewModel != null)
{
viewModel.InitializeProperties();
@@ -146,13 +146,13 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
{
base.UnsafeCleanup();
RootContent?.SafeCleanup();
foreach (ContentViewModel item in Children)
foreach (var item in Children)
{
item.SafeCleanup();
}
Children.Clear();
ITreeContent? model = Model.Unsafe;
var model = Model.Unsafe;
if (model != null)
{
model.PropChanged -= Model_PropChanged;

View File

@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation
// 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 CommunityToolkit.Mvvm.ComponentModel;
namespace Microsoft.CmdPal.UI.ViewModels;
public record HistoryItem

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -21,7 +21,7 @@ public partial class HotkeyManager : ObservableObject
public void UpdateHotkey(string commandId, HotkeySettings? hotkey)
{
// If any of the commands were already bound to this hotkey, remove that
foreach (TopLevelHotkey item in _commandHotkeys)
foreach (var item in _commandHotkeys)
{
if (item.Hotkey == hotkey)
{
@@ -31,7 +31,7 @@ public partial class HotkeyManager : ObservableObject
_commandHotkeys.RemoveAll(item => item.Hotkey == null);
foreach (TopLevelHotkey item in _commandHotkeys)
foreach (var item in _commandHotkeys)
{
if (item.CommandId == commandId)
{

View File

@@ -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 ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CommandPalette.Extensions;
using Windows.ApplicationModel;
@@ -83,12 +84,12 @@ public partial class ExtensionService : IExtensionService, IDisposable
private void InstallPackageUnderLock(Package package)
{
IsExtensionResult isCmdPalExtensionResult = Task.Run(() =>
var isCmdPalExtensionResult = Task.Run(() =>
{
return IsValidCmdPalExtension(package);
}).Result;
bool isExtension = isCmdPalExtensionResult.IsExtension;
AppExtension? extension = isCmdPalExtensionResult.Extension;
var isExtension = isCmdPalExtensionResult.IsExtension;
var extension = isCmdPalExtensionResult.Extension;
if (isExtension && extension != null)
{
CommandPaletteHost.Instance.DebugLog($"Installed new extension app {extension.DisplayName}");
@@ -98,7 +99,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
await _getInstalledExtensionsLock.WaitAsync();
try
{
List<ExtensionWrapper> wrappers = await CreateWrappersForExtension(extension);
var wrappers = await CreateWrappersForExtension(extension);
UpdateExtensionsListsFromWrappers(wrappers);
@@ -115,7 +116,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
private void UninstallPackageUnderLock(Package package)
{
List<IExtensionWrapper> removedExtensions = [];
foreach (IExtensionWrapper extension in _installedExtensions)
foreach (var extension in _installedExtensions)
{
if (extension.PackageFullName == package.Id.FullName)
{
@@ -144,12 +145,12 @@ public partial class ExtensionService : IExtensionService, IDisposable
private static async Task<IsExtensionResult> IsValidCmdPalExtension(Package package)
{
IReadOnlyList<AppExtension> extensions = await AppExtensionCatalog.Open("com.microsoft.commandpalette").FindAllAsync();
foreach (AppExtension? extension in extensions)
var extensions = await AppExtensionCatalog.Open("com.microsoft.commandpalette").FindAllAsync();
foreach (var extension in extensions)
{
if (package.Id?.FullName == extension.Package?.Id?.FullName)
{
(IPropertySet? cmdPalProvider, List<string> classId) = await GetCmdPalExtensionPropertiesAsync(extension);
var (cmdPalProvider, classId) = await GetCmdPalExtensionPropertiesAsync(extension);
return new(cmdPalProvider != null && classId.Count != 0, extension);
}
@@ -160,21 +161,21 @@ public partial class ExtensionService : IExtensionService, IDisposable
private static async Task<(IPropertySet? CmdPalProvider, List<string> ClassIds)> GetCmdPalExtensionPropertiesAsync(AppExtension extension)
{
List<string> classIds = new List<string>();
IPropertySet? properties = await extension.GetExtensionPropertiesAsync();
var classIds = new List<string>();
var properties = await extension.GetExtensionPropertiesAsync();
if (properties is null)
{
return (null, classIds);
}
IPropertySet? cmdPalProvider = GetSubPropertySet(properties, "CmdPalProvider");
var cmdPalProvider = GetSubPropertySet(properties, "CmdPalProvider");
if (cmdPalProvider is null)
{
return (null, classIds);
}
IPropertySet? activation = GetSubPropertySet(cmdPalProvider, "Activation");
var activation = GetSubPropertySet(cmdPalProvider, "Activation");
if (activation is null)
{
return (cmdPalProvider, classIds);
@@ -195,10 +196,10 @@ public partial class ExtensionService : IExtensionService, IDisposable
{
if (_installedExtensions.Count == 0)
{
IEnumerable<AppExtension> extensions = await GetInstalledAppExtensionsAsync();
foreach (AppExtension extension in extensions)
var extensions = await GetInstalledAppExtensionsAsync();
foreach (var extension in extensions)
{
List<ExtensionWrapper> wrappers = await CreateWrappersForExtension(extension);
var wrappers = await CreateWrappersForExtension(extension);
UpdateExtensionsListsFromWrappers(wrappers);
}
}
@@ -213,11 +214,11 @@ public partial class ExtensionService : IExtensionService, IDisposable
private static void UpdateExtensionsListsFromWrappers(List<ExtensionWrapper> wrappers)
{
foreach (ExtensionWrapper extensionWrapper in wrappers)
foreach (var extensionWrapper in wrappers)
{
// var localSettingsService = Application.Current.GetService<ILocalSettingsService>();
string extensionUniqueId = extensionWrapper.ExtensionUniqueId;
bool isExtensionDisabled = false; // await localSettingsService.ReadSettingAsync<bool>(extensionUniqueId + "-ExtensionDisabled");
var extensionUniqueId = extensionWrapper.ExtensionUniqueId;
var isExtensionDisabled = false; // await localSettingsService.ReadSettingAsync<bool>(extensionUniqueId + "-ExtensionDisabled");
_installedExtensions.Add(extensionWrapper);
if (!isExtensionDisabled)
@@ -234,7 +235,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
private static async Task<List<ExtensionWrapper>> CreateWrappersForExtension(AppExtension extension)
{
(IPropertySet? cmdPalProvider, List<string> classIds) = await GetCmdPalExtensionPropertiesAsync(extension);
var (cmdPalProvider, classIds) = await GetCmdPalExtensionPropertiesAsync(extension);
if (cmdPalProvider == null || classIds.Count == 0)
{
@@ -242,9 +243,9 @@ public partial class ExtensionService : IExtensionService, IDisposable
}
List<ExtensionWrapper> wrappers = [];
foreach (string classId in classIds)
foreach (var classId in classIds)
{
ExtensionWrapper extensionWrapper = CreateExtensionWrapper(extension, cmdPalProvider, classId);
var extensionWrapper = CreateExtensionWrapper(extension, cmdPalProvider, classId);
wrappers.Add(extensionWrapper);
}
@@ -253,14 +254,15 @@ public partial class ExtensionService : IExtensionService, IDisposable
private static ExtensionWrapper CreateExtensionWrapper(AppExtension extension, IPropertySet cmdPalProvider, string classId)
{
ExtensionWrapper extensionWrapper = new ExtensionWrapper(extension, classId);
var extensionWrapper = new ExtensionWrapper(extension, classId);
IPropertySet? supportedInterfaces = GetSubPropertySet(cmdPalProvider, "SupportedInterfaces");
var supportedInterfaces = GetSubPropertySet(cmdPalProvider, "SupportedInterfaces");
if (supportedInterfaces is not null)
{
foreach (KeyValuePair<string, object> supportedInterface in supportedInterfaces)
foreach (var supportedInterface in supportedInterfaces)
{
if (Enum.TryParse(supportedInterface.Key, out ProviderType pt))
ProviderType pt;
if (Enum.TryParse(supportedInterface.Key, out pt))
{
extensionWrapper.AddProviderType(pt);
}
@@ -277,28 +279,36 @@ public partial class ExtensionService : IExtensionService, IDisposable
public IExtensionWrapper? GetInstalledExtension(string extensionUniqueId)
{
IEnumerable<IExtensionWrapper> extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
return extension.FirstOrDefault();
}
public async Task SignalStopExtensionsAsync()
{
IEnumerable<IExtensionWrapper> installedExtensions = await GetInstalledExtensionsAsync();
foreach (IExtensionWrapper installedExtension in installedExtensions)
var installedExtensions = await GetInstalledExtensionsAsync();
foreach (var installedExtension in installedExtensions)
{
if (installedExtension.IsRunning())
Logger.LogDebug($"Signaling dispose to {installedExtension.ExtensionUniqueId}");
try
{
installedExtension.SignalDispose();
if (installedExtension.IsRunning())
{
installedExtension.SignalDispose();
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to send dispose signal to extension {installedExtension.ExtensionUniqueId}", ex);
}
}
}
public async Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(ProviderType providerType, bool includeDisabledExtensions = false)
{
IEnumerable<IExtensionWrapper> installedExtensions = await GetInstalledExtensionsAsync(includeDisabledExtensions);
var installedExtensions = await GetInstalledExtensionsAsync(includeDisabledExtensions);
List<IExtensionWrapper> filteredExtensions = [];
foreach (IExtensionWrapper installedExtension in installedExtensions)
foreach (var installedExtension in installedExtensions)
{
if (installedExtension.HasProviderType(providerType))
{
@@ -329,9 +339,9 @@ public partial class ExtensionService : IExtensionService, IDisposable
}
}
private static IPropertySet? GetSubPropertySet(IPropertySet propSet, string name) => propSet.TryGetValue(name, out object? value) ? value as IPropertySet : null;
private static IPropertySet? GetSubPropertySet(IPropertySet propSet, string name) => propSet.TryGetValue(name, out var value) ? value as IPropertySet : null;
private static object[]? GetSubPropertySetArray(IPropertySet propSet, string name) => propSet.TryGetValue(name, out object? value) ? value as object[] : null;
private static object[]? GetSubPropertySetArray(IPropertySet propSet, string name) => propSet.TryGetValue(name, out var value) ? value as object[] : null;
/// <summary>
/// There are cases where the extension creates multiple COM instances.
@@ -340,11 +350,11 @@ public partial class ExtensionService : IExtensionService, IDisposable
/// <returns>List of ClassId strings associated with the activation property</returns>
private static List<string> GetCreateInstanceList(IPropertySet activationPropSet)
{
List<string> propSetList = new List<string>();
IPropertySet? singlePropertySet = GetSubPropertySet(activationPropSet, CreateInstanceProperty);
var propSetList = new List<string>();
var singlePropertySet = GetSubPropertySet(activationPropSet, CreateInstanceProperty);
if (singlePropertySet != null)
{
string? classId = GetProperty(singlePropertySet, ClassIdProperty);
var classId = GetProperty(singlePropertySet, ClassIdProperty);
// If the instance has a classId as a single string, then it's only supporting a single instance.
if (classId != null)
@@ -354,17 +364,17 @@ public partial class ExtensionService : IExtensionService, IDisposable
}
else
{
object[]? propertySetArray = GetSubPropertySetArray(activationPropSet, CreateInstanceProperty);
var propertySetArray = GetSubPropertySetArray(activationPropSet, CreateInstanceProperty);
if (propertySetArray != null)
{
foreach (object prop in propertySetArray)
foreach (var prop in propertySetArray)
{
if (prop is not IPropertySet propertySet)
{
continue;
}
string? classId = GetProperty(propertySet, ClassIdProperty);
var classId = GetProperty(propertySet, ClassIdProperty);
if (classId != null)
{
propSetList.Add(classId);
@@ -380,13 +390,13 @@ public partial class ExtensionService : IExtensionService, IDisposable
public void EnableExtension(string extensionUniqueId)
{
IEnumerable<IExtensionWrapper> extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
_enabledExtensions.Add(extension.First());
}
public void DisableExtension(string extensionUniqueId)
{
IEnumerable<IExtensionWrapper> extension = _enabledExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
var extension = _enabledExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal));
_enabledExtensions.Remove(extension.First());
}

View File

@@ -108,15 +108,15 @@ public class ExtensionWrapper : IExtensionWrapper
unsafe
{
void* extensionPtr = (void*)nint.Zero;
var extensionPtr = (void*)nint.Zero;
try
{
// -2147024809: E_INVALIDARG
// -2147467262: E_NOINTERFACE
// -2147024893: E_PATH_NOT_FOUND
Guid guid = typeof(IExtension).GUID;
var guid = typeof(IExtension).GUID;
global::Windows.Win32.Foundation.HRESULT hr = PInvoke.CoCreateInstance(Guid.Parse(ExtensionClassId), null, CLSCTX.CLSCTX_LOCAL_SERVER, guid, out extensionPtr);
var hr = PInvoke.CoCreateInstance(Guid.Parse(ExtensionClassId), null, CLSCTX.CLSCTX_LOCAL_SERVER, guid, out extensionPtr);
if (hr.Value == -2147024893)
{
@@ -181,7 +181,7 @@ public class ExtensionWrapper : IExtensionWrapper
{
await StartExtensionAsync();
object? supportedProviders = GetExtensionObject()?.GetProvider(_providerTypeMap[typeof(T)]);
var supportedProviders = GetExtensionObject()?.GetProvider(_providerTypeMap[typeof(T)]);
if (supportedProviders is IEnumerable<T> multipleProvidersSupported)
{
return multipleProvidersSupported;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -47,7 +47,7 @@ public class ProviderSettings
public bool IsFallbackEnabled(TopLevelViewModel command)
{
return !FallbackCommands.TryGetValue(command.Id, out bool enabled) || enabled;
return FallbackCommands.TryGetValue(command.Id, out var enabled) ? enabled : true;
}
public void SetFallbackEnabled(TopLevelViewModel command, bool enabled)

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -126,7 +126,10 @@ public partial class ProviderSettingsViewModel(
{
get
{
field ??= BuildTopLevelViewModels();
if (field == null)
{
field = BuildTopLevelViewModels();
}
return field;
}
@@ -134,8 +137,8 @@ public partial class ProviderSettingsViewModel(
private List<TopLevelViewModel> BuildTopLevelViewModels()
{
CommandProviderWrapper thisProvider = _provider;
TopLevelViewModel[] providersCommands = thisProvider.TopLevelItems;
var thisProvider = _provider;
var providersCommands = thisProvider.TopLevelItems;
// Remember! This comes in on the UI thread!
return [.. providersCommands];
@@ -146,7 +149,10 @@ public partial class ProviderSettingsViewModel(
{
get
{
field ??= BuildFallbackViewModels();
if (field == null)
{
field = BuildFallbackViewModels();
}
return field;
}
@@ -156,8 +162,8 @@ public partial class ProviderSettingsViewModel(
private List<TopLevelViewModel> BuildFallbackViewModels()
{
CommandProviderWrapper thisProvider = _provider;
TopLevelViewModel[] providersCommands = thisProvider.FallbackItems;
var thisProvider = _provider;
var providersCommands = thisProvider.FallbackItems;
// Remember! This comes in on the UI thread!
return [.. providersCommands];

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -22,7 +22,7 @@ public partial class RecentCommandsManager : ObservableObject
{
lock (_lock)
{
(int Index, HistoryItem Item) entry = History
var entry = History
.Index()
.Where(item => item.Item.CommandId == commandId)
.FirstOrDefault();
@@ -32,10 +32,10 @@ public partial class RecentCommandsManager : ObservableObject
// Usually it has a weight of 84, compared to 109 for the VS cmd prompt
if (entry.Item != null)
{
int index = entry.Index;
var index = entry.Index;
// First, add some weight based on how early in the list this appears
int bucket = index switch
var bucket = index switch
{
var i when index <= 2 => 35,
var i when index <= 10 => 25,
@@ -45,7 +45,7 @@ public partial class RecentCommandsManager : ObservableObject
};
// Then, add weight for how often this is used, but cap the weight from usage.
int uses = Math.Min(entry.Item.Uses * 5, 35);
var uses = Math.Min(entry.Item.Uses * 5, 35);
return bucket + uses;
}
@@ -58,12 +58,12 @@ public partial class RecentCommandsManager : ObservableObject
{
lock (_lock)
{
HistoryItem? entry = History
var entry = History
.Where(item => item.CommandId == commandId)
.FirstOrDefault();
if (entry == null)
{
HistoryItem newitem = new HistoryItem() { CommandId = commandId, Uses = 1 };
var newitem = new HistoryItem() { CommandId = commandId, Uses = 1 };
History.Insert(0, newitem);
}
else

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -60,7 +60,7 @@ public record HotkeySettings// : ICmdLineRepresentable
public override string ToString()
{
StringBuilder output = new StringBuilder();
var output = new StringBuilder();
if (Win)
{
@@ -84,7 +84,7 @@ public record HotkeySettings// : ICmdLineRepresentable
if (Code > 0)
{
string localKey = Helper.GetKeyName((uint)Code);
var localKey = Helper.GetKeyName((uint)Code);
output.Append(localKey);
}
else if (output.Length >= 2)
@@ -97,7 +97,7 @@ public record HotkeySettings// : ICmdLineRepresentable
public List<object> GetKeysList()
{
List<object> shortcutList = new List<object>();
var shortcutList = new List<object>();
if (Win)
{
@@ -135,7 +135,7 @@ public record HotkeySettings// : ICmdLineRepresentable
shortcutList.Add(Code);
break;
default:
string localKey = Helper.GetKeyName((uint)Code);
var localKey = Helper.GetKeyName((uint)Code);
shortcutList.Add(localKey);
break;
}
@@ -146,7 +146,7 @@ public record HotkeySettings// : ICmdLineRepresentable
public bool IsValid()
{
return !IsAccessibleShortcut() && (Alt || Ctrl || Win || Shift) && Code != 0;
return IsAccessibleShortcut() ? false : (Alt || Ctrl || Win || Shift) && Code != 0;
}
public bool IsEmpty()
@@ -164,10 +164,10 @@ public record HotkeySettings// : ICmdLineRepresentable
public static bool TryParseFromCmd(string cmd, out object? result)
{
bool win = false, ctrl = false, alt = false, shift = false;
int code = 0;
var code = 0;
string[] parts = cmd.Split('+');
foreach (string part in parts)
var parts = cmd.Split('+');
foreach (var part in parts)
{
switch (part.Trim().ToLower(CultureInfo.InvariantCulture))
{

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -60,7 +60,8 @@ public partial class SettingsModel : ObservableObject
public ProviderSettings GetProviderSettings(CommandProviderWrapper provider)
{
if (!ProviderSettings.TryGetValue(provider.ProviderId, out ProviderSettings? settings))
ProviderSettings? settings;
if (!ProviderSettings.TryGetValue(provider.ProviderId, out settings))
{
settings = new ProviderSettings(provider);
settings.Connect(provider);
@@ -90,9 +91,9 @@ public partial class SettingsModel : ObservableObject
try
{
// Read the JSON content from the file
string jsonContent = File.ReadAllText(FilePath);
var jsonContent = File.ReadAllText(FilePath);
SettingsModel? loaded = JsonSerializer.Deserialize<SettingsModel>(jsonContent, JsonSerializationContext.Default.SettingsModel);
var loaded = JsonSerializer.Deserialize<SettingsModel>(jsonContent, JsonSerializationContext.Default.SettingsModel);
Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse");
@@ -116,23 +117,23 @@ public partial class SettingsModel : ObservableObject
try
{
// Serialize the main dictionary to JSON and save it to the file
string settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.SettingsModel);
var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.SettingsModel);
// Is it valid JSON?
if (JsonNode.Parse(settingsJson) is JsonObject newSettings)
{
// Now, read the existing content from the file
string oldContent = File.Exists(FilePath) ? File.ReadAllText(FilePath) : "{}";
var oldContent = File.Exists(FilePath) ? File.ReadAllText(FilePath) : "{}";
// Is it valid JSON?
if (JsonNode.Parse(oldContent) is JsonObject savedSettings)
{
foreach (KeyValuePair<string, JsonNode?> item in newSettings)
foreach (var item in newSettings)
{
savedSettings[item.Key] = item.Value?.DeepClone();
}
string serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.Options);
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.Options);
File.WriteAllText(FilePath, serialized);
// TODO: Instead of just raising the event here, we should
@@ -158,7 +159,7 @@ public partial class SettingsModel : ObservableObject
internal static string SettingsJsonPath()
{
string directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
Directory.CreateDirectory(directory);
// now, the settings is just next to the exe

View File

@@ -125,22 +125,22 @@ public partial class SettingsViewModel : INotifyPropertyChanged
_settings = settings;
_serviceProvider = serviceProvider;
IEnumerable<CommandProviderWrapper> activeProviders = GetCommandProviders();
Dictionary<string, ProviderSettings> allProviderSettings = _settings.ProviderSettings;
var activeProviders = GetCommandProviders();
var allProviderSettings = _settings.ProviderSettings;
foreach (CommandProviderWrapper item in activeProviders)
foreach (var item in activeProviders)
{
ProviderSettings providerSettings = settings.GetProviderSettings(item);
var providerSettings = settings.GetProviderSettings(item);
ProviderSettingsViewModel settingsModel = new ProviderSettingsViewModel(item, providerSettings, _serviceProvider);
var settingsModel = new ProviderSettingsViewModel(item, providerSettings, _serviceProvider);
CommandProviders.Add(settingsModel);
}
}
private IEnumerable<CommandProviderWrapper> GetCommandProviders()
{
TopLevelCommandManager manager = _serviceProvider.GetService<TopLevelCommandManager>()!;
IEnumerable<CommandProviderWrapper> allProviders = manager.CommandProviders;
var manager = _serviceProvider.GetService<TopLevelCommandManager>()!;
var allProviders = manager.CommandProviders;
return allProviders;
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Common.Helpers;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
@@ -20,7 +21,8 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public partial class TopLevelCommandManager : ObservableObject,
IRecipient<ReloadCommandsMessage>,
IPageContext
IPageContext,
IDisposable
{
private readonly IServiceProvider _serviceProvider;
private readonly TaskScheduler _taskScheduler;
@@ -28,6 +30,7 @@ public partial class TopLevelCommandManager : ObservableObject,
private readonly List<CommandProviderWrapper> _builtInCommands = [];
private readonly List<CommandProviderWrapper> _extensionCommandProviders = [];
private readonly Lock _commandProvidersLock = new();
private readonly SupersedingAsyncGate _reloadCommandsGate;
TaskScheduler IPageContext.Scheduler => _taskScheduler;
@@ -36,6 +39,7 @@ public partial class TopLevelCommandManager : ObservableObject,
_serviceProvider = serviceProvider;
_taskScheduler = _serviceProvider.GetService<TaskScheduler>()!;
WeakReferenceMessenger.Default.Register<ReloadCommandsMessage>(this);
_reloadCommandsGate = new(ReloadAllCommandsAsyncCore);
}
public ObservableCollection<TopLevelViewModel> TopLevelCommands { get; set; } = [];
@@ -56,7 +60,7 @@ public partial class TopLevelCommandManager : ObservableObject,
public async Task<bool> LoadBuiltinsAsync()
{
Stopwatch s = new Stopwatch();
var s = new Stopwatch();
s.Start();
lock (_commandProvidersLock)
@@ -66,8 +70,8 @@ public partial class TopLevelCommandManager : ObservableObject,
// Load built-In commands first. These are all in-proc, and
// owned by our ServiceProvider.
IEnumerable<ICommandProvider> builtInCommands = _serviceProvider.GetServices<ICommandProvider>();
foreach (ICommandProvider provider in builtInCommands)
var builtInCommands = _serviceProvider.GetServices<ICommandProvider>();
foreach (var provider in builtInCommands)
{
CommandProviderWrapper wrapper = new(provider, _taskScheduler);
lock (_commandProvidersLock)
@@ -75,10 +79,10 @@ public partial class TopLevelCommandManager : ObservableObject,
_builtInCommands.Add(wrapper);
}
IEnumerable<TopLevelViewModel> commands = await LoadTopLevelCommandsFromProvider(wrapper);
var commands = await LoadTopLevelCommandsFromProvider(wrapper);
lock (TopLevelCommands)
{
foreach (TopLevelViewModel c in commands)
foreach (var c in commands)
{
TopLevelCommands.Add(c);
}
@@ -99,16 +103,16 @@ public partial class TopLevelCommandManager : ObservableObject,
await commandProvider.LoadTopLevelCommands(_serviceProvider, weakSelf);
List<TopLevelViewModel> commands = await Task.Factory.StartNew(
var commands = await Task.Factory.StartNew(
() =>
{
List<TopLevelViewModel> commands = [];
foreach (TopLevelViewModel item in commandProvider.TopLevelItems)
foreach (var item in commandProvider.TopLevelItems)
{
commands.Add(item);
}
foreach (TopLevelViewModel item in commandProvider.FallbackItems)
foreach (var item in commandProvider.FallbackItems)
{
if (item.IsEnabled)
{
@@ -144,47 +148,11 @@ public partial class TopLevelCommandManager : ObservableObject,
/// <returns>an awaitable task</returns>
private async Task UpdateCommandsForProvider(CommandProviderWrapper sender, IItemsChangedEventArgs args)
{
// Work on a clone of the list, so that we can just do one atomic
// update to the actual observable list at the end
List<TopLevelViewModel> clone = [.. TopLevelCommands];
List<TopLevelViewModel> newItems = [];
int startIndex = -1;
TopLevelViewModel firstCommand = sender.TopLevelItems[0];
int commandsToRemove = sender.TopLevelItems.Length + sender.FallbackItems.Length;
// Tricky: all Commands from a single provider get added to the
// top-level list all together, in a row. So if we find just the first
// one, we can slice it out and insert the new ones there.
for (int i = 0; i < clone.Count; i++)
{
TopLevelViewModel wrapper = clone[i];
try
{
bool isTheSame = wrapper == firstCommand;
if (isTheSame)
{
startIndex = i;
break;
}
}
catch
{
}
}
WeakReference<IPageContext> weakSelf = new(this);
// Fetch the new items
await sender.LoadTopLevelCommands(_serviceProvider, weakSelf);
SettingsModel settings = _serviceProvider.GetService<SettingsModel>()!;
foreach (TopLevelViewModel i in sender.TopLevelItems)
{
newItems.Add(i);
}
foreach (TopLevelViewModel i in sender.FallbackItems)
List<TopLevelViewModel> newItems = [..sender.TopLevelItems];
foreach (var i in sender.FallbackItems)
{
if (i.IsEnabled)
{
@@ -192,28 +160,55 @@ public partial class TopLevelCommandManager : ObservableObject,
}
}
// Slice out the old commands
if (startIndex != -1)
// modify the TopLevelCommands under shared lock; event if we clone it, we don't want
// TopLevelCommands to get modified while we're working on it. Otherwise, we might
// out clone would be stale at the end of this method.
lock (TopLevelCommands)
{
clone.RemoveRange(startIndex, commandsToRemove);
}
else
{
// ... or, just stick them at the end (this is unexpected)
startIndex = clone.Count;
}
// Work on a clone of the list, so that we can just do one atomic
// update to the actual observable list at the end
// TODO: just added a lock around all of this anyway, but keeping the clone
// while looking on some other ways to improve this; can be removed later.
List<TopLevelViewModel> clone = [.. TopLevelCommands];
var startIndex = -1;
// add the new commands into the list at the place we found the old ones
clone.InsertRange(startIndex, newItems);
// Tricky: all Commands from a single provider get added to the
// top-level list all together, in a row. So if we find just the first
// one, we can slice it out and insert the new ones there.
for (var i = 0; i < clone.Count; i++)
{
var wrapper = clone[i];
try
{
if (sender.ProviderId == wrapper.CommandProviderId)
{
startIndex = i;
break;
}
}
catch
{
}
}
// now update the actual observable list with the new contents
ListHelpers.InPlaceUpdateList(TopLevelCommands, clone);
clone.RemoveAll(item => item.CommandProviderId == sender.ProviderId);
clone.InsertRange(startIndex, newItems);
ListHelpers.InPlaceUpdateList(TopLevelCommands, clone);
}
}
public async Task ReloadAllCommandsAsync()
{
// gate ensures that the reload is serialized and if multiple calls
// request a reload, only the first and the last one will be executed.
// this should be superseded with a cancellable version.
await _reloadCommandsGate.ExecuteAsync(CancellationToken.None);
}
private async Task ReloadAllCommandsAsyncCore(CancellationToken cancellationToken)
{
IsLoading = true;
IExtensionService extensionService = _serviceProvider.GetService<IExtensionService>()!;
var extensionService = _serviceProvider.GetService<IExtensionService>()!;
await extensionService.SignalStopExtensionsAsync();
lock (TopLevelCommands)
@@ -235,12 +230,12 @@ public partial class TopLevelCommandManager : ObservableObject,
[RelayCommand]
public async Task<bool> LoadExtensionsAsync()
{
IExtensionService extensionService = _serviceProvider.GetService<IExtensionService>()!;
var extensionService = _serviceProvider.GetService<IExtensionService>()!;
extensionService.OnExtensionAdded -= ExtensionService_OnExtensionAdded;
extensionService.OnExtensionRemoved -= ExtensionService_OnExtensionRemoved;
ImmutableList<IExtensionWrapper> extensions = (await extensionService.GetInstalledExtensionsAsync()).ToImmutableList();
var extensions = (await extensionService.GetInstalledExtensionsAsync()).ToImmutableList();
lock (_commandProvidersLock)
{
_extensionCommandProviders.Clear();
@@ -273,14 +268,14 @@ public partial class TopLevelCommandManager : ObservableObject,
private async Task StartExtensionsAndGetCommands(IEnumerable<IExtensionWrapper> extensions)
{
Stopwatch timer = new Stopwatch();
var timer = new Stopwatch();
timer.Start();
// Start all extensions in parallel
IEnumerable<Task<CommandProviderWrapper?>> startTasks = extensions.Select(StartExtensionWithTimeoutAsync);
var startTasks = extensions.Select(StartExtensionWithTimeoutAsync);
// Wait for all extensions to start
List<CommandProviderWrapper> wrappers = (await Task.WhenAll(startTasks)).Where(wrapper => wrapper != null).Select(w => w!).ToList();
var wrappers = (await Task.WhenAll(startTasks)).Where(wrapper => wrapper != null).Select(w => w!).ToList();
lock (_commandProvidersLock)
{
@@ -288,15 +283,15 @@ public partial class TopLevelCommandManager : ObservableObject,
}
// Load the commands from the providers in parallel
IEnumerable<Task<IEnumerable<TopLevelViewModel>?>> loadTasks = wrappers.Select(LoadCommandsWithTimeoutAsync);
var loadTasks = wrappers.Select(LoadCommandsWithTimeoutAsync);
List<IEnumerable<TopLevelViewModel>> commandSets = (await Task.WhenAll(loadTasks)).Where(results => results != null).Select(r => r!).ToList();
var commandSets = (await Task.WhenAll(loadTasks)).Where(results => results != null).Select(r => r!).ToList();
lock (TopLevelCommands)
{
foreach (IEnumerable<TopLevelViewModel>? commands in commandSets)
foreach (var commands in commandSets)
{
foreach (TopLevelViewModel? c in commands)
foreach (var c in commands)
{
TopLevelCommands.Add(c);
}
@@ -350,11 +345,11 @@ public partial class TopLevelCommandManager : ObservableObject,
List<TopLevelViewModel> commandsToRemove = [];
lock (TopLevelCommands)
{
foreach (IExtensionWrapper extension in extensions)
foreach (var extension in extensions)
{
foreach (TopLevelViewModel command in TopLevelCommands)
foreach (var command in TopLevelCommands)
{
CommandPaletteHost host = command.ExtensionHost;
var host = command.ExtensionHost;
if (host?.Extension == extension)
{
commandsToRemove.Add(command);
@@ -373,7 +368,7 @@ public partial class TopLevelCommandManager : ObservableObject,
{
if (commandsToRemove.Count != 0)
{
foreach (TopLevelViewModel deleted in commandsToRemove)
foreach (var deleted in commandsToRemove)
{
TopLevelCommands.Remove(deleted);
}
@@ -390,7 +385,7 @@ public partial class TopLevelCommandManager : ObservableObject,
{
lock (TopLevelCommands)
{
foreach (TopLevelViewModel command in TopLevelCommands)
foreach (var command in TopLevelCommands)
{
if (command.Id == id)
{
@@ -407,7 +402,7 @@ public partial class TopLevelCommandManager : ObservableObject,
void IPageContext.ShowException(Exception ex, string? extensionHint)
{
string errorMessage = $"A bug occurred in {$"the \"{extensionHint}\"" ?? "an unknown's"} extension's code:\n{ex.Message}\n{ex.Source}\n{ex.StackTrace}\n\n";
var errorMessage = $"A bug occurred in {$"the \"{extensionHint}\"" ?? "an unknown's"} extension's code:\n{ex.Message}\n{ex.Source}\n{ex.StackTrace}\n\n";
CommandPaletteHost.Instance.Log(errorMessage);
}
@@ -419,4 +414,10 @@ public partial class TopLevelCommandManager : ObservableObject,
|| _extensionCommandProviders.Any(wrapper => wrapper.Id == id && wrapper.IsActive);
}
}
public void Dispose()
{
_reloadCommandsGate.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -1,7 +1,8 @@
// Copyright (c) Microsoft Corporation
// 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.Text.Json.Serialization;
using Microsoft.CmdPal.UI.ViewModels.Settings;
namespace Microsoft.CmdPal.UI.ViewModels;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -47,6 +47,8 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
public CommandItemViewModel ItemViewModel => _commandItemViewModel;
public string CommandProviderId => _commandProviderId;
////// ICommandItem
public string Title => _commandItemViewModel.Title;
@@ -63,9 +65,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();
@@ -185,7 +191,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
if (IsFallback)
{
ICommandItem? model = _commandItemViewModel.Model.Unsafe;
var model = _commandItemViewModel.Model.Unsafe;
// RPC to check type
if (model is IFallbackCommandItem fallback)
@@ -222,7 +228,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
public void SetAlias()
{
CommandAlias? commandAlias = Alias is null
var commandAlias = Alias is null
? null
: new CommandAlias(Alias.Alias, Alias.CommandId, Alias.IsDirect);
@@ -232,10 +238,10 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
private void FetchAliasFromAliasManager()
{
AliasManager? am = _serviceProvider.GetService<AliasManager>();
var am = _serviceProvider.GetService<AliasManager>();
if (am != null)
{
CommandAlias? commandAlias = am.AliasFromId(Id);
var commandAlias = am.AliasFromId(Id);
if (commandAlias is not null)
{
// Decouple from the alias manager alias object
@@ -246,7 +252,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
private void UpdateHotkey()
{
TopLevelHotkey? hotkey = _settings.CommandHotkeys.Where(hk => hk.CommandId == Id).FirstOrDefault();
var hotkey = _settings.CommandHotkeys.Where(hk => hk.CommandId == Id).FirstOrDefault();
if (hotkey != null)
{
_hotkey = hotkey.Hotkey;
@@ -279,13 +285,13 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
// Use WyHash64 to generate stable ID hashes.
// manually seeding with 0, so that the hash is stable across launches
ulong result = WyHash64.ComputeHash64(_commandProviderId + DisplayTitle + Title + Subtitle, seed: 0);
var result = WyHash64.ComputeHash64(_commandProviderId + DisplayTitle + Title + Subtitle, seed: 0);
_generatedId = $"{_commandProviderId}{result}";
}
private void DoOnUiThread(Action action)
{
if (_commandItemViewModel.PageContext.TryGetTarget(out IPageContext? pageContext))
if (_commandItemViewModel.PageContext.TryGetTarget(out var pageContext))
{
Task.Factory.StartNew(
action,
@@ -327,16 +333,16 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
/// <returns>true if our Title changed across this call</returns>
private bool UnsafeUpdateFallbackSynchronous(string newQuery)
{
ICommandItem? model = _commandItemViewModel.Model.Unsafe;
var model = _commandItemViewModel.Model.Unsafe;
// RPC to check type
if (model is IFallbackCommandItem fallback)
{
bool wasEmpty = string.IsNullOrEmpty(Title);
var wasEmpty = string.IsNullOrEmpty(Title);
// RPC for method
fallback.FallbackHandler.UpdateQuery(newQuery);
bool isEmpty = string.IsNullOrEmpty(Title);
var isEmpty = string.IsNullOrEmpty(Title);
return wasEmpty != isEmpty;
}
@@ -347,4 +353,9 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
return new PerformCommandMessage(this.CommandViewModel.Model, new Core.ViewModels.Models.ExtensionObject<IListItem>(this));
}
public override string ToString()
{
return $"{nameof(TopLevelViewModel)}: {Id} ({Title}) - display: {DisplayTitle} - fallback: {IsFallback} - enabled: {IsEnabled}";
}
}

View File

@@ -81,7 +81,7 @@ public partial class App : Application
{
AppWindow = new MainWindow();
Windows.AppLifecycle.AppActivationArguments activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
((MainWindow)AppWindow).HandleLaunch(activatedEventArgs);
}
@@ -97,11 +97,8 @@ public partial class App : Application
services.AddSingleton(TaskScheduler.FromCurrentSynchronizationContext());
// Built-in Commands. Order matters - this is the order they'll be presented by default.
#pragma warning disable CA2000 // Dispose objects before losing scope
// justification: these two live for the life of the app
AllAppsCommandProvider allApps = new AllAppsCommandProvider();
IndexerCommandsProvider files = new IndexerCommandsProvider();
#pragma warning restore CA2000 // Dispose objects before losing scope
var allApps = new AllAppsCommandProvider();
var files = new IndexerCommandsProvider();
files.SuppressFallbackWhen(ShellCommandsProvider.SuppressFileFallbackIf);
services.AddSingleton<ICommandProvider>(allApps);
@@ -121,11 +118,8 @@ public partial class App : Application
// for WinGetStatics
try
{
#pragma warning disable CA2000 // Dispose objects before losing scope
// justification: lives for the life of the app
WinGetExtensionCommandsProvider winget = new WinGetExtensionCommandsProvider();
#pragma warning restore CA2000 // Dispose objects before losing scope
Func<string, ICommandItem?> callback = allApps.LookupApp;
var winget = new WinGetExtensionCommandsProvider();
var callback = allApps.LookupApp;
winget.SetAllLookup(callback);
services.AddSingleton<ICommandProvider>(winget);
}
@@ -147,9 +141,9 @@ public partial class App : Application
services.AddSingleton<TopLevelCommandManager>();
services.AddSingleton<AliasManager>();
services.AddSingleton<HotkeyManager>();
SettingsModel sm = SettingsModel.LoadSettings();
var sm = SettingsModel.LoadSettings();
services.AddSingleton(sm);
AppStateModel state = AppStateModel.LoadState();
var state = AppStateModel.LoadState();
services.AddSingleton(state);
services.AddSingleton<IExtensionService, ExtensionService>();
services.AddSingleton<TrayIconService>();

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