Skip to content

Commit

Permalink
LSP Folding Ranges (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
georghinkel authored Jan 30, 2025
2 parents f076d7f + 63d2696 commit 9fa0f23
Show file tree
Hide file tree
Showing 20 changed files with 472 additions and 11 deletions.
2 changes: 2 additions & 0 deletions AnyText/AnyMeta/AnyMetaParseContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ private Dictionary<string, INamespace> GetOrCreateNamespaceDictionary()
{
_namespaces = new Dictionary<string, INamespace>();

RegisterNamespace(MetaElement.ClassInstance.Namespace);

if (Imports.Count > 0)
{
var repository = new ModelRepository();
Expand Down
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
40 changes: 40 additions & 0 deletions AnyText/AnyText.Core/Rules/RuleApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,46 @@ 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];

RuleApplication endCommentRuleApplication;
do
{
endCommentRuleApplication = Comments[i++];
}
while (endCommentRuleApplication.CurrentPosition.Col == commentRuleApplication.CurrentPosition.Col && i < Comments.Count);

if (commentRuleApplication.CurrentPosition.Line == endCommentRuleApplication.CurrentPosition.Line + endCommentRuleApplication.Length.Line) continue;

var commentsFoldingRange = new FoldingRange()
{
StartLine = (uint)commentRuleApplication.CurrentPosition.Line,
StartCharacter = (uint)commentRuleApplication.CurrentPosition.Col,
EndLine = (uint)(endCommentRuleApplication.CurrentPosition.Line + endCommentRuleApplication.Length.Line),
EndCharacter = (uint)(endCommentRuleApplication.CurrentPosition.Col + endCommentRuleApplication.Length.Col),
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
38 changes: 38 additions & 0 deletions AnyText/AnyText.Lsp/LspServer.FoldingRanges.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using LspTypes;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NMF.AnyText
{
public partial class LspServer
{
/// <inheritdoc cref="ILspServer.QueryFoldingRanges(JToken)"/>
public LspTypes.FoldingRange[] QueryFoldingRanges(JToken arg)
{
var foldingRangeParams = arg.ToObject<FoldingRangeParams>();
string uri = foldingRangeParams.TextDocument.Uri;

if (!_documents.TryGetValue(uri, out var document))
{
return Array.Empty<LspTypes.FoldingRange>();
}

var foldingRanges = document.GetFoldingRangesFromRoot();

return foldingRanges.Select(foldingRange => new LspTypes.FoldingRange()
{
StartLine = foldingRange.StartLine,
StartCharacter = foldingRange.StartCharacter,
EndLine = foldingRange.EndLine,
EndCharacter = foldingRange.EndCharacter,
Kind = foldingRange.Kind
}).ToArray();
}
}
}
Loading

0 comments on commit 9fa0f23

Please sign in to comment.