Skip to content

Commit e0dfc4d

Browse files
committed
(GH-793) Add a folding provider and tests
This commit adds the the code to return folding ranges when the server receives a FoldingRangeRequest. This commit; * Uses a similar method to that implemented in the VS Code extension [1] * 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 * Translates the tests in VS Code [1] into equivalent C# tests [1] PowerShell/vscode-powershell#1355
1 parent a307a81 commit e0dfc4d

File tree

5 files changed

+653
-0
lines changed

5 files changed

+653
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
11+
public class FoldingRangeRequest
12+
{
13+
public static readonly
14+
RequestType<FoldingRangeParams, FoldingRange[], object, object> Type =
15+
RequestType<FoldingRangeParams, FoldingRange[], object, object>.Create("textDocument/foldingRange");
16+
}
17+
18+
public class FoldingRangeParams
19+
{
20+
/// <summary>
21+
/// The document to request folding ranges for.
22+
/// </summary>
23+
public TextDocumentIdentifier TextDocument { get; set; }
24+
}
25+
26+
public class FoldingRange
27+
{
28+
/// <summary>
29+
/// The zero-based line number from where the folded range starts.
30+
/// </summary>
31+
public int StartLine { get; set; }
32+
33+
/// <summary>
34+
/// The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line.
35+
/// </summary>
36+
public int StartCharacter { get; set; }
37+
38+
/// <summary>
39+
/// The zero-based line number where the folded range ends.
40+
/// </summary>
41+
public int EndLine { get; set; }
42+
43+
/// <summary>
44+
/// The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line.
45+
/// </summary>
46+
public int EndCharacter { get; set; }
47+
48+
/// <summary>
49+
/// Describes the kind of the folding range such as `comment' or 'region'. The kind
50+
/// is used to categorize folding ranges and used by commands like 'Fold all comments'. See
51+
/// [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds.
52+
/// <summary>
53+
public string Kind { get; set; }
54+
}
55+
56+
public sealed class FoldingRangeKind
57+
{
58+
private FoldingRangeKind() {}
59+
60+
/// <summary>
61+
/// Folding range for a comment
62+
/// <summary>
63+
public static readonly string Comment = "comment";
64+
/// <summary>
65+
/// Folding range for a imports or includes
66+
/// <summary>
67+
public static readonly string Imports = "imports";
68+
/// <summary>
69+
/// Folding range for a region (e.g. `#region`)
70+
/// <summary>
71+
public static readonly string Region = "region";
72+
}
73+
}

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

+28
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ public void Start()
131131
this.messageHandlers.SetRequestHandler(
132132
DocumentRangeFormattingRequest.Type,
133133
this.HandleDocumentRangeFormattingRequest);
134+
this.messageHandlers.SetRequestHandler(FoldingRangeRequest.Type, this.HandleFoldingRangeRequest);
134135

135136
this.messageHandlers.SetRequestHandler(ShowOnlineHelpRequest.Type, this.HandleShowOnlineHelpRequest);
136137
this.messageHandlers.SetRequestHandler(ShowHelpRequest.Type, this.HandleShowHelpRequest);
@@ -1251,6 +1252,14 @@ await requestContext.SendResult(new TextEdit[1]
12511252
});
12521253
}
12531254

1255+
protected async Task HandleFoldingRangeRequest(
1256+
FoldingRangeParams foldingParams,
1257+
RequestContext<FoldingRange[]> requestContext)
1258+
{
1259+
var result = Fold(foldingParams.TextDocument.Uri);
1260+
await requestContext.SendResult(result);
1261+
}
1262+
12541263
protected Task HandleEvaluateRequest(
12551264
DebugAdapterMessages.EvaluateRequestArguments evaluateParams,
12561265
RequestContext<DebugAdapterMessages.EvaluateResponseBody> requestContext)
@@ -1289,6 +1298,25 @@ protected Task HandleEvaluateRequest(
12891298

12901299
#region Event Handlers
12911300

1301+
private FoldingRange[] Fold(
1302+
string documentUri)
1303+
{
1304+
// TODO Surely there's a better way to do type adapters?
1305+
List<FoldingRange> result = new List<FoldingRange>();
1306+
foreach (FoldingReference fold in TokenOperations.FoldableRegions(
1307+
editorSession.Workspace.GetFile(documentUri).ScriptTokens))
1308+
{
1309+
FoldingRange item = new FoldingRange();
1310+
item.EndCharacter = fold.EndCharacter;
1311+
item.EndLine = fold.EndLine;
1312+
item.Kind = fold.Kind; // TODO: Need to make sure the string is expected
1313+
item.StartCharacter = fold.StartCharacter;
1314+
item.StartLine = fold.StartLine;
1315+
result.Add(item);
1316+
}
1317+
return result.ToArray();
1318+
}
1319+
12921320
private async Task<Tuple<string, Range>> Format(
12931321
string documentUri,
12941322
FormattingOptions options,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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; }
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; }
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+
/// Builds a folding reference for start and end line and character position
42+
/// </summary>
43+
public FoldingReference(
44+
int startLine,
45+
int startCharacter,
46+
int endLine,
47+
int endCharacter,
48+
string kind)
49+
{
50+
this.EndCharacter = endCharacter;
51+
this.EndLine = endLine;
52+
this.Kind = kind;
53+
this.StartCharacter = startCharacter;
54+
this.StartLine = startLine;
55+
}
56+
57+
/// <summary>
58+
/// Builds a folding reference for start and end line
59+
/// </summary>
60+
public FoldingReference(
61+
int startLine,
62+
int endLine,
63+
string kind)
64+
{
65+
this.EndCharacter = 0;
66+
this.EndLine = endLine;
67+
this.Kind = kind;
68+
this.StartCharacter = 0;
69+
this.StartLine = startLine;
70+
}
71+
72+
/// <summary>
73+
/// A custom comparable method which can properly sort FoldingReference objects
74+
/// </summary>
75+
public int CompareTo(FoldingReference that) {
76+
// Initially look at the start line
77+
if (this.StartLine < that.StartLine) { return -1; }
78+
if (this.StartLine > that.StartLine) { return 1; }
79+
// They have the same start line so now consider the end line.
80+
// The biggest line range is sorted first
81+
if (this.EndLine > that.EndLine) { return -1; }
82+
if (this.EndLine < that.EndLine) { return 1; }
83+
// They have the same lines, but what about character offsets
84+
if (this.StartCharacter < that.StartCharacter) { return -1; }
85+
if (this.StartCharacter > that.StartCharacter) { return 1; }
86+
if (this.EndCharacter < that.EndCharacter) { return -1; }
87+
if (this.EndCharacter > that.EndCharacter) { return 1; }
88+
// They're the same range, but what about kind
89+
// Check for nulls
90+
if ((this.Kind == null) & (that.Kind == null)) { return 0; }
91+
return this.Kind.CompareTo(that.Kind);
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)