Skip to content

Auto-complete comment help for functions #452

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 8 commits into from
May 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;

namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
{
class CommentHelpRequest
{
public static readonly RequestType<CommentHelpRequestParams, CommentHelpRequestResult, object, object> Type
= RequestType<CommentHelpRequestParams, CommentHelpRequestResult, object, object>.Create("powerShell/getCommentHelp");
}

public class CommentHelpRequestResult
{
public string[] Content { get; set; }
}

public class CommentHelpRequestParams
{
public string DocumentUri { get; set; }
public Position TriggerPosition { get; set; }
public bool BlockComment { get; set; }
}
}

62 changes: 50 additions & 12 deletions src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using System.Threading;
using System.Threading.Tasks;
using DebugAdapterMessages = Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter;
using System.Collections;

namespace Microsoft.PowerShell.EditorServices.Protocol.Server
{
Expand Down Expand Up @@ -147,7 +148,7 @@ protected override void Initialize()
this.SetRequestHandler(ScriptRegionRequest.Type, this.HandleGetFormatScriptRegionRequest);

this.SetRequestHandler(GetPSHostProcessesRequest.Type, this.HandleGetPSHostProcessesRequest);

this.SetRequestHandler(CommentHelpRequest.Type, this.HandleCommentHelpRequest);
// Initialize the extension service
// TODO: This should be made awaited once Initialize is async!
this.editorSession.ExtensionService.Initialize(
Expand Down Expand Up @@ -241,9 +242,9 @@ private async Task HandleSetPSSARulesRequest(
var ruleInfos = dynParams.ruleInfos;
foreach (dynamic ruleInfo in ruleInfos)
{
if ((Boolean) ruleInfo.isEnabled)
if ((Boolean)ruleInfo.isEnabled)
{
activeRules.Add((string) ruleInfo.name);
activeRules.Add((string)ruleInfo.name);
}
}
editorSession.AnalysisService.ActiveRules = activeRules.ToArray();
Expand Down Expand Up @@ -306,8 +307,9 @@ private async Task HandleScriptFileMarkersRequest(
{
var markers = await editorSession.AnalysisService.GetSemanticMarkersAsync(
editorSession.Workspace.GetFile(requestParams.fileUri),
editorSession.AnalysisService.GetPSSASettingsHashtable(requestParams.settings));
await requestContext.SendResult(new ScriptFileMarkerRequestResultParams {
AnalysisService.GetPSSASettingsHashtable(requestParams.settings));
await requestContext.SendResult(new ScriptFileMarkerRequestResultParams
{
markers = markers
});
}
Expand Down Expand Up @@ -569,7 +571,7 @@ protected async Task HandleDidChangeConfigurationNotification(
{
bool oldLoadProfiles = this.currentSettings.EnableProfileLoading;
bool oldScriptAnalysisEnabled =
this.currentSettings.ScriptAnalysis.Enable.HasValue ? this.currentSettings.ScriptAnalysis.Enable.Value : false ;
this.currentSettings.ScriptAnalysis.Enable.HasValue ? this.currentSettings.ScriptAnalysis.Enable.Value : false;
string oldScriptAnalysisSettingsPath =
this.currentSettings.ScriptAnalysis.SettingsPath;

Expand Down Expand Up @@ -1072,6 +1074,42 @@ protected async Task HandleGetPSHostProcessesRequest(
await requestContext.SendResult(psHostProcesses.ToArray());
}

protected async Task HandleCommentHelpRequest(
CommentHelpRequestParams requestParams,
RequestContext<CommentHelpRequestResult> requestContext)
{
var scriptFile = EditorSession.Workspace.GetFile(requestParams.DocumentUri);
var expectedFunctionLine = requestParams.TriggerPosition.Line + 2;
var functionDefinitionAst = EditorSession.LanguageService.GetFunctionDefinitionAtLine(
scriptFile,
expectedFunctionLine);
var result = new CommentHelpRequestResult();

if (functionDefinitionAst != null)
{
// todo create a semantic marker api that take only string
var analysisResults = await EditorSession.AnalysisService.GetSemanticMarkersAsync(
scriptFile,
AnalysisService.GetCommentHelpRuleSettings(
true,
false,
requestParams.BlockComment,
true,
"before"));

var analysisResult = analysisResults?.FirstOrDefault(x =>
{
return x.Correction != null
&& x.Correction.Edits[0].StartLineNumber == expectedFunctionLine;
});

// find the analysis result whose correction starts on
result.Content = analysisResult?.Correction.Edits[0].Text.Split('\n').Select(x => x.Trim('\r')).ToArray();
}

await requestContext.SendResult(result);
}

private bool IsQueryMatch(string query, string symbolName)
{
return symbolName.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
Expand Down Expand Up @@ -1134,12 +1172,12 @@ protected Task HandleEvaluateRequest(
// Return an empty result since the result value is irrelevant
// for this request in the LanguageServer
return
requestContext.SendResult(
new DebugAdapterMessages.EvaluateResponseBody
{
Result = "",
VariablesReference = 0
});
requestContext.SendResult(
new DebugAdapterMessages.EvaluateResponseBody
{
Result = "",
VariablesReference = 0
});
});

return Task.FromResult(true);
Expand Down
71 changes: 49 additions & 22 deletions src/PowerShellEditorServices/Analysis/AnalysisService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ private bool hasScriptAnalyzerModule
"PSShouldProcess",
"PSMissingModuleManifestField",
"PSAvoidDefaultValueSwitchParameter",
"PSUseDeclaredVarsMoreThanAssigments"
"PSUseDeclaredVarsMoreThanAssignments"
};

#endregion // Private Fields
Expand Down Expand Up @@ -141,6 +141,54 @@ public AnalysisService(IConsoleHost consoleHost, string settingsPath = null)

#region Public Methods

/// <summary>
/// Get PSScriptAnalyzer settings hashtable for PSProvideCommentHelp rule.
/// </summary>
/// <param name="enable">Enable the rule.</param>
/// <param name="exportedOnly">Analyze only exported functions/cmdlets.</param>
/// <param name="blockComment">Use block comment or line comment.</param>
/// <param name="vscodeSnippetCorrection">Return a vscode snipped correction should be returned.</param>
/// <param name="placement">Place comment help at the given location relative to the function definition.</param>
/// <returns>A PSScriptAnalyzer settings hashtable.</returns>
public static Hashtable GetCommentHelpRuleSettings(
bool enable,
bool exportedOnly,
bool blockComment,
bool vscodeSnippetCorrection,
string placement)
{
var settings = new Dictionary<string, Hashtable>();
var ruleSettings = new Hashtable();
ruleSettings.Add("Enable", enable);
ruleSettings.Add("ExportedOnly", exportedOnly);
ruleSettings.Add("BlockComment", blockComment);
ruleSettings.Add("VSCodeSnippetCorrection", vscodeSnippetCorrection);
ruleSettings.Add("Placement", placement);
settings.Add("PSProvideCommentHelp", ruleSettings);
return GetPSSASettingsHashtable(settings);
}

/// <summary>
/// Construct a PSScriptAnalyzer settings hashtable
/// </summary>
/// <param name="ruleSettingsMap">A settings hashtable</param>
/// <returns></returns>
public static Hashtable GetPSSASettingsHashtable(IDictionary<string, Hashtable> ruleSettingsMap)
{
var hashtable = new Hashtable();
var ruleSettingsHashtable = new Hashtable();

hashtable["IncludeRules"] = ruleSettingsMap.Keys.ToArray<object>();
hashtable["Rules"] = ruleSettingsHashtable;

foreach (var kvp in ruleSettingsMap)
{
ruleSettingsHashtable.Add(kvp.Key, kvp.Value);
}

return hashtable;
}

/// <summary>
/// Perform semantic analysis on the given ScriptFile and returns
/// an array of ScriptFileMarkers.
Expand Down Expand Up @@ -181,27 +229,6 @@ public IEnumerable<string> GetPSScriptAnalyzerRules()
return ruleNames;
}

/// <summary>
/// Construct a PSScriptAnalyzer settings hashtable
/// </summary>
/// <param name="ruleSettingsMap">A settings hashtable</param>
/// <returns></returns>
public Hashtable GetPSSASettingsHashtable(IDictionary<string, Hashtable> ruleSettingsMap)
{
var hashtable = new Hashtable();
var ruleSettingsHashtable = new Hashtable();

hashtable["IncludeRules"] = ruleSettingsMap.Keys.ToArray<object>();
hashtable["Rules"] = ruleSettingsHashtable;

foreach (var kvp in ruleSettingsMap)
{
ruleSettingsHashtable.Add(kvp.Key, kvp.Value);
}

return hashtable;
}

/// <summary>
/// Disposes the runspace being used by the analysis service.
/// </summary>
Expand Down
27 changes: 23 additions & 4 deletions src/PowerShellEditorServices/Language/LanguageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ await AstOperations.GetCompletions(

return completionResults;
}
catch(ArgumentException e)
catch (ArgumentException e)
{
// Bad completion results could return an invalid
// replacement range, catch that here
Expand Down Expand Up @@ -231,15 +231,17 @@ public FindOccurrencesResult FindSymbolsInFile(ScriptFile scriptFile)
AstOperations
.FindSymbolsInDocument(scriptFile.ScriptAst, this.powerShellContext.LocalPowerShellVersion.Version)
.Select(
reference => {
reference =>
{
reference.SourceLine =
scriptFile.GetLine(reference.ScriptRegion.StartLineNumber);
reference.FilePath = scriptFile.FilePath;
return reference;
});

return
new FindOccurrencesResult {
new FindOccurrencesResult
{
FoundOccurrences = symbolReferencesinFile
};
}
Expand Down Expand Up @@ -267,7 +269,7 @@ public async Task<FindReferencesResult> FindReferencesOfSymbol(

// We want to look for references first in referenced files, hence we use ordered dictionary
var fileMap = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
foreach(ScriptFile file in referencedFiles)
foreach (ScriptFile file in referencedFiles)
{
fileMap.Add(file.FilePath, file);
}
Expand Down Expand Up @@ -520,6 +522,23 @@ public ScriptRegion FindSmallestStatementAstRegion(
return ScriptRegion.Create(ast.Extent);
}

/// <summary>
/// Gets the function defined on a given line.
/// </summary>
/// <param name="scriptFile">Open script file.</param>
/// <param name="lineNumber">The 1 based line on which to look for function definition.</param>
/// <returns>If found, returns the function definition on the given line. Otherwise, returns null.</returns>
public FunctionDefinitionAst GetFunctionDefinitionAtLine(
ScriptFile scriptFile,
int lineNumber)
{
var functionDefinitionAst = scriptFile.ScriptAst.Find(
ast => ast is FunctionDefinitionAst && ast.Extent.StartLineNumber == lineNumber,
true);

return functionDefinitionAst as FunctionDefinitionAst;
}

#endregion

#region Private Fields
Expand Down