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 code action #81

Merged
merged 8 commits into from
Jan 31, 2025
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
75 changes: 75 additions & 0 deletions AnyText/AnyText.Core/CodeActionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using NMF.AnyText.Rules;
using NMF.AnyText.Workspace;

namespace NMF.AnyText
{
/// <summary>
/// Represents the information about a code action.
/// </summary>
public class CodeActionInfo
{
/// <summary>
/// RuleApplication of the Action
/// </summary>
public RuleApplication RuleApplication { get; set; }
/// <summary>
/// The title is typically displayed in the UI to describe the action.
/// </summary>
public string Title { get; set; }

/// <summary>
/// Kind of the code action.
/// Possible values:
/// - "quickfix"
/// - "refactor"
/// - "refactor.extract"
/// - "refactor.inline"
/// - "refactor.rewrite"
/// - "source"
/// - "source.organizeImports"
/// </summary>
public string Kind { get; set; }

/// <summary>
/// This array holds diagnostics for which this action is relevant. If no diagnostics are set, the action may apply generally.
/// </summary>
public string[] Diagnostics { get; set; }

/// <summary>
/// A value of <c>true</c> indicates that the code action is preferred; otherwise, <c>false</c> or <c>null</c> if there's no preference.
/// </summary>
public bool IsPreferred { get; set; }

/// <summary>
/// This is the text that describes the command to execute, which can be shown to the user.
/// </summary>
public string CommandTitle { get; set; }

/// <summary>
/// The command is the identifier or name of the action to execute when the user selects it.
/// </summary>
public string CommandIdentifier { get; set; }

/// <summary>
/// These are the parameters passed to the command when it is executed.
/// </summary>
public Dictionary<string, object> Arguments { get; set; }

/// <summary>
/// Identifies the Diagnostic that this Action fixes
/// </summary>
public string DiagnosticIdentifier { get; set; }

/// <summary>
/// Defines the how the WorkspaceEdit Object of this CodeAction is created
/// </summary>
public Func<ExecuteCommandArguments, WorkspaceEdit> WorkspaceEdit { get; set; }

/// <summary>
/// The actual execution of this CodeAction
/// </summary>
public Action<ExecuteCommandArguments> Action { get; set; }
}
}
41 changes: 41 additions & 0 deletions AnyText/AnyText.Core/CodeLensInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using NMF.AnyText.Rules;

namespace NMF.AnyText
{
/// <summary>
/// Represents a CodeLens item used for a Language Server Protocol (LSP) server.
/// CodeLens provides information or actions associated with specific locations in a text document.
/// </summary>
public class CodeLensInfo
{
/// <summary>
/// RuleApplication of the Lens
/// </summary>
public RuleApplication RuleApplication {get;set;}
/// <summary>
/// Gets or sets the title of the CodeLens item, typically a label displayed in the editor.
/// </summary>
public string Title { get; set; }

/// <summary>
/// Gets or sets the identifier for the command to be executed when the CodeLens is activated.
/// </summary>
public string CommandIdentifier { get; set; }

/// <summary>
/// Gets or sets the dictionary of arguments to be passed along with the command when invoked.
/// </summary>
public Dictionary<string, object> Arguments { get; set; }

/// <summary>
/// Gets or sets additional data associated with this CodeLens, which can be used for custom functionality.
/// </summary>
public object Data { get; set; }
/// <summary>
/// The actual execution of this CodeLens
/// </summary>
public Action<ExecuteCommandArguments> Action { get; set; }
}
}
41 changes: 41 additions & 0 deletions AnyText/AnyText.Core/ExecuteCommandArguments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Collections.Generic;
using NMF.AnyText.Rules;

