serdarciplak / BlazorMonaco

Blazor component for Microsoft's Monaco Editor which powers Visual Studio Code.
https://serdarciplak.github.io/BlazorMonaco/
MIT License
432 stars 98 forks source link

Async CompletionItemProvider #124

Closed b12-144 closed 4 months ago

b12-144 commented 5 months ago

Hi, thank you for this awesome project. How can I register an async CompletionItemProvider in order to be able to await editor.GetValue() ? The idea is to get the editor value, parse it and then calculate the suggestions to be presented to the User, but how to do it on a Blazor Server since GetValue() needs to be awaited and editor.GetValue().Result does not work? Follow the code I'm trying to use below:

await BlazorMonaco.Languages.Global.RegisterCompletionItemProvider(JSRuntime, "csharp", 
            async (modelUri, position, context) => { //async is not valid here 
                //trying to get the editor content to calculate what should be displayed as suggestions.  
                var editorContent= await codeEditor.GetValue();
                var completionList = new CompletionList() {
                    Suggestions = [
                        new BlazorMonaco.Languages.CompletionItem {
                            LabelAsString = "Replace by THIS",
                            Kind = CompletionItemKind.Variable,
                            Detail = "this -> THIS",
                            InsertText = "THIS",
                            Preselect = true,
                            RangeAsObject = new BlazorMonaco.Range {
                                StartLineNumber = position.LineNumber,
                                StartColumn = position.Column,
                                EndLineNumber = position.LineNumber,
                                EndColumn = position.Column + 10
                            }
                        }
                    ]
                };
                return completionList;
            });

Thanks, Bruno.

DJSures commented 4 months ago

The problem is that you can't await a Task in the delegate because it isn't a matching return type.

The delegate for the completion provider is (CompletionList)

  public delegate CompletionList ProvideCompletionItemsDelegate(string modelUri, Position position, CompletionContext context);

So you will not be able to await any asynchronous calls in the delegate method. The solution would be for the author to modify the CompletionItemProvider to have a ...

  public delegate Task<CompletionList> ProvideCompletionItemsDelegate(string modelUri, Position position, CompletionContext context);
DJSures commented 4 months ago

Here is a temporary solution. This is a CompletionItemProviderAsync to use instead...

using BlazorMonaco;
using BlazorMonaco.Languages;
using Microsoft.JSInterop;

namespace Core.MonacoEx {

  public class CompletionItemProviderAsync {

    public delegate Task<CompletionList> ProvideCompletionItemsDelegate(string modelUri, Position position, CompletionContext context);

    public delegate Task<CompletionItem> ResolveCompletionItemDelegate(CompletionItem completionItem);

    public List<string> TriggerCharacters { get; set; }

    public ProvideCompletionItemsDelegate ProvideCompletionItemsFunc { get; set; }

    public ResolveCompletionItemDelegate ResolveCompletionItemFunc { get; set; }

    [JSInvokable]
    public Task<CompletionList> ProvideCompletionItems(string modelUri, Position position, CompletionContext context) {
      return ProvideCompletionItemsFunc(modelUri, position, context);
    }

    [JSInvokable]
    async public Task<CompletionItem> ResolveCompletionItem(CompletionItem completionItem) {
      return await ResolveCompletionItemFunc?.Invoke(completionItem);
    }

    public CompletionItemProviderAsync(ProvideCompletionItemsDelegate provideCompletionItems, ResolveCompletionItemDelegate resolveCompletionItem = null) {
      ProvideCompletionItemsFunc = provideCompletionItems;
      ResolveCompletionItemFunc = resolveCompletionItem;
    }
  }
}

And you can register it with...

    public Task RegisterCompletionItemProvider(IJSRuntime jsRuntime, LanguageSelector language, CompletionItemProviderAsync.ProvideCompletionItemsDelegate provideCompletionItems, CompletionItemProviderAsync.ResolveCompletionItemDelegate resolveCompletionItem = null) {

      return RegisterCompletionItemProvider(jsRuntime, language, new CompletionItemProviderAsync(provideCompletionItems, resolveCompletionItem));
    }

And you can AWAIT all you want in the delegate method :)

b12-144 commented 4 months ago

Hi @DJSures , thank you so much for the help!