From a60a91e4aa96947ad0a73a3b0a88e36b4ffd2520 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Sat, 20 May 2017 20:42:05 -0700 Subject: [PATCH 1/6] Add method to detect comment help location in a function --- .../Language/LanguageService.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index 3a953f5f5..33469e90c 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -539,6 +539,57 @@ public FunctionDefinitionAst GetFunctionDefinitionAtLine( return functionDefinitionAst as FunctionDefinitionAst; } + // todo add xml doc + public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( + ScriptFile scriptFile, + int lineNumber, + out string helpLocation) + { + var foundAst = scriptFile.ScriptAst.FindAll( + ast => + { + // find all the script definitions that contain the line `lineNumber` + var fdAst = ast as FunctionDefinitionAst; + if (fdAst == null) + { + return false; + } + + return fdAst.Body.Extent.StartLineNumber < lineNumber && + fdAst.Body.Extent.EndLineNumber > lineNumber; + }, + true).Aggregate((x, y) => + { + // of all the function definitions found, return the innermost function definition that contains + // `lineNumber` + if (x.Extent.StartOffset >= y.Extent.StartOffset && x.Extent.EndOffset <= x.Extent.EndOffset) + { + return x; + } + + return y; + }); + + // TODO use tokens to check for non empty character instead of just checking for line offset + // check if the line number is the first line in the function body + // check if the line number is the last line in the function body + if (foundAst == null) + { + helpLocation = "before"; + return GetFunctionDefinitionAtLine(scriptFile, lineNumber + 1); + } + + // check if the next line contains a function definition + var funcDefnAst = foundAst as FunctionDefinitionAst; + if (funcDefnAst.Body.Extent.StartLineNumber == lineNumber - 1) + { + helpLocation = "begin"; + } + + helpLocation = "end"; + return funcDefnAst; + } + #endregion #region Private Fields From 77eab6dbe61d1f14ce0d6e7623f5d44bc9728cba Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Sat, 20 May 2017 23:31:26 -0700 Subject: [PATCH 2/6] Auto-complete help in function body --- .../Server/LanguageServer.cs | 30 +++++++++--------- .../Language/LanguageService.cs | 31 ++++++++++++------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs index 43ca4b91d..572c924a4 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs @@ -1082,14 +1082,16 @@ protected async Task HandleCommentHelpRequest( { var scriptFile = EditorSession.Workspace.GetFile(requestParams.DocumentUri); var expectedFunctionLine = requestParams.TriggerPosition.Line + 2; - var functionDefinitionAst = EditorSession.LanguageService.GetFunctionDefinitionAtLine( + string helpLocation; + var functionDefinitionAst = EditorSession.LanguageService.GetFunctionDefinitionForHelpComment( scriptFile, - expectedFunctionLine); - var result = new CommentHelpRequestResult(); + requestParams.TriggerPosition.Line + 1, + out helpLocation); + var result = new CommentHelpRequestResult(); if (functionDefinitionAst != null) { - // todo create a semantic marker api that take only string + // todo create a semantic marker api that take only string var analysisResults = await EditorSession.AnalysisService.GetSemanticMarkersAsync( scriptFile, AnalysisService.GetCommentHelpRuleSettings( @@ -1097,16 +1099,16 @@ protected async Task HandleCommentHelpRequest( 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(); + helpLocation)); + + result.Content = analysisResults? + .FirstOrDefault()? + .Correction? + .Edits[0] + .Text + .Split('\n') + .Select(x => x.Trim('\r')) + .ToArray(); } await requestContext.SendResult(result); diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index 33469e90c..9102fcdf9 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -545,7 +545,7 @@ public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( int lineNumber, out string helpLocation) { - var foundAst = scriptFile.ScriptAst.FindAll( + var foundAsts = scriptFile.ScriptAst.FindAll( ast => { // find all the script definitions that contain the line `lineNumber` @@ -558,7 +558,16 @@ public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( return fdAst.Body.Extent.StartLineNumber < lineNumber && fdAst.Body.Extent.EndLineNumber > lineNumber; }, - true).Aggregate((x, y) => + true); + + // check if the next line contains a function definition + if (foundAsts == null || !foundAsts.Any()) + { + helpLocation = "before"; + return GetFunctionDefinitionAtLine(scriptFile, lineNumber + 1); + } + + var funcDefnAst = foundAsts.Cast().Aggregate((x, y) => { // of all the function definitions found, return the innermost function definition that contains // `lineNumber` @@ -570,24 +579,24 @@ public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( return y; }); + // TODO fix help completion in nested functions // TODO use tokens to check for non empty character instead of just checking for line offset // check if the line number is the first line in the function body // check if the line number is the last line in the function body - if (foundAst == null) + if (funcDefnAst.Body.Extent.StartLineNumber == lineNumber - 1) { - helpLocation = "before"; - return GetFunctionDefinitionAtLine(scriptFile, lineNumber + 1); + helpLocation = "begin"; + return funcDefnAst; } - // check if the next line contains a function definition - var funcDefnAst = foundAst as FunctionDefinitionAst; - if (funcDefnAst.Body.Extent.StartLineNumber == lineNumber - 1) + if (funcDefnAst.Body.Extent.EndLineNumber == lineNumber + 1) { - helpLocation = "begin"; + helpLocation = "end"; + return funcDefnAst; } - helpLocation = "end"; - return funcDefnAst; + helpLocation = null; + return null; } #endregion From a06fafe54c4edf64c0c27b0f6666560712b49f3e Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Mon, 22 May 2017 13:24:01 -0700 Subject: [PATCH 3/6] Fix help completion at the beginning of a function --- .../Server/LanguageServer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs index 572c924a4..fb6504c68 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs @@ -1109,6 +1109,12 @@ protected async Task HandleCommentHelpRequest( .Split('\n') .Select(x => x.Trim('\r')) .ToArray(); + + if (helpLocation.Equals("begin", StringComparison.OrdinalIgnoreCase)) + { + // we need to trim the leading `{` that the correction sends. + result.Content = result.Content?.Skip(1).ToArray(); + } } await requestContext.SendResult(result); From e0c5dd70039079b67b361dc9591acdc33a567ed6 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Mon, 22 May 2017 14:05:55 -0700 Subject: [PATCH 4/6] Fix help completion in nested functions --- .../Language/LanguageService.cs | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index 9102fcdf9..540c81477 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -545,6 +545,14 @@ public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( int lineNumber, out string helpLocation) { + // check if the next line contains a function definition + var funcDefnAst = GetFunctionDefinitionAtLine(scriptFile, lineNumber + 1); + if (funcDefnAst != null) + { + helpLocation = "before"; + return funcDefnAst; + } + var foundAsts = scriptFile.ScriptAst.FindAll( ast => { @@ -560,14 +568,9 @@ public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( }, true); - // check if the next line contains a function definition - if (foundAsts == null || !foundAsts.Any()) + if (foundAsts != null && foundAsts.Any()) { - helpLocation = "before"; - return GetFunctionDefinitionAtLine(scriptFile, lineNumber + 1); - } - - var funcDefnAst = foundAsts.Cast().Aggregate((x, y) => + funcDefnAst = foundAsts.Cast().Aggregate((x, y) => { // of all the function definitions found, return the innermost function definition that contains // `lineNumber` @@ -579,20 +582,21 @@ public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( return y; }); - // TODO fix help completion in nested functions - // TODO use tokens to check for non empty character instead of just checking for line offset - // check if the line number is the first line in the function body - // check if the line number is the last line in the function body - if (funcDefnAst.Body.Extent.StartLineNumber == lineNumber - 1) - { - helpLocation = "begin"; - return funcDefnAst; - } + // TODO fix help completion in nested functions + // TODO use tokens to check for non empty character instead of just checking for line offset + // check if the line number is the first line in the function body + // check if the line number is the last line in the function body + if (funcDefnAst.Body.Extent.StartLineNumber == lineNumber - 1) + { + helpLocation = "begin"; + return funcDefnAst; + } - if (funcDefnAst.Body.Extent.EndLineNumber == lineNumber + 1) - { - helpLocation = "end"; - return funcDefnAst; + if (funcDefnAst.Body.Extent.EndLineNumber == lineNumber + 1) + { + helpLocation = "end"; + return funcDefnAst; + } } helpLocation = null; From d26790e912964328aae5a864b4e8f534435ae468 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Mon, 22 May 2017 14:06:07 -0700 Subject: [PATCH 5/6] Fix help completion within function defition body --- .../Server/LanguageServer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs index 728f31cb7..4d4a506fd 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs @@ -1082,12 +1082,12 @@ protected async Task HandleCommentHelpRequest( { var scriptFile = EditorSession.Workspace.GetFile(requestParams.DocumentUri); var expectedFunctionLine = requestParams.TriggerPosition.Line + 2; + string helpLocation; var functionDefinitionAst = EditorSession.LanguageService.GetFunctionDefinitionForHelpComment( scriptFile, requestParams.TriggerPosition.Line + 1, out helpLocation); - var result = new CommentHelpRequestResult(); if (functionDefinitionAst != null) { @@ -1100,7 +1100,6 @@ protected async Task HandleCommentHelpRequest( requestParams.BlockComment, true, helpLocation)); - result.Content = analysisResults? .FirstOrDefault()? .Correction? @@ -1109,10 +1108,11 @@ protected async Task HandleCommentHelpRequest( .Split('\n') .Select(x => x.Trim('\r')) .ToArray(); - - if (helpLocation.Equals("begin", StringComparison.OrdinalIgnoreCase)) + if (helpLocation != null && + !helpLocation.Equals("before", StringComparison.OrdinalIgnoreCase)) { - // we need to trim the leading `{` that the correction sends. + // we need to trim the leading `{` and newline when helpLocation=="begin" + // we also need to trim the leading newline when helpLocation=="end" result.Content = result.Content?.Skip(1).ToArray(); } } From e9f40166c94cffe813993df3c0282c899f74b84c Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 24 May 2017 13:22:15 -0700 Subject: [PATCH 6/6] Add comment documentation --- .../Server/LanguageServer.cs | 1 - .../Language/LanguageService.cs | 17 ++++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs index 4d4a506fd..0c62328fb 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs @@ -1091,7 +1091,6 @@ protected async Task HandleCommentHelpRequest( var result = new CommentHelpRequestResult(); if (functionDefinitionAst != null) { - // todo create a semantic marker api that take only string var analysisResults = await EditorSession.AnalysisService.GetSemanticMarkersAsync( functionDefinitionAst.Extent.Text, AnalysisService.GetCommentHelpRuleSettings( diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index 540c81477..57b0eef4c 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -539,7 +539,13 @@ public FunctionDefinitionAst GetFunctionDefinitionAtLine( return functionDefinitionAst as FunctionDefinitionAst; } - // todo add xml doc + /// + /// Finds a function definition that follows or contains the given line number. + /// + /// Open script file. + /// The 1 based line on which to look for function definition. + /// + /// If found, returns the function definition, otherwise, returns null. public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( ScriptFile scriptFile, int lineNumber, @@ -553,10 +559,10 @@ public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( return funcDefnAst; } + // find all the script definitions that contain the line `lineNumber` var foundAsts = scriptFile.ScriptAst.FindAll( ast => { - // find all the script definitions that contain the line `lineNumber` var fdAst = ast as FunctionDefinitionAst; if (fdAst == null) { @@ -570,10 +576,10 @@ public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( if (foundAsts != null && foundAsts.Any()) { + // of all the function definitions found, return the innermost function + // definition that contains `lineNumber` funcDefnAst = foundAsts.Cast().Aggregate((x, y) => { - // of all the function definitions found, return the innermost function definition that contains - // `lineNumber` if (x.Extent.StartOffset >= y.Extent.StartOffset && x.Extent.EndOffset <= x.Extent.EndOffset) { return x; @@ -582,10 +588,7 @@ public FunctionDefinitionAst GetFunctionDefinitionForHelpComment( return y; }); - // TODO fix help completion in nested functions // TODO use tokens to check for non empty character instead of just checking for line offset - // check if the line number is the first line in the function body - // check if the line number is the last line in the function body if (funcDefnAst.Body.Extent.StartLineNumber == lineNumber - 1) { helpLocation = "begin";