Skip to content

Commit fbe8871

Browse files
glennsartirjmholt
authored andcommitted
Add syntax folding (#777)
* Add simple folding range provider capability * Uses a similar method to that implemented in the VS Code extension * Uses the PowerShell tokeniser instead of the AST. This is due to the AST ignoring comment sections. Without the comment parsing folding for #region etc. will not work * uses the the client configuration settings to modify the behaviour of the server. In particular the Enable and ShowLastLine setting. * The enable setting will change the provider to return null when disabled * The ShowLastLine setting emulates the behaviour in PowerShell/vscode-powershell#1557
1 parent c68ca39 commit fbe8871

File tree

7 files changed

+703
-1
lines changed

7 files changed

+703
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
7+
8+
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
9+
{
10+
public class FoldingRangeRequest
11+
{
12+
/// <summary>
13+
/// A request to provide folding ranges in a document. The request's
14+
/// parameter is of type [FoldingRangeParams](#FoldingRangeParams), the
15+
/// response is of type [FoldingRangeList](#FoldingRangeList) or a Thenable
16+
/// that resolves to such.
17+
/// Ref: https://github.com/Microsoft/vscode-languageserver-node/blob/5350bc2ffe8afb17357c1a66fbdd3845fa05adfd/protocol/src/protocol.foldingRange.ts#L112-L120
18+
/// </summary>
19+
public static readonly
20+
RequestType<FoldingRangeParams, FoldingRange[], object, object> Type =
21+
RequestType<FoldingRangeParams, FoldingRange[], object, object>.Create("textDocument/foldingRange");
22+
}
23+
24+
/// <summary>
25+
/// Parameters for a [FoldingRangeRequest](#FoldingRangeRequest).
26+
/// Ref: https://github.com/Microsoft/vscode-languageserver-node/blob/5350bc2ffe8afb17357c1a66fbdd3845fa05adfd/protocol/src/protocol.foldingRange.ts#L102-L110
27+
/// </summary>
28+
public class FoldingRangeParams
29+
{
30+
/// <summary>
31+
/// The text document
32+
/// </summary>
33+
public TextDocumentIdentifier TextDocument { get; set; }
34+
}
35+
36+
/// <summary>
37+
/// Represents a folding range.
38+
/// Ref: https://github.com/Microsoft/vscode-languageserver-node/blob/5350bc2ffe8afb17357c1a66fbdd3845fa05adfd/protocol/src/protocol.foldingRange.ts#L69-L100
39+
/// </summary>
40+
public class FoldingRange
41+
{
42+
/// <summary>
43+
/// The zero-based line number from where the folded range starts.
44+
/// </summary>
45+
public int StartLine { get; set; }
46+
47+
/// <summary>
48+
/// The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line.
49+
/// </summary>
50+
public int StartCharacter { get; set; }
51+
52+
/// <summary>
53+
/// The zero-based line number where the folded range ends.
54+
/// </summary>
55+
public int EndLine { get; set; }
56+
57+
/// <summary>
58+
/// The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line.
59+
/// </summary>
60+
public int EndCharacter { get; set; }
61+
62+
/// <summary>
63+
/// Describes the kind of the folding range such as `comment' or 'region'. The kind
64+
/// is used to categorize folding ranges and used by commands like 'Fold all comments'. See
65+
/// [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds.
66+
/// </summary>
67+
public string Kind { get; set; }
68+
}
69+
}

src/PowerShellEditorServices.Protocol/LanguageServer/ServerCapabilities.cs

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public class ServerCapabilities
4242
public ExecuteCommandOptions ExecuteCommandProvider { get; set; }
4343

4444
public object Experimental { get; set; }
45+
46+
public bool FoldingRangeProvider { get; set; } = false;
4547
}
4648

4749
/// <summary>

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

+31-1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ public void Start()
134134
this.messageHandlers.SetRequestHandler(
135135
DocumentRangeFormattingRequest.Type,
136136
this.HandleDocumentRangeFormattingRequest);
137+
this.messageHandlers.SetRequestHandler(FoldingRangeRequest.Type, this.HandleFoldingRangeRequestAsync);
137138

138139
this.messageHandlers.SetRequestHandler(ShowOnlineHelpRequest.Type, this.HandleShowOnlineHelpRequest);
139140
this.messageHandlers.SetRequestHandler(ShowHelpRequest.Type, this.HandleShowHelpRequest);
@@ -243,7 +244,8 @@ await requestContext.SendResult(
243244
},
244245
DocumentFormattingProvider = false,
245246
DocumentRangeFormattingProvider = false,
246-
RenameProvider = false
247+
RenameProvider = false,
248+
FoldingRangeProvider = true
247249
}
248250
});
249251
}
@@ -1329,6 +1331,13 @@ await requestContext.SendResult(new TextEdit[1]
13291331
});
13301332
}
13311333

1334+
protected async Task HandleFoldingRangeRequestAsync(
1335+
FoldingRangeParams foldingParams,
1336+
RequestContext<FoldingRange[]> requestContext)
1337+
{
1338+
await requestContext.SendResult(Fold(foldingParams.TextDocument.Uri));
1339+
}
1340+
13321341
protected Task HandleEvaluateRequest(
13331342
DebugAdapterMessages.EvaluateRequestArguments evaluateParams,
13341343
RequestContext<DebugAdapterMessages.EvaluateResponseBody> requestContext)
@@ -1367,6 +1376,27 @@ protected Task HandleEvaluateRequest(
13671376

13681377
#region Event Handlers
13691378

1379+
private FoldingRange[] Fold(
1380+
string documentUri)
1381+
{
1382+
// TODO Should be using dynamic registrations
1383+
if (!this.currentSettings.CodeFolding.Enable) { return null; }
1384+
var result = new List<FoldingRange>();
1385+
foreach (FoldingReference fold in TokenOperations.FoldableRegions(
1386+
editorSession.Workspace.GetFile(documentUri).ScriptTokens,
1387+
this.currentSettings.CodeFolding.ShowLastLine))
1388+
{
1389+
result.Add(new FoldingRange {
1390+
EndCharacter = fold.EndCharacter,
1391+
EndLine = fold.EndLine,
1392+
Kind = fold.Kind,
1393+
StartCharacter = fold.StartCharacter,
1394+
StartLine = fold.StartLine
1395+
});
1396+
}
1397+
return result.ToArray();
1398+
}
1399+
13701400
private async Task<Tuple<string, Range>> Format(
13711401
string documentUri,
13721402
FormattingOptions options,

src/PowerShellEditorServices.Protocol/Server/LanguageServerSettings.cs

+39
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@ public class LanguageServerSettings
2020

2121
public CodeFormattingSettings CodeFormatting { get; set; }
2222

23+
public CodeFoldingSettings CodeFolding { get; set; }
24+
2325
public LanguageServerSettings()
2426
{
2527
this.ScriptAnalysis = new ScriptAnalysisSettings();
2628
this.CodeFormatting = new CodeFormattingSettings();
29+
this.CodeFolding = new CodeFoldingSettings();
2730
}
2831

2932
public void Update(
@@ -39,6 +42,7 @@ public void Update(
3942
workspaceRootPath,
4043
logger);
4144
this.CodeFormatting = new CodeFormattingSettings(settings.CodeFormatting);
45+
this.CodeFolding.Update(settings.CodeFolding, logger);
4246
}
4347
}
4448
}
@@ -261,6 +265,41 @@ private Hashtable GetCustomPSSASettingsHashtable(int tabSize, bool insertSpaces)
261265
}
262266
}
263267

268+
/// <summary>
269+
/// Code folding settings
270+
/// </summary>
271+
public class CodeFoldingSettings
272+
{
273+
/// <summary>
274+
/// Whether the folding is enabled. Default is true as per VSCode
275+
/// </summary>
276+
public bool Enable { get; set; } = true;
277+
278+
/// <summary>
279+
/// Whether to show or hide the last line of a folding region. Default is true as per VSCode
280+
/// </summary>
281+
public bool ShowLastLine { get; set; } = true;
282+
283+
/// <summary>
284+
/// Update these settings from another settings object
285+
/// </summary>
286+
public void Update(
287+
CodeFoldingSettings settings,
288+
ILogger logger)
289+
{
290+
if (settings != null) {
291+
if (this.Enable != settings.Enable) {
292+
this.Enable = settings.Enable;
293+
logger.Write(LogLevel.Verbose, string.Format("Using Code Folding Enabled - {0}", this.Enable));
294+
}
295+
if (this.ShowLastLine != settings.ShowLastLine) {
296+
this.ShowLastLine = settings.ShowLastLine;
297+
logger.Write(LogLevel.Verbose, string.Format("Using Code Folding ShowLastLine - {0}", this.ShowLastLine));
298+
}
299+
}
300+
}
301+
}
302+
264303
public class LanguageServerSettingsWrapper
265304
{
266305
// NOTE: This property is capitalized as 'Powershell' because the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System;
7+
8+
namespace Microsoft.PowerShell.EditorServices
9+
{
10+
/// <summary>
11+
/// A class that holds the information for a foldable region of text in a document
12+
/// </summary>
13+
public class FoldingReference: IComparable<FoldingReference>
14+
{
15+
/// <summary>
16+
/// The zero-based line number from where the folded range starts.
17+
/// </summary>
18+
public int StartLine { get; set; }
19+
20+
/// <summary>
21+
/// The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line.
22+
/// </summary>
23+
public int StartCharacter { get; set; } = 0;
24+
25+
/// <summary>
26+
/// The zero-based line number where the folded range ends.
27+
/// </summary>
28+
public int EndLine { get; set; }
29+
30+
/// <summary>
31+
/// The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line.
32+
/// </summary>
33+
public int EndCharacter { get; set; } = 0;
34+
35+
/// <summary>
36+
/// Describes the kind of the folding range such as `comment' or 'region'.
37+
/// </summary>
38+
public string Kind { get; set; }
39+
40+
/// <summary>
41+
/// A custom comparable method which can properly sort FoldingReference objects
42+
/// </summary>
43+
public int CompareTo(FoldingReference that) {
44+
// Initially look at the start line
45+
if (this.StartLine < that.StartLine) { return -1; }
46+
if (this.StartLine > that.StartLine) { return 1; }
47+
48+
// They have the same start line so now consider the end line.
49+
// The biggest line range is sorted first
50+
if (this.EndLine > that.EndLine) { return -1; }
51+
if (this.EndLine < that.EndLine) { return 1; }
52+
53+
// They have the same lines, but what about character offsets
54+
if (this.StartCharacter < that.StartCharacter) { return -1; }
55+
if (this.StartCharacter > that.StartCharacter) { return 1; }
56+
if (this.EndCharacter < that.EndCharacter) { return -1; }
57+
if (this.EndCharacter > that.EndCharacter) { return 1; }
58+
59+
// They're the same range, but what about kind
60+
return string.Compare(this.Kind, that.Kind);
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)