Skip to content
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

LSP Folding Ranges #79

Open
wants to merge 9 commits into
base: anytext
Choose a base branch
from
47 changes: 47 additions & 0 deletions AnyText/AnyText.Core/FoldingRange.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NMF.AnyText
{
/// <summary>
/// Denotes a part in a parsed document that can be folded away (hidden).
/// Analogous to the LspTypes FoldingRange interface.
/// </summary>
public class FoldingRange
{
/// <summary>
/// The zero-based start line of the range to fold. The folded area starts
/// after the line's last character. To be valid, the end must be zero or
/// larger and smaller than the number of lines in the document.
/// </summary>
public uint StartLine { get; set; }

/// <summary>
/// The zero-based character offset from where the folded range starts.
/// If not defined, defaults to the length of the start line.
/// </summary>
public uint StartCharacter { get; set; }

/// <summary>
/// The zero-based end line of the range to fold. The folded area ends with
/// the line's last character. To be valid, the end must be zero or larger
/// and smaller than the number of lines in the document.
/// </summary>
public uint EndLine { get; set; }

/// <summary>
/// The zero-based character offset before the folded range ends.
/// If not defined, defaults to the length of the end line.
/// </summary>
public uint EndCharacter { get; set; }

/// <summary>
/// Describes the kind of the folding range.
/// Supports values "comment", "imports" and "region".
/// </summary>
public string Kind { get; set; }
}
}
7 changes: 7 additions & 0 deletions AnyText/AnyText.Core/Model/ParanthesesRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ protected override RuleApplication CreateRuleApplication(ParsePosition currentPo
return new ParanthesesRuleApplication(this, currentPosition, inner, length, examined);
}

/// <inheritdoc />
public override bool HasFoldingKind(out string kind)
{
kind = null;
return true;
}

private sealed class ParanthesesRuleApplication : MultiRuleApplication
{
public ParanthesesRuleApplication(Rule rule, ParsePosition currentPosition, List<RuleApplication> inner, ParsePositionDelta endsAt, ParsePositionDelta examinedTo) : base(rule, currentPosition, inner, endsAt, examinedTo)
Expand Down
33 changes: 33 additions & 0 deletions AnyText/AnyText.Core/Parser.FoldingRanges.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using NMF.AnyText.Model;
using NMF.AnyText.Rules;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.XPath;

namespace NMF.AnyText
{
public partial class Parser
{
/// <summary>
/// Parses folding ranges starting from the root rule application
/// </summary>
/// <returns>An IEnumerable of <see cref="FoldingRange"/> objects, each containing details on a folding range in the document.</returns>
public IEnumerable<FoldingRange> GetFoldingRangesFromRoot()
{
RuleApplication rootApplication = Context.RootRuleApplication;

if (rootApplication.IsPositive)
{
var result = new List<FoldingRange>();
rootApplication.AddFoldingRanges(result);
return result;
}

return Enumerable.Empty<FoldingRange>();
}
}
}
35 changes: 34 additions & 1 deletion AnyText/AnyText.Core/Rules/MultiRuleApplication.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using NMF.AnyText.PrettyPrinting;
using NMF.AnyText.Model;
using NMF.AnyText.PrettyPrinting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
Expand Down Expand Up @@ -161,6 +163,37 @@ public override object GetValue(ParseContext context)
}
}

/// <inheritdoc />
public override void AddFoldingRanges(ICollection<FoldingRange> result)
{
base.AddFoldingRanges(result);

if (Rule.HasFoldingKind(out var kind))
{
AddFoldingRange(kind, result);
}

foreach (var innerRuleApplication in Inner)
{
innerRuleApplication.AddFoldingRanges(result);
}
}

private void AddFoldingRange(string? kind, ICollection<FoldingRange> result)
{
if (Inner.Count < 2) return;

var foldingRange = new FoldingRange()
{
StartLine = (uint)Inner.First().CurrentPosition.Line,
StartCharacter = (uint)Inner.First().CurrentPosition.Col,
EndLine = (uint)Inner.Last().CurrentPosition.Line,
EndCharacter = (uint)(Inner.First().CurrentPosition.Col + Inner.Last().Length.Col),
Kind = kind
};

result.Add(foldingRange);
}

/// <inheritdoc />
public override void IterateLiterals(Action<LiteralRuleApplication> action)
Expand Down
21 changes: 21 additions & 0 deletions AnyText/AnyText.Core/Rules/Rule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,27 @@ protected internal virtual void OnDeactivate(RuleApplication application, ParseC
/// </summary>
public virtual bool IsComment => false;

/// <summary>
/// True, if the application of this rule can be folded away (hidden)
/// </summary>
public virtual bool IsFoldable() => false;

/// <summary>
/// True, if the rule is used to define imports
/// </summary>
public virtual bool IsImports() => false;

/// <summary>
/// Returns the folding kind for a rule if one is defined for the rule
/// </summary>
/// <param name="kind">The folding kind of the rule</param>
/// <returns>True, if a folding kind is defined for the rule</returns>
public virtual bool HasFoldingKind(out string kind)
{
kind = null;
return false;
}

/// <summary>
/// True, if the rule permits trailing whitespaces, otherwise false
/// </summary>
Expand Down
54 changes: 54 additions & 0 deletions AnyText/AnyText.Core/Rules/RuleApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,60 @@ protected RuleApplication(Rule rule, ParsePosition currentPosition, ParsePositio
/// <returns>the parsed newPosition</returns>
public abstract object GetValue(ParseContext context);

/// <summary>
/// Gets the folding ranges present in the rule application
/// </summary>
/// <param name="result">The IEnumerable to hold the folding ranges</param>
public virtual void AddFoldingRanges(ICollection<FoldingRange> result)
{
if (Comments != null)
{
AddCommentFoldingRanges(result);
}
}

private void AddCommentFoldingRanges(ICollection<FoldingRange> result)
{
for (var i = 0; i < Comments.Count; i++)
{
var commentRuleApplication = Comments[i];

uint endLine, endCol;
if (commentRuleApplication.Rule is MultilineCommentRule)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sehe ich das richtig, dass wir hier davon ausgehen, wenn es ein Multiline Comment ist, dann gibt es genau einen, sonst könnte es mehrere geben?

{
endLine = (uint)(commentRuleApplication.CurrentPosition.Line + commentRuleApplication.Length.Line);
endCol = (uint)(commentRuleApplication.CurrentPosition.Col + commentRuleApplication.Length.Col);
}
else
{
RuleApplication endCommentRuleApplication;
do
{
endCommentRuleApplication = Comments[i++];
}
while (endCommentRuleApplication.Rule is not MultilineCommentRule
&& endCommentRuleApplication.CurrentPosition.Col == commentRuleApplication.CurrentPosition.Col
&& i < Comments.Count);

if (commentRuleApplication == endCommentRuleApplication) continue;

endLine = (uint)endCommentRuleApplication.CurrentPosition.Line;
endCol = (uint)(endCommentRuleApplication.CurrentPosition.Col + endCommentRuleApplication.Length.Col);
}

var commentsFoldingRange = new FoldingRange()
{
StartLine = (uint)commentRuleApplication.CurrentPosition.Line,
StartCharacter = (uint)commentRuleApplication.CurrentPosition.Col,
EndLine = endLine,
EndCharacter = endCol,
Kind = "comment"
};

result.Add(commentsFoldingRange);
}
}

/// <summary>
/// The rule that was matched
/// </summary>
Expand Down
49 changes: 46 additions & 3 deletions AnyText/AnyText.Core/Rules/SequenceRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,24 @@ protected internal override bool IsEpsilonAllowed(List<Rule> trace)
return true;
}

/// <inheritdoc />
public override bool HasFoldingKind(out string kind)
{
if (IsRegion())
{
kind = "region";
return true;
}

if (IsFoldable())
{
kind = null;
return true;
}

return base.HasFoldingKind(out kind);
}

/// <summary>
/// The rules that should occur in sequence
/// </summary>
Expand Down Expand Up @@ -138,15 +156,40 @@ public override bool CanSynthesize(object semanticElement, ParseContext context)
return Array.TrueForAll(Rules, r => r.Rule.CanSynthesize(semanticElement, context));
}

protected virtual bool IsOpeningParanthesis(string literal)
public bool IsRegion()
{
if (Rules.First().Rule is LiteralRule startLiteralRule && Rules.Last().Rule is LiteralRule endLiteralRule)
{
return IsRegionStartLiteral(startLiteralRule.Literal) && IsMatchingEndLiteral(endLiteralRule.Literal, startLiteralRule.Literal);
}
return false;
}

/// <inheritdoc />
public override bool IsFoldable()
{
if (Rules.First().Rule is LiteralRule startLiteralRule && Rules.Last().Rule is LiteralRule endLiteralRule)
{
return IsRangeStartLiteral(startLiteralRule.Literal) && IsMatchingEndLiteral(endLiteralRule.Literal, startLiteralRule.Literal);
}
return false;
}

protected virtual bool IsRegionStartLiteral(string literal)
{
return literal == "#region";
}

protected virtual bool IsRangeStartLiteral(string literal)
{
return literal == "(" || literal == "[" || literal == "{";
}

protected virtual bool IsMatchingClosingParanthesis(string literal, string openingParanthesis)
protected virtual bool IsMatchingEndLiteral(string literal, string startLiteral)
{
switch (openingParanthesis)
switch (startLiteral)
{
case "#region": return literal == "#endregion";
case "(": return literal == ")";
case "[": return literal == "]";
case "{": return literal == "}";
Expand Down
7 changes: 7 additions & 0 deletions AnyText/AnyText.Core/Rules/SingleRuleApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ public override object GetValue(ParseContext context)
return Inner?.GetValue(context);
}

/// <inheritdoc />
public override void AddFoldingRanges(ICollection<FoldingRange> result)
{
base.AddFoldingRanges(result);
Inner.AddFoldingRanges(result);
}

/// <inheritdoc />
public override void IterateLiterals(Action<LiteralRuleApplication> action)
{
Expand Down
12 changes: 12 additions & 0 deletions AnyText/AnyText.Core/Rules/ZeroOrMoreRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ protected internal override bool IsEpsilonAllowed(List<Rule> trace)
return true;
}

/// <inheritdoc />
public override bool HasFoldingKind(out string kind)
{
if (InnerRule.IsImports())
{
kind = "imports";
return true;
}

return base.HasFoldingKind(out kind);
}

/// <summary>
/// Gets or sets the inner rule
/// </summary>
Expand Down
22 changes: 22 additions & 0 deletions AnyText/AnyText.Lsp/ILspServer.FoldingRanges.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using LspTypes;
using Newtonsoft.Json.Linq;
using StreamJsonRpc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NMF.AnyText
{
public partial interface ILspServer
{
/// <summary>
/// Handles the <c>textDocument/foldingRange/full</c> request from the client. This is used to retrieve all folding ranges for a document.
/// </summary>
/// <param name="arg">The JSON token containing the parameters of the request. (FoldingRangeParams)</param>
/// <returns>An array of <see cref="FoldingRange" /> objects, each containing details on a folding range in the document.</returns>
[JsonRpcMethod(Methods.TextDocumentFoldingRangeName)]
LspTypes.FoldingRange[] QueryFoldingRanges(JToken arg);
}
}
1 change: 1 addition & 0 deletions AnyText/AnyText.Lsp/ILspServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Newtonsoft.Json.Linq;
using StreamJsonRpc;
using System;
using System.Reflection.PortableExecutable;
using System.Threading;

namespace NMF.AnyText
Expand Down
Loading