New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for custom CommandNotFound OSC #16848
base: main
Are you sure you want to change the base?
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
779cda8
to
534c2d9
Compare
if (WI_IsFlagSet(source, SuggestionsSource::WinGetCommandNotFound) && | ||
context != nullptr) | ||
{ | ||
const auto recentCommands = Command::ToSendInputCommands(context.WinGetSuggestions()); | ||
for (const auto& t : recentCommands) | ||
{ | ||
commandsCollection.push_back(t); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zadjii-msft is this kinda what you meant by adding the new suggestions to the context? If that's the case, we should rename the context to something else. Maybe ShellIntegrationContext
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly what I was thinking. My quick recommendations:
- I'd probably not name the member
WinGetSuggestions
, but insteadQuickFixes
. I'd think these should come through just the same as any other "quick fix" we add to the control (i.e. like a VT-driven one). - the same thing but with the flag in
SuggestionsSource
- that pobably also needs to get added to theJSON_FLAG_MAPPER
inTerminalSettingsSerializationHelpers.h
, asquickFixes
- we probably want to pass a
\uEA80
icon in here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oofda I see the comment about the OEM icon below now. Well, similar idea I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh huh, maybe we do want to give the winget suggestions a different icon. That's actually a really fun idea.
// TODO CARLOS: should we delete this after a new command is run? Or delete it after a suggestion is used? Or just after the next winget suggestion (current impl)? | ||
// No clue which we should do. Thoughts? | ||
context->WinGetSuggestions(_cachedWinGetSuggestions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zadjii-msft thoughts on how to proceed here? I think...
- delete after a new command is run: doable, but I'm not familiar with shell integration enough. If we wanna go this route, tips?
- delete after a suggestion is used: this doesn't sound like something we can do easily. And also it doesn't sound like it'd feel totally right, imo.
- delete after the next winget suggestion: I like that we don't hold on to stale suggestions, but I don't like that it's held on after another command is run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay this is gonna sound like maybe a wacky idea, but what if we just stored these on the buffer, on the ROW itself? Kinda a bit like what I'm doing over in https://github.com/microsoft/terminal/pull/16937/files#diff-af0f626e5bb6718b60aa712a64c969e274f30968b5e24cc013318cf5c6badc12. If we just have one collection of suggestions per-row (a std::vector<wstring>
), then we'd be able to just iterate up to the most recent collection of quick fixes, and only return those.
BUT even as I'm typing that up - I'd guess that a vector of strings, even an empty one, isn't low enough overhead to have the buffer go fast.
I guess a map of row->vector would work, but you'd have to deal with reflowing it and decrementing all rows on circling. Probably not terrible.
Knowing the row that the suggestion was printed on would also make drawing the icons more trivial.
another alternative / future thought - instead of just returning the most recent quick fix, you could further refine to be "the most recent quick fix if it was emitted in the most recent command with output". The most recent mark probably doesn't have output (it's just a prompt & possibly typed command), so keep looking backwards till you find one with output. Then only return quick fixes in that mark's extent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... I'm leaning towards this idea:
I guess a map of row->vector would work, but you'd have to deal with reflowing it and decrementing all rows on circling. Probably not terrible.
We need to do this now so that we don't have the same suggestions appear forever. But the annoying thing is going to be that until quick fixes is implemented, we're just gonna hold on to the winget suggestions until they the buffer circles with no way to access them (since sxn ui can only be applied to the input line, right?). We can probably add some code in the interim that forces the map to only hold on to the most recent one, then undo that behavior as a part of quick fixes.
Another thing to point out here is that when the VT sequence is emitted, it'll be at the beginning of the "is not recognized..." line (where the flag is drawn in the image below)
So, this means that if there's multiple "is not recognized" messages, output from a command is going to have to consolidate all of them to suggest them as a quick fix on the next prompt. But without shell integration, we won't know which ones to consolidate.
As for this:
another alternative / future thought - instead of just returning the most recent quick fix, you could further refine to be "the most recent quick fix if it was emitted in the most recent command with output". The most recent mark probably doesn't have output (it's just a prompt & possibly typed command), so keep looking backwards till you find one with output. Then only return quick fixes in that mark's extent.
I like that idea a lot, but we can't rely on scroll marks existing since it requires changes to the shell, right?
So, if we're going to need shell integration anyways, perhaps the "the most recent quick fix if it was emitted in the most recent command with output" idea is best then. It just feels weird to couple it with shell integration 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're just gonna hold on to the winget suggestions
oh no a couple strings, oh nooooo /s. Honestly, if it's the right design in the next ~2 releases timeframe, then let's just do it right and not try and over-engineer the interim solution.
So, this means that if there's multiple "is not recognized" messages, output from a command is going to have to consolidate all of them to suggest them as a quick fix on the next prompt. But without shell integration, we won't know which ones to consolidate
If we store them as a property on the row, then we can do:
- If there's a most recent prompt scroll mark, then get all the quick fixes from all the ROWs until that prompt
- There's no shell integration? Then just get the quick fixes on the most recent ROW with quick fixes.
Basically, it works without shell integration, but it could work better with it
222e334
to
c2417bb
Compare
auto c = winrt::make_self<Command>(); | ||
c->_ActionAndArgs = actionAndArgs; | ||
c->_name = command; | ||
c->_iconPath = L"\ue74c"; // OEM icon |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// TODO CARLOS: should we delete this after a new command is run? Or delete it after a suggestion is used? Or just after the next winget suggestion (current impl)? | ||
// No clue which we should do. Thoughts? | ||
context->WinGetSuggestions(_cachedWinGetSuggestions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay this is gonna sound like maybe a wacky idea, but what if we just stored these on the buffer, on the ROW itself? Kinda a bit like what I'm doing over in https://github.com/microsoft/terminal/pull/16937/files#diff-af0f626e5bb6718b60aa712a64c969e274f30968b5e24cc013318cf5c6badc12. If we just have one collection of suggestions per-row (a std::vector<wstring>
), then we'd be able to just iterate up to the most recent collection of quick fixes, and only return those.
BUT even as I'm typing that up - I'd guess that a vector of strings, even an empty one, isn't low enough overhead to have the buffer go fast.
I guess a map of row->vector would work, but you'd have to deal with reflowing it and decrementing all rows on circling. Probably not terrible.
Knowing the row that the suggestion was printed on would also make drawing the icons more trivial.
another alternative / future thought - instead of just returning the most recent quick fix, you could further refine to be "the most recent quick fix if it was emitted in the most recent command with output". The most recent mark probably doesn't have output (it's just a prompt & possibly typed command), so keep looking backwards till you find one with output. Then only return quick fixes in that mark's extent.
if (WI_IsFlagSet(source, SuggestionsSource::WinGetCommandNotFound) && | ||
context != nullptr) | ||
{ | ||
const auto recentCommands = Command::ToSendInputCommands(context.WinGetSuggestions()); | ||
for (const auto& t : recentCommands) | ||
{ | ||
commandsCollection.push_back(t); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly what I was thinking. My quick recommendations:
- I'd probably not name the member
WinGetSuggestions
, but insteadQuickFixes
. I'd think these should come through just the same as any other "quick fix" we add to the control (i.e. like a VT-driven one). - the same thing but with the flag in
SuggestionsSource
- that pobably also needs to get added to theJSON_FLAG_MAPPER
inTerminalSettingsSerializationHelpers.h
, asquickFixes
- we probably want to pass a
\uEA80
icon in here.
if (WI_IsFlagSet(source, SuggestionsSource::WinGetCommandNotFound) && | ||
context != nullptr) | ||
{ | ||
const auto recentCommands = Command::ToSendInputCommands(context.WinGetSuggestions()); | ||
for (const auto& t : recentCommands) | ||
{ | ||
commandsCollection.push_back(t); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oofda I see the comment about the OEM icon below now. Well, similar idea I guess.
if (WI_IsFlagSet(source, SuggestionsSource::WinGetCommandNotFound) && | ||
context != nullptr) | ||
{ | ||
const auto recentCommands = Command::ToSendInputCommands(context.WinGetSuggestions()); | ||
for (const auto& t : recentCommands) | ||
{ | ||
commandsCollection.push_back(t); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh huh, maybe we do want to give the winget suggestions a different icon. That's actually a really fun idea.
@@ -439,6 +439,11 @@ | |||
<ResolvedFrom>CppWinRTImplicitlyExpandTargetPlatform</ResolvedFrom> | |||
<IsSystemReference>True</IsSystemReference> | |||
</Reference> | |||
<Reference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\Microsoft.Management.Deployment.winmd"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but also, just adding this here, is that enough to actually let our package know what dll these types are implemented in? Or do we need to do something to make sure the winget package (that presumably implements these) is actually in our package graph?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No clue! This and this comment are gonna be my next step (figuring out how to actually interact with WinGet).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a thought: stage this as two PRs.
- this first one: plumbing, store the "quick fix" in the buffer, plumb into sxnui. But only store a blind
winget install foo
, don't actually do the package lookup.- probably just hide this behind velocity into canary only for the time being
- a second PR that enlightens the suggestion to include real winget results for
foo
.
Adds support for custom OSC "command not found" sequence. Upon receiving the "CmdNotFound" variant with the missing command payload, we search for a relevant package using winget and present the suggestions to the user in the suggestions UI (when they open the Suggestions UI).