Skip to content

Change Get-Help behavior to return local help when no online help available #721

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

Merged
merged 15 commits into from
Sep 21, 2018
Merged

Change Get-Help behavior to return local help when no online help available #721

merged 15 commits into from
Sep 21, 2018

Conversation

corbob
Copy link
Contributor

@corbob corbob commented Aug 10, 2018

Current behavior is to attempt to show online help. This returns an error when there is no online help. Would be better to return full output of online help if there is some.

Furthermore: if you try to get help for a command that doesn't exist, currently it takes 4-5 seconds and doesn't do anything. This will change that to less than a second and return an error.

Addresses some of the discussion around PowerShell/vscode-powershell#884

@corbob corbob changed the title WIP: Change Get-Help behavior to return help when no online help available WIP: Change Get-Help behavior to return local help when no online help available Aug 10, 2018
@corbob corbob changed the title WIP: Change Get-Help behavior to return local help when no online help available Change Get-Help behavior to return local help when no online help available Aug 18, 2018
Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good but I have a few ideas to make it a bit more readable :)

IEnumerable<object> isCommand = await editorSession.PowerShellContext.ExecuteCommand<object>(psCommand);
if (isCommand != null && isCommand.Count() > 0){
psCommand = new PSCommand();
psCommand.AddCommand("Get-Help");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to chain this:

psCommand.AddCommand("Get-Help")
    .AddArgument(helpParams)
    .AddCommand("Select-Object")
    .AddArgument("RelatedLinks")

etc...

PSCommand psCommand = new PSCommand();
// Check if it's an actual command first. This returns near instant. Get-Help takes a good 5 seconds to return when not a command.
psCommand.AddCommand("Get-Command").AddArgument(helpParams);
IEnumerable<object> isCommand = await editorSession.PowerShellContext.ExecuteCommand<object>(psCommand);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think you could use something instead of object here? It should be a CmdletInfo I think

IEnumerable<object> relatedLinks = await editorSession.PowerShellContext.ExecuteCommand<object>(psCommand);
psCommand = new PSCommand();
psCommand.AddCommand("Get-Help");
psCommand.AddArgument(helpParams);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chain ALL the thingssss

psCommand.AddArgument(helpParams);
psCommand.AddCommand("Select-Object").AddArgument("RelatedLinks");
IEnumerable<object> relatedLinks = await editorSession.PowerShellContext.ExecuteCommand<object>(psCommand);
psCommand = new PSCommand();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could probably be a bit more descriptive here :) have a new variable instead of reusing psCommand and call it like... getHelpCommand... call the previous one getRelatedLinksCommand etc.

bool sendOutput = false;
// Check if the first returned item converted to string is an empty object.
// From experience this is the return when there are no RelatedLinks.
if (relatedLinks != null && relatedLinks.First().ToString() != "@{RelatedLinks=}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we could probably handle this a little better.

Also something I noticed:

A module could have a related links section but Online version would be empty:

(Get-Help AdvancedHistory).RelatedLinks

Online Version:

(Get-Help AdvancedHistory | Select-Object RelatedLinks)

relatedLinks
------------
@{navigationLink=@{linkText=Online Version:; uri=}}

So comparing the ToString value might not be enough. You can use PSCustomObjects in C# and drill into them like a hashtable so maybe try to do that.

(Get-Help AdvancedHistory | Select-Object RelatedLinks)[0].relatedLinks.navigationLink.uri for example returns nothing. @omniomi is to blame for that module :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another option (which I'm liking better) is to convert 95% of this logic into a PowerShell script and just use AddScript. Especially since this function doesn't return anything, this would be a good time to do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funny, I asked about the related links in slack and was told that there would never be a related link without a online version. Clearly this is false, so this needs to be fixed. I like the idea of the powershell script instead. I might even still have one in the commit history (or I might have removed it...)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drilling down a PSObject is kind of obtuse in C#, but it's still probably better to keep it in C#. I'd just make it a static helper method like this

internal static bool TryGetHelpUri(PSObject helpInfo, out string helpUri)
{
    PSObject links = helpInfo.Properties['relatedLinks']?.Value as PSObject;
    if (links == null)
    {
        return false;
    }

    PSObject[] navigation = links.Properties['navigationLink']?.Value as PSObject[];
    if (navigation == null)
    {
        return false;
    }

    helpUri = navigation.FirstOrDefault()?.Properties['uri']?.Value as string;
    return !string.IsNullOrWhiteSpace(helpUri);
}
PSObject helpInfo = MethodThatInvokesGetHelp();
if (TryGetHelpUri(helpInfo, out string helpUri)
{
    // use help uri
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's better. 👌

@corbob
Copy link
Contributor Author

corbob commented Sep 3, 2018

I've refactored the code to use a PowerShell function to check for if the command exists instead of executing a bunch of times and then delving into a property that doesn't seem to be completely consistent.

The PowerShell function operates off of the presumption that the Get-Command and Get-Help cmdlets will error with predictable exceptions and we catch those exceptions only for our handling.

Copy link
Collaborator

@SeeminglyScience SeeminglyScience left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good :) just a couple changes

Get-Command $Command -ErrorAction Stop
$CommandExists = $true
} catch [System.Management.Automation.CommandNotFoundException] {
Write-Warning ""$Command does not exist.""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of a warning, I'd prefer if it just let the exception come through.

Maybe to clean up the error message a little bit by using ThrowTerminatingError (requires [CmdletBinding()])

} catch [System.Management.Automation.CommandNotFoundException] {
    $PSCmdlet.ThrowTerminatingError($PSItem)
    return
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! I was trying to do Write-Error, but it was too much information, and it output the __Check-Help function name. The only thing perhaps is that this causes an uncaught exception in the debugger. Should we be wrapping the script execution in a try catch block?

Also output to Host. This is an expiriment on vscode-powershell issue 884.
If not a command, Get-Help takes 4-5 seconds to return, and we will crash PSES if we don't account for the return of a 0 count.
Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good. I just have a few comments :)

await editorSession.PowerShellContext.ExecuteCommand<object>(psCommand);

if (helpParams == null) { helpParams = "Get-Help"; }
string script = @"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should pull this script out to a static private property so it's not created every time the function is run.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved this to a static private property, not sure if positioning it at the top of the file is the best spot for it, or what the convention is for this type of scenario.

Try {
Microsoft.PowerShell.Core\Get-Command $CommandName -ErrorAction Stop | Out-Null
} catch [System.Management.Automation.CommandNotFoundException] {
$PSCmdlet.ThrowTerminatingError($PSItem)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, based on your logic here, that this this ThrowTerminatingError is redundant if you already have ErrorAction Stop

@SeeminglyScience correct me if I'm wrong here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in other words you might be able to ditch the try/catch all together.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it with the try catch because without it looks like this:
image

and with it looks like this:
image

But I will go with whichever you and @SeeminglyScience want.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tylerl0706 the benefit of using ThrowTerminatingError over letting the exception propagate is that it changes the "context" of the error from the callee(?) to the caller.

Here's a little function you can use to check it out

function Test-TerminatingError {
    [CmdletBinding()]
    param(
        [Parameter(Position = 0, Mandatory)]
        [ValidateSet('NormalThrow', 'ThrowTerminatingError')]
        [string] $ThrowType
    )
    end {
        if ($ThrowType -eq 'NormalThrow') {                j
            Get-Command DefinitelyDoesntExist -ErrorAction Stop
        }

        try {
            Get-Command DefinitelyDoesntExist -ErrorAction Stop
        } catch {
            $PSCmdlet.ThrowTerminatingError($PSItem)
        }
    }
}

If NormalThrow is specified, this is what is displayed

Get-Command : The term 'DefinitelyDoesntExist' is not recognized as the name of a cmdlet, function, script file, or
operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try
again.
At line:11 char:13
+             Get-Command DefinitelyDoesntExist -ErrorAction Stop
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (DefinitelyDoesntExist:String) [Get-Command], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException,Microsoft.PowerShell.Commands.GetCommandCommand

It shows the line in the function/script itself that failed, which is implementation detail the user doesn't care about. Contrast that to this:

Test-TerminatingError : The term 'DefinitelyDoesntExist' is not recognized as the name of a cmdlet, function, script
file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct
and try again.
At line:1 char:1
+ Test-TerminatingError ThrowTerminatingError
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (DefinitelyDoesntExist:String) [Test-TerminatingError], CommandNotFoundE
   xception
    + FullyQualifiedErrorId : CommandNotFoundException,Test-TerminatingError

It also lets you fully (well mostly) customize the displayed error by creating your own ErrorRecord

All that said, @corbob that is definitely not how I expected that to look... something in our host does not handle that right...

We should still stick with it, but we should definitely look into why that's doing that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SeeminglyScience should we be opening an issue for "something in our host does not handle that right...
...
we should definitely look into why that's doing that." ?

Copy link
Collaborator

@SeeminglyScience SeeminglyScience left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few more things ;)

Also:
* make CheckHelpScript constant
* assign output of Get-Command to $null
* Move CheckHelpScript to top of class.
Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉 thanks for putting up with our comments 😄

Copy link
Contributor

@rjmholt rjmholt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@@ -251,8 +251,9 @@ protected Task Stop()
{
if (helpParams == null) { helpParams = "Get-Help"; }

PSCommand checkHelpPSCommand = new PSCommand();
checkHelpPSCommand.AddScript(CheckHelpScript, useLocalScope: true).AddArgument(helpParams);
PSCommand checkHelpPSCommand = new PSCommand()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was about to complain about the lack of var here, but technically the type here is the return type of the method. So all I feel is ambivalence.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used PSCommand due to all the other comments about being explicit about the variable type 😁

Copy link
Contributor

@rjmholt rjmholt Sep 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wholly agree. In general you wouldn't lose explicit variable type constraints with var when the constructor is on the RHS of the assignment (var x = new Banana();), but here it's more nuanced because of a sort of Builder pattern scenario...

{
public static readonly
RequestType<string, object, object, object> Type =
RequestType<string, object, object, object>.Create("powerShell/showOnlineHelp");
RequestType<string, object, object, object>.Create("powerShell/showHelp");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to play Devil's advocate here:

Is the reason for the breaking change just that "showOnlineHelp" would be an inaccurate name? Or does possibly getting offline help make the change breaking too?

Just thinking that there are plenty of APIs where the message name doesn't quite mean what it used to because changing the message name is a bigger breaking change than changing what the message does.

It may be that we want to break the message compatibility anyway, but we do have other clients that might benefit from us not changing the name.

Anyway, just want to ask the question: "if we change the functionality but not the message name, is this still a breaking change?" with the followup "if the message name change is the breaking change, is it worth the breakage?"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given this is a command in the command pallet and not a settings related change or something along those lines... I think if someone complains we can consider a patch that will re-ADD the old functionality.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what @rjmholt is talking about is breaking on both sides with the change of the message as PSES isn't used exclusively by vscode-powershell. I'm not opposed to keeping the message name the old one (in fact I think it should be a matter of just backing out a single commit on both sides...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds reasonable!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm game 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh sure, now I need to first remove my big foot from my mouth and figure out how to rewrite the git history...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There we go, apparently it's a simple interactive rebase off of master 😀

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on how brave you feel:

Brave way that deletes history

git reset --hard HEAD~1

There's a safer way to do it without git revert that I can't find a good answer for.

It makes no difference to the repo, since it will all be squashed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the interactive rebase allowed me to delete the commits that changed the wording of the message (which is partly why I made them separate commits) while still preserving the super difficult minor bug fix commit at the end there.

@corbob
Copy link
Contributor Author

corbob commented Sep 6, 2018

Last minute commit that hopefully doesn't change the approvals 😜

I was looking at the overall changes and was reminded of a scenario where I found if nothing was sent across it was actually empty string and not null (discovered while working on the Command Explorer). I believe the only way for this to happen with the Show Help is if we have a completely empty file and try, but this at least prevents an error in that instance.

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're watching you @corbob 😎

@@ -249,7 +249,7 @@ protected Task Stop()
string helpParams,
RequestContext<object> requestContext)
{
if (helpParams == null) { helpParams = "Get-Help"; }
if (helpParams == "") { helpParams = "Get-Help"; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth just making this string.IsNullOrEmpty(helpParams). We lose nothing really if the string is never null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what you're talking about... it's definitely string.IsNullOrEmpty whistles innocently

helpParams doesn't actually come over null...
It comes over as empty string. Fix this to prevent a likely rare bug condition because we can.
Copy link
Collaborator

@SeeminglyScience SeeminglyScience left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 😀

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 😊

[String]$CommandName
)
Try {
$null = Microsoft.PowerShell.Core\Get-Command $CommandName -ErrorAction Stop
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for module prefixed commands!

param (
[String]$CommandName
)
Try {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below you use a lower case try (and everywhere else uses lower case keywords). Probably should tweak that here to be consistent. :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll get this corrected after the below comment is replied to so I'm not doing a bunch of extra work :)

[String]$CommandName
)
Try {
$null = Microsoft.PowerShell.Core\Get-Command $CommandName -ErrorAction Stop
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this script is only used in the HandleShowOnlineHelpRequest method, then it should be moved inside that method as a const string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to make sense. As was said in slack: if it's a const string it won't be stack alloc'd each time the methos is invoked. https://stackoverflow.com/questions/7616448/const-string-inside-a-function

Unless @tylerl0706 @SeeminglyScience or @rjmholt have any objections I'll move the string back into the function as const...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reasonable to me! Let's hear what 1 other has to say

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep no issues with that 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the string into the function as const

(also creeped the scope a bit by handling both commands that could come in (although not from vscode-powershell...) and marking one as deprecated.

If someone calls showOnlineHelp we will write a warning that it's deprecated and then call showHelp.

This should allow us to more easily update to remove the references to Online.

Also: Move CheckHelpScript into the function and make it constant...
)
{
const string deprecatedWarning = @"
Write-Warning ""'powerShell/showOnlineHelp' has been deprecated. Use 'powerShell/showHelp' instead.""";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooh, does this show in the Integrated Console?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does 😃 it's due to sendOutputToHost: true

Copy link
Contributor

@rjmholt rjmholt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM3!

@@ -7,10 +7,18 @@

namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
{
// We don't expect ShowOnlineHelpRequest to come from vscode anymore, but it could come from another editor.
// TODO: Note that it's deprecated if it's called???
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to mark this with the [Obsolete] attribute :)

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉

public class ShowOnlineHelpRequest
{
public static readonly
RequestType<string, object, object, object> Type =
RequestType<string, object, object, object>.Create("powerShell/showOnlineHelp");
}
public class ShowHelpRequest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: the indentation of this line is a bit off

@@ -230,22 +231,45 @@ protected Task Stop()
}
});
}

protected async Task HandleShowOnlineHelpRequest(
protected async Task HandleShowHelpRequest(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: newline between functions

Write-Warning ""'powerShell/showOnlineHelp' has been deprecated. Use 'powerShell/showHelp' instead.""";
PSCommand commandDeprecated = new PSCommand()
.AddScript(deprecatedWarning);
await editorSession.PowerShellContext.ExecuteCommand<PSObject>(commandDeprecated, sendOutputToHost: true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of (or in addition to) a PSIC warning, we could log a warning to the log file. I could go either way on this.

Copy link
Contributor

@rjmholt rjmholt Sep 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 on log file Actually, users won't read the log file, and it won't be relevant when looking back through the logs. We should warn the user somehow.

The ideal would be to launch a popup. But that would be a lot for this PR. Perhaps instead we can leave it as a PSIC message and open an issue to make a popup?

Copy link
Contributor

@rkeithhill rkeithhill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

)
{
const string deprecatedWarning = @"
Write-Warning ""'powerShell/showOnlineHelp' has been deprecated. Use 'powerShell/showHelp' instead.""";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of AddScript you could use AddCommand here instead.

pwsh.AddCommand("Microsoft.PowerShell.Utility\\Write-Verbose")
    .AddParameter("Message", "'powerShell/showOnlineHelp' has been deprecated. Use 'powerShell/showHelp instead.");

Using command instead allows it to create a command processor for that command specifically, instead of having to parse a script.

PSCommand commandDeprecated = new PSCommand()
.AddScript(deprecatedWarning);
.AddCommand("Microsoft.PowerShell.Utility\\Write-Verbose")
.AddParameter("Message", ";powerShell/showOnlineHelp' has been deprecated. Use 'powerShell/showHelp' instead.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo here I think (from ; to ')

Copy link
Contributor

@rjmholt rjmholt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok this looks good to me! If you can just make the whitespace changes, we can merge this in and then the vscode one :)


namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
{
[Obsolete("This class is deprecated. Use ShowHelpRequest instead.")]
public class ShowOnlineHelpRequest
{
public static readonly
RequestType<string, object, object, object> Type =
RequestType<string, object, object, object>.Create("powerShell/showOnlineHelp");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a newline here?

@@ -122,6 +122,7 @@ public void Start()
this.HandleDocumentRangeFormattingRequest);

this.messageHandlers.SetRequestHandler(ShowOnlineHelpRequest.Type, this.HandleShowOnlineHelpRequest);
this.messageHandlers.SetRequestHandler(ShowHelpRequest.Type, this.HandleShowHelpRequest);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add newlines around this line to keep the style consistent?

} catch [System.Management.Automation.PSInvalidOperationException] {
Microsoft.PowerShell.Core\Get-Help $CommandName -Full
}";
if (string.IsNullOrEmpty(helpParams)) { helpParams = "Get-Help"; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a newline above this if

PSCommand checkHelpPSCommand = new PSCommand()
.AddScript(CheckHelpScript, useLocalScope: true)
.AddArgument(helpParams);
await editorSession.PowerShellContext.ExecuteCommand<PSObject>(checkHelpPSCommand, sendOutputToHost: true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a newline above the first await statement

await requestContext.SendResult(null);
}
protected async Task HandleShowOnlineHelpRequest(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely add a newline before this method definition here

PSCommand commandDeprecated = new PSCommand()
.AddCommand("Microsoft.PowerShell.Utility\\Write-Verbose")
.AddParameter("Message", ";powerShell/showOnlineHelp' has been deprecated. Use 'powerShell/showHelp' instead.");
await editorSession.PowerShellContext.ExecuteCommand<PSObject>(commandDeprecated, sendOutputToHost: true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a newline above this await

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ship it 🚢

Copy link
Contributor

@rjmholt rjmholt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sold!

@rjmholt rjmholt merged commit 11f4e9b into PowerShell:master Sep 21, 2018
@rjmholt
Copy link
Contributor

rjmholt commented Sep 27, 2018

This doesn't seem to be working now that it's merged in. Going to investigate today.

@rjmholt
Copy link
Contributor

rjmholt commented Sep 27, 2018

Can't reproduce the behaviour I saw earlier.

But it looks like Get-Help -Online doesn't fail if there's no internet or the help site is down.

What scenarios would trigger the offline help?

I'm trying to think of a way to identify when online help is inaccessible and fall back appropriately.

@rjmholt
Copy link
Contributor

rjmholt commented Sep 27, 2018

Spoke to @adityapatwardhan offline.

Get-Help -Online won't give us any indication if getting online help failed -- it just passes a URL to the browser and forgets. It might give an error if there's no browser, but I can't imagine a common scenario where VSCode is installed but there's no browser.

There's also no simple way to get the help URL out of Get-Help to test it ourselves.

He suggested getting local help first and falling back to online instead.

The problem is the Get-Help is designed never to throw, so you get a generated help object back instead. But, that object won't have the Description property populated. So that could be a check.

Ideally what we turn this into would be:

  • Ctrl+F1 gets local help first.
  • If description is populated, we turn the whole thing into a string (with Out-String) and send it in a message to VSCode to put in a pane.
  • Otherwise, we run Get-Help -Online and return null (in which case VSCode knows to do nothing).

@SeeminglyScience
Copy link
Collaborator

@rjmholt

There's also no simple way to get the help URL out of Get-Help to test it ourselves.

What about Microsoft.PowerShell.Commands.GetHelpCodeMethods.GetHelpUri(PSObject)?

Note: The PSObject required is a CommandInfo object (like from Get-Command), and it needs to be invoked from the pipeline thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants