Skip to content

Enable formatOnType feature #370

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 18 commits into from
Feb 15, 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,57 @@
//
// 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
{
/// <summary>
/// Class to encapsulate the request type.
/// </summary>
class ScriptRegionRequest
{
public static readonly
RequestType<ScriptRegionRequestParams, ScriptRegionRequestResult> Type =
RequestType<ScriptRegionRequestParams, ScriptRegionRequestResult>.Create("powerShell/getScriptRegion");
}

/// <summary>
/// Class to encapsulate the request parameters.
/// </summary>
class ScriptRegionRequestParams
{
/// <summary>
/// Path of the file for which the formatting region is requested.
/// </summary>
public string FileUri;

/// <summary>
/// Hint character.
/// </summary>
public string Character;

/// <summary>
/// 1-based line number of the character.
/// </summary>
public int Line;

/// <summary>
/// 1-based column number of the character.
/// </summary>
public int Column;
}

/// <summary>
/// Class to encapsulate the result of ScriptRegionRequest.
/// </summary>
class ScriptRegionRequestResult
{
/// <summary>
/// A region in the script that encapsulates the given character/position which is suitable
/// for formatting
/// </summary>
public ScriptRegion scriptRegion;
}
}
43 changes: 43 additions & 0 deletions src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ protected override void Initialize()
this.SetRequestHandler(SetPSSARulesRequest.Type, this.HandleSetPSSARulesRequest);

this.SetRequestHandler(ScriptFileMarkersRequest.Type, this.HandleScriptFileMarkersRequest);
this.SetRequestHandler(ScriptRegionRequest.Type, this.HandleGetFormatScriptRegionRequest);

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

Expand Down Expand Up @@ -235,6 +236,48 @@ await RunScriptDiagnostics(
await sendresult;
}

private async Task HandleGetFormatScriptRegionRequest(
ScriptRegionRequestParams requestParams,
RequestContext<ScriptRegionRequestResult> requestContext)
{
var scriptFile = this.editorSession.Workspace.GetFile(requestParams.FileUri);
var lineNumber = requestParams.Line;
var columnNumber = requestParams.Column;
ScriptRegion scriptRegion = null;

switch (requestParams.Character)
{
case "\n":
// find the smallest statement ast that occupies
// the element before \n or \r\n and return the extent.
--lineNumber; // vscode sends the next line when pressed enter
var line = scriptFile.GetLine(lineNumber);
if (!String.IsNullOrEmpty(line))
{
scriptRegion = this.editorSession.LanguageService.FindSmallestStatementAstRegion(
scriptFile,
lineNumber,
line.Length);
}
break;

case "}":
scriptRegion = this.editorSession.LanguageService.FindSmallestStatementAstRegion(
scriptFile,
lineNumber,
columnNumber);
break;

default:
break;
}

await requestContext.SendResult(new ScriptRegionRequestResult
{
scriptRegion = scriptRegion
});
}

private async Task HandleScriptFileMarkersRequest(
ScriptFileMarkerRequestParams requestParams,
RequestContext<ScriptFileMarkerRequestResultParams> requestContext)
Expand Down
33 changes: 33 additions & 0 deletions src/PowerShellEditorServices/Language/LanguageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,28 @@ await CommandHelpers.GetCommandInfo(
}
}

/// <summary>
/// Gets the smallest statment ast that contains the given script position as
/// indicated by lineNumber and columnNumber parameters.
/// </summary>
/// <param name="scriptFile">Open script file.</param>
/// <param name="lineNumber">1-based line number of the position.</param>
/// <param name="columnNumber">1-based column number of the position.</param>
/// <returns></returns>
public ScriptRegion FindSmallestStatementAstRegion(
ScriptFile scriptFile,
int lineNumber,
int columnNumber)
{
var ast = FindSmallestStatementAst(scriptFile, lineNumber, columnNumber);
if (ast == null)
{
return null;
}

return ScriptRegion.Create(ast.Extent);
}

#endregion

#region Private Fields
Expand Down Expand Up @@ -569,6 +591,17 @@ private SymbolReference FindDeclarationForBuiltinCommand(
return foundDefinition;
}

private Ast FindSmallestStatementAst(ScriptFile scriptFile, int lineNumber, int columnNumber)
{
var asts = scriptFile.ScriptAst.FindAll(ast =>
{
return ast is StatementAst && ast.Extent.Contains(lineNumber, columnNumber);
}, true);

// Find ast with the smallest extent
return asts.MinElement((astX, astY) => astX.Extent.ExtentWidthComparer(astY.Extent));
}

#endregion
}
}
120 changes: 120 additions & 0 deletions src/PowerShellEditorServices/Utility/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
//

using System;
using System.Linq;
using System.Collections.Generic;
using System.Management.Automation.Language;

namespace Microsoft.PowerShell.EditorServices.Utility
{
Expand All @@ -30,5 +33,122 @@ public static string SafeToString(this object obj)

return str;
}

/// <summary>
/// Get the maximum of the elements from the given enumerable.
/// </summary>
/// <typeparam name="T">Type of object for which the enumerable is defined.</typeparam>
/// <param name="elements">An enumerable object of type T</param>
/// <param name="comparer">A comparer for ordering elements of type T. The comparer should handle null values.</param>
/// <returns>An object of type T. If the enumerable is empty or has all null elements, then the method returns null.</returns>
public static T MaxElement<T>(this IEnumerable<T> elements, Func<T,T,int> comparer) where T:class
{
if (elements == null)
{
throw new ArgumentNullException(nameof(elements));
}

if (comparer == null)
{
throw new ArgumentNullException(nameof(comparer));
}

if (!elements.Any())
{
return null;
}

var maxElement = elements.First();
foreach(var element in elements.Skip(1))
{
if (element != null && comparer(element, maxElement) > 0)
{
maxElement = element;
}
}

return maxElement;
}

/// <summary>
/// Get the minimum of the elements from the given enumerable.
/// </summary>
/// <typeparam name="T">Type of object for which the enumerable is defined.</typeparam>
/// <param name="elements">An enumerable object of type T</param>
/// <param name="comparer">A comparer for ordering elements of type T. The comparer should handle null values.</param>
/// <returns>An object of type T. If the enumerable is empty or has all null elements, then the method returns null.</returns>
public static T MinElement<T>(this IEnumerable<T> elements, Func<T, T, int> comparer) where T : class
{
return MaxElement<T>(elements, (elementX, elementY) => -1 * comparer(elementX, elementY));
}

/// <summary>
/// Compare extents with respect to their widths.
///
/// Width of an extent is defined as the difference between its EndOffset and StartOffest properties.
/// </summary>
/// <param name="extentX">Extent of type IScriptExtent.</param>
/// <param name="extentY">Extent of type IScriptExtent.</param>
/// <returns>0 if extentX and extentY are equal in width. 1 if width of extent X is greater than that of extent Y. Otherwise, -1.</returns>
public static int ExtentWidthComparer(this IScriptExtent extentX, IScriptExtent extentY)
{

if (extentX == null && extentY == null)
{
return 0;
}

if (extentX != null && extentY == null)
{
return 1;
}

if (extentX == null)
{
return -1;
}

var extentWidthX = extentX.EndOffset - extentX.StartOffset;
var extentWidthY = extentY.EndOffset - extentY.StartOffset;
if (extentWidthX > extentWidthY)
{
return 1;
}
else if (extentWidthX < extentWidthY)
{
return -1;
}
else
{
return 0;
}
}

/// <summary>
/// Check if the given coordinates are wholly contained in the instance's extent.
/// </summary>
/// <param name="scriptExtent">Extent of type IScriptExtent.</param>
/// <param name="line">1-based line number.</param>
/// <param name="column">1-based column number</param>
/// <returns>True if the coordinates are wholly contained in the instance's extent, otherwise, false.</returns>
public static bool Contains(this IScriptExtent scriptExtent, int line, int column)
{
if (scriptExtent.StartLineNumber > line || scriptExtent.EndLineNumber < line)
{
return false;
}

if (scriptExtent.StartLineNumber == line)
{
return scriptExtent.StartColumnNumber <= column;
}

if (scriptExtent.EndLineNumber == line)
{
return scriptExtent.EndColumnNumber >= column;
}

return true;
}
}
}