namespace NMF.AnyText
{
/// <summary>
/// Represents the arguments for executing a command on a document.
/// </summary>
public class ExecuteCommandArguments
{
/// <summary>
/// RuleApplication of the Action
/// </summary>
public RuleApplication RuleApplication { get; set; }

/// <summary>
/// ParseContext of the Document
/// </summary>
public ParseContext Context { get; set; }

/// <summary>
/// URI of the document.
/// </summary>
public string DocumentUri { get; set; }

/// <summary>
/// Starting position of the Range.
/// </summary>
public ParsePosition Start { get; set; }

/// <summary>
/// Ending position of the Range.
/// </summary>
public ParsePosition End { get; set; }

/// <summary>
/// Additional options for the command execution.
/// </summary>
public Dictionary<string, object> OtherOptions { get; set; }
}
}
30 changes: 30 additions & 0 deletions AnyText/AnyText.Core/Grammars/Grammar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public void Initialize()
foreach (var rule in allRules)
{
rule.Initialize(_context);

AddActionsFromRule(rule);

if (rule.TokenType != null)
{
CalculateTokenIndices(tokenTypes, tokenModifiers, rule, out int tokenTypeIndex, out int tokenModifierIndex);
Expand All @@ -73,6 +76,18 @@ public void Initialize()
}
}

private void AddActionsFromRule(Rule rule)
{
if (rule.SupportedCodeActions.Any())
foreach (var actionInfo in rule.SupportedCodeActions)
if (!string.IsNullOrEmpty(actionInfo.CommandIdentifier))
ExecutableActions.TryAdd(actionInfo.CommandIdentifier, actionInfo.Action);

if (rule.SupportedCodeLenses.Any())
foreach (var lensInfo in rule.SupportedCodeLenses)
ExecutableActions.TryAdd(lensInfo.CommandIdentifier, lensInfo.Action);
}

/// <summary>
/// Gets an array of comment rules used by this grammar
/// </summary>
Expand Down Expand Up @@ -177,5 +192,20 @@ public Rule Root
/// <param name="context">a context to resolve the root rule</param>
/// <returns>the root rule for this grammar</returns>
protected abstract Rule GetRootRule(GrammarContext context);

/// <summary>
/// Dictionary of executable actions.
/// The key is the action identifier, and the value is the action executor.
/// </summary>
private Dictionary<string, Action<ExecuteCommandArguments>> ExecutableActions { get; } = new ();

/// <summary>
/// Retrieves the dictionary of executable actions as a read-only dictionary.
/// </summary>
/// <returns>A read-only view of the dictionary.</returns>
public IReadOnlyDictionary<string, Action<ExecuteCommandArguments>> GetExecutableActions()
{
return ExecutableActions;
}
}
}
70 changes: 70 additions & 0 deletions AnyText/AnyText.Core/Parser.CodeAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NMF.AnyText.Rules;

namespace NMF.AnyText
{
public partial class Parser
{
/// <summary>
/// Retrieves code action information within a specified range of parse positions.
/// </summary>
/// <param name="start">The starting position of the range.</param>
/// <param name="end">The ending position of the range.</param>
/// <param name="predicate">An optional predicate to filter rule applications.</param>
/// <returns>A collection of <see cref="CodeActionInfo"/> objects representing available code actions.</returns>
public IEnumerable<CodeActionInfo> GetCodeActionInfo(ParsePosition start, ParsePosition end,
Predicate<RuleApplication> predicate = null)
{
predicate ??= _ => true;
var codeActionInfos = new List<CodeActionInfo>();

var ruleApp = Context.Matcher.GetRuleApplicationsAt(start)
.FirstOrDefault(r => r.Rule.IsLiteral);

if (ruleApp == null) return codeActionInfos.ToArray();

while (!(ruleApp.CurrentPosition <= start &&
ruleApp.CurrentPosition + ruleApp.Length >= end))
{
ruleApp = ruleApp.Parent;
if (ruleApp == null)
return codeActionInfos;
}

CollectCodeActionsWithRuleApplication(ruleApp, predicate, codeActionInfos);

var parent = ruleApp.Parent;
while (parent != null && parent.Length == ruleApp.Length)
{
CollectCodeActionsWithRuleApplication(parent, predicate, codeActionInfos);
parent = parent.Parent;

}


return codeActionInfos;
}

private static void CollectCodeActionsWithRuleApplication(RuleApplication ruleApp, Predicate<RuleApplication> predicate,
List<CodeActionInfo> codeActionInfos)
{
if (predicate.Invoke(ruleApp))
codeActionInfos.AddRange(ruleApp.Rule.SupportedCodeActions.Select(a => new CodeActionInfo
{
RuleApplication = ruleApp,
Action = a.Action,
CommandIdentifier = a.CommandIdentifier,
Arguments = a.Arguments,
Diagnostics = a.Diagnostics,
Kind = a.Kind,
CommandTitle = a.CommandTitle,
Title = a.Title,
DiagnosticIdentifier = a.DiagnosticIdentifier,
WorkspaceEdit = a.WorkspaceEdit,
IsPreferred = a.IsPreferred
}));
}
}
}
10 changes: 10 additions & 0 deletions AnyText/AnyText.Core/Rules/MultiRuleApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ public override void Deactivate(ParseContext context)
}
base.Deactivate(context);
}

/// <inheritdoc />
public override void AddCodeLenses(ICollection<CodeLensInfo> codeLenses, Predicate<RuleApplication> predicate = null)
{
foreach (var ruleApplication in Inner)
{
ruleApplication.AddCodeLenses(codeLenses, predicate);
}
base.AddCodeLenses(codeLenses, predicate);
}

internal override RuleApplication MigrateTo(MultiRuleApplication multiRule, ParseContext context)
{
Expand Down
10 changes: 10 additions & 0 deletions AnyText/AnyText.Core/Rules/Rule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,5 +221,15 @@ public uint? TokenModifierIndex
get;
internal set;
}

/// <summary>
/// Gets the list of code actions for this rule.
/// </summary>
public virtual IEnumerable<CodeActionInfo> SupportedCodeActions => Enumerable.Empty<CodeActionInfo>();

/// <summary>
/// Gets the list of code lenses for this rule.
/// </summary>
public virtual IEnumerable<CodeLensInfo> SupportedCodeLenses => Enumerable.Empty<CodeLensInfo>();
}
}
28 changes: 27 additions & 1 deletion AnyText/AnyText.Core/Rules/RuleApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,33 @@ public virtual void Deactivate(ParseContext context)
/// </summary>
/// <returns>A collection of parse errors</returns>
public virtual IEnumerable<ParseError> CreateParseErrors() => Enumerable.Empty<ParseError>();


/// <summary>
/// Adds all CodeLens information of this <see cref="RuleApplication"/> to the provided collection.
/// </summary>
/// <param name="codeLenses">The collection to which the <see cref="CodeLensInfo"/> objects will be added.</param>
/// <param name="predicate">An optional predicate that filters which rule applications should have their CodeLenses added. Default is <c>true</c> for all.</param>
public virtual void AddCodeLenses(ICollection<CodeLensInfo> codeLenses, Predicate<RuleApplication> predicate = null)
{
predicate ??= _ => true;

if (Rule.SupportedCodeLenses.Any() && predicate.Invoke(this))
{
var ruleCodeLenses = Rule.SupportedCodeLenses.Select(a => new CodeLensInfo()
georghinkel marked this conversation as resolved.
Show resolved Hide resolved
{
Arguments = a.Arguments,
CommandIdentifier = a.CommandIdentifier,
Data = a.Data,
Title = a.Title,
RuleApplication = this
});
foreach (var codeLens in ruleCodeLenses)
{
codeLenses.Add(codeLens);
}
}
}

/// <summary>
/// Gets called when the newPosition of the given rule application changes
/// </summary>
Expand Down
9 changes: 9 additions & 0 deletions AnyText/AnyText.Core/Rules/SingleRuleApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ public override void Deactivate(ParseContext context)
}
base.Deactivate(context);
}
/// <inheritdoc />
public override void AddCodeLenses(ICollection<CodeLensInfo> codeLenses, Predicate<RuleApplication> predicate = null)
{
if (Inner != null)
{
Inner.AddCodeLenses(codeLenses, predicate);
}
base.AddCodeLenses(codeLenses, predicate);
}

internal override RuleApplication MigrateTo(SingleRuleApplication singleRule, ParseContext context)
{
Expand Down
23 changes: 23 additions & 0 deletions AnyText/AnyText.Core/Workspace/ChangeAnnotation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace NMF.AnyText.Workspace
{
/// <summary>
/// Represents metadata or instructions for an annotation associated with a change.
/// </summary>
public class ChangeAnnotation
{
/// <summary>
/// A label for the annotation (e.g., "Refactor").
/// </summary>
public string Label { get; set; }

/// <summary>
/// Indicates if the change requires user confirmation.
/// </summary>
public bool? NeedsConfirmation { get; set; }

/// <summary>
/// A description or explanation of the annotation.
/// </summary>
public string Description { get; set; }
}
}
Loading