RobotNet/RobotNet.WebApp/Scripts/Components/Editor.razor
2025-10-15 15:15:53 +07:00

263 lines
8.8 KiB
Plaintext

@implements IAsyncDisposable
@using Microsoft.AspNetCore.Components
@using Microsoft.CodeAnalysis
@using Microsoft.CodeAnalysis.Text
@using Microsoft.JSInterop
@using RobotNet.WebApp.Scripts.Monaco
@using RobotNet.WebApp.Scripts.Monaco.Editor
@using RobotNet.WebApp.Scripts.Monaco.Languages
@using System.Timers
@inject IJSRuntime JsRuntime
@inject ISnackbar Snackbar
<div class="w-100 h-100 d-flex flex-column" tabindex="0" @onkeyup:preventDefault @onkeyup:stopPropagation @onkeyup="OnKeyPress">
<div class="w-100 d-flex align-items-center justify-content-between">
<div></div>
<div>@NameFile</div>
<div></div>
</div>
<div @ref=EditorContainer class="flex-grow-1 w-100"></div>
</div>
@code {
[CascadingParameter]
private ScriptWorkspace Workspace { get; set; } = null!;
private object dotNetHelper = default!;
private IJSInProcessObjectReference? editor;
private IJSInProcessObjectReference? editorModel;
private IJSInProcessObjectReference? disposableCompletionItemProvider;
private IJSInProcessObjectReference? disposableSignatureHelpProvider;
private IJSInProcessObjectReference? disposableHoverProvider;
private bool IsEditorInitialized;
private string Code = "";
private string NameFile = "";
public ProcessorState State { get; private set; } = ProcessorState.Idle;
private bool IsReadOnly = true;
private ElementReference EditorContainer;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
dotNetHelper = DotNetObjectReference.Create(this);
editor = await JsRuntime.InvokeAsync<IJSInProcessObjectReference>("monaco.editor.create", EditorContainer, new
{
Value = "",
Language = "csharp",
Theme = "vs-dark",
GlyphMargin = true,
AutomaticLayout = true,
ReadOnly = IsReadOnly,
});
editorModel = await editor.InvokeAsync<IJSInProcessObjectReference>("getModel");
await JsRuntime.InvokeVoidAsync("MonacoRuntime.OnCodeEditorDidChangeModelContent", editor, dotNetHelper, nameof(CodeEditorDidChangeModelContent));
disposableCompletionItemProvider = await JsRuntime.InvokeAsync<IJSInProcessObjectReference>("MonacoRuntime.CSharpLanguageRegisterCompletionItemProvider", dotNetHelper, nameof(GetCompletionItems));
disposableSignatureHelpProvider = await JsRuntime.InvokeAsync<IJSInProcessObjectReference>("MonacoRuntime.CSharpLanguageRegisterSignatureHelpProvider", dotNetHelper, nameof(GetSignatureHelp));
disposableHoverProvider = await JsRuntime.InvokeAsync<IJSInProcessObjectReference>("MonacoRuntime.CSharpLanguageRegisterHoverProvider", dotNetHelper, nameof(GetQuickInfo));
IsEditorInitialized = true;
Workspace.OnFileChanged += FileChanged;
Workspace.OnDiagnoticChanged += DiagnoticChanged;
}
}
public void OnProcessStateChanged(ProcessorState state)
{
State = state;
var isReadOnly = State != ProcessorState.Ready && State != ProcessorState.Idle && State != ProcessorState.BuildError;
if (isReadOnly != IsReadOnly)
{
IsReadOnly = isReadOnly;
if (editor != null)
{
_ = InvokeAsync(async Task () =>
{
await editor.InvokeVoidAsync("updateOptions", new
{
ReadOnly = IsReadOnly,
});
});
}
}
}
private async Task FileChanged(ScriptFileModel? file)
{
await this.InvokeAsync(async Task () =>
{
if (file is null)
{
NameFile = "";
Code = "";
}
else
{
NameFile = file.Name;
Code = file.EditCode;
}
if (IsEditorInitialized && editor is not null)
{
await editor.InvokeVoidAsync("setValue", Code);
await JsRuntime.InvokeVoidAsync("monaco.editor.setModelMarkers", editorModel, "default", file?.Diagnostics.Select(ToMonacoDiagnostic) ?? []);
// await editor.InvokeVoidAsync("updateOptions", new
// {
// ReadOnly = IsReadOnly,
// });
}
StateHasChanged();
});
}
private async Task DiagnoticChanged(IEnumerable<Diagnostic> diagnostics)
{
await this.InvokeAsync(async Task () =>
{
if (IsEditorInitialized && editor is not null)
{
await JsRuntime.InvokeVoidAsync("monaco.editor.setModelMarkers", editorModel, "default", diagnostics.Select(ToMonacoDiagnostic) ?? []);
}
});
}
[JSInvokable]
public async Task<CompletionList> GetCompletionItems(int line, int column, int kind, char? triggerCharacter)
{
return new()
{
Suggestions = [.. await Workspace.GetCompletionCurrentFileAsync(line, column, kind, triggerCharacter)],
};
}
[JSInvokable]
public async Task<string?> GetQuickInfo(int line, int column)
{
return await Workspace.GetQuickInfoCurrentFile(line, column);
}
[JSInvokable]
public async Task<SignatureHelpResult?> GetSignatureHelp(int line, int column)
{
return await Workspace.GetSignatureHelpCurrentFile(line, column);
}
[JSInvokable]
public async Task CodeEditorDidChangeModelContent(ModelContentChangedEvent e)
{
if (e.IsFlush) return;
if (e.Changes.Length != 0)
{
if (editor is not null)
{
var code = await editor.InvokeAsync<string>("getValue");
if (code != Code)
{
Code = code;
Workspace.WriteDocument(Code);
}
}
}
}
bool isSaving = false;
private async Task OnKeyPress(KeyboardEventArgs e)
{
if ((e.Key == "s" || e.Key == "S") && e.CtrlKey)
{
if(IsReadOnly)
{
Snackbar.Add($"Hệ thống đang ở trạng thái {State}, Không thể thay đổi script", Severity.Warning);
return;
}
if (isSaving) return;
isSaving = true;
var result = await Workspace.SaveCurrentFileAsync();
if (!result.IsSuccess)
{
Snackbar.Add(result.Message, Severity.Error);
}
isSaving = false;
}
}
public async ValueTask DisposeAsync()
{
if (disposableCompletionItemProvider != null)
{
await disposableCompletionItemProvider.InvokeVoidAsync("dispose");
await disposableCompletionItemProvider.DisposeAsync();
disposableCompletionItemProvider = null;
}
if (disposableSignatureHelpProvider != null)
{
await disposableSignatureHelpProvider.InvokeVoidAsync("dispose");
await disposableSignatureHelpProvider.DisposeAsync();
disposableSignatureHelpProvider = null;
}
if (disposableHoverProvider != null)
{
await disposableHoverProvider.InvokeVoidAsync("dispose");
await disposableHoverProvider.DisposeAsync();
disposableHoverProvider = null;
}
if (editorModel != null)
{
await editorModel.InvokeVoidAsync("dispose");
await editorModel.DisposeAsync();
editorModel = null;
}
if (editor != null)
{
await editor.InvokeVoidAsync("dispose");
await editor.DisposeAsync();
editor = null;
}
if (IsEditorInitialized)
{
Workspace.OnFileChanged -= FileChanged;
Workspace.OnDiagnoticChanged -= DiagnoticChanged;
}
GC.SuppressFinalize(this);
}
private static MarkerData ToMonacoDiagnostic(Diagnostic diagnostic)
{
var lineSpan = diagnostic.Location.GetLineSpan();
return new()
{
StartLineNumber = lineSpan.StartLinePosition.Line + 1,
StartColumn = lineSpan.StartLinePosition.Character + 1,
EndLineNumber = lineSpan.EndLinePosition.Line + 1,
EndColumn = lineSpan.EndLinePosition.Character + 1,
Message = diagnostic.GetMessage(),
Severity = diagnostic.Severity switch
{
DiagnosticSeverity.Info => MarkerSeverity.Info,
DiagnosticSeverity.Warning => MarkerSeverity.Warning,
DiagnosticSeverity.Error => MarkerSeverity.Error,
_ => MarkerSeverity.Hint,
},
};
}
}