Luthetus.TextEditor
4.8.0
See the version list below for details.
dotnet add package Luthetus.TextEditor --version 4.8.0
NuGet\Install-Package Luthetus.TextEditor -Version 4.8.0
<PackageReference Include="Luthetus.TextEditor" Version="4.8.0" />
<PackageVersion Include="Luthetus.TextEditor" Version="4.8.0" />
<PackageReference Include="Luthetus.TextEditor" />
paket add Luthetus.TextEditor --version 4.8.0
#r "nuget: Luthetus.TextEditor, 4.8.0"
#addin nuget:?package=Luthetus.TextEditor&version=4.8.0
#tool nuget:?package=Luthetus.TextEditor&version=4.8.0
Full documentation is available in the GitHub repo:
Luthetus.TextEditor (v4.9.0)
Installation
Goal
- Reference the
Luthetus.TextEditor
Nuget Package - Register the
Services
- Reference the
CSS
- Reference the
JavaScript
- In
MainLayout.razor
render the<Luthetus.Common.RazorLib.Installations.Displays.LuthetusCommonInitializer />
and the<Luthetus.TextEditor.RazorLib.Installations.Displays.LuthetusTextEditorInitializer />
Blazor components
Steps
- Reference the
Luthetus.TextEditor
NuGet Package
Use your preferred way to install NuGet Packages to install Luthetus.TextEditor
.
- Register the
Services
Go to the file that you register your services and add the following lines of C# code.
/* using Luthetus.Common.RazorLib.Installations.Models;
using Luthetus.Common.RazorLib.BackgroundTasks.Models;
using Luthetus.TextEditor.RazorLib.Installations.Models;
using Luthetus.TextEditor.RazorLib.CompilerServices;
using Luthetus.TextEditor.RazorLib.Decorations.Models; */
// Use either Wasm or ServerSide depending on how your app is being hosted.
// var luthetusHostingKind = LuthetusHostingKind.ServerSide;
var luthetusHostingKind = LuthetusHostingKind.Wasm;
var hostingInformation = new LuthetusHostingInformation(
luthetusHostingKind,
LuthetusPurposeKind.TextEditor,
new BackgroundTaskService());
services.AddLuthetusTextEditor(hostingInformation);
// CompilerServiceRegistry
//
// You can create your own implementation of:
// - ICompilerServiceRegistry
// - IDecorationMapperRegistry
//
// In order to support any file extension.
//
// The "Default" ones will treat all file extensions
// as plain text.
//
return services
.AddScoped<ICompilerServiceRegistry, CompilerServiceRegistryDefault>()
.AddScoped<IDecorationMapperRegistry, DecorationMapperRegistryDefault>();
- Reference the
CSS
Go to the file that you reference CSS files from and add the following CSS references.
<link href="_content/Luthetus.Common/luthetusCommon.css" rel="stylesheet" />
<link href="_content/Luthetus.TextEditor/luthetusTextEditor.css" rel="stylesheet" />
- Reference the
JavaScript
Go to the file that you reference JavaScript files from and add the following JavaScript reference below the Blazor framework JavaScript reference
<script src="_content/Luthetus.Common/luthetusCommon.js"></script>
<script src="_content/Luthetus.TextEditor/luthetusTextEditor.js"></script>
- In
App.razor
add the following towards the top of the file:
<Luthetus.Common.RazorLib.Installations.Displays.LuthetusCommonInitializer />
<Luthetus.TextEditor.RazorLib.Installations.Displays.LuthetusTextEditorInitializer />
Luthetus.TextEditor (v4.9.0)
Usage
Goal
- Render a C# Text Editor which makes use of the CSharpCompilerService
Steps
- Create a codebehind for the file:
Pages/Index.razor
// Pages/Index.razor.cs
using Microsoft.AspNetCore.Components;
namespace Luthetus.Tutorials.RazorLib.Pages;
public partial class Index : ComponentBase
{
}
There are 2 parts to rendering a Text Editor.
First, one needs to make a TextEditorModel.
Then, one needs to make a TextEditorViewModel.
In Index.razor.cs add the following line of code:
// using Luthetus.TextEditor.RazorLib.Lexers.Models;
public static ResourceUri ResourceUri { get; } = new("/index.txt");
The ResourceUri is a unique identifier for a given
TextEditorModel
.One might think of a
TextEditorModel
as mapping to a file on their filesystem, as an example.Override the Blazor lifecycle method named:
OnInitialized()
protected override void OnInitialized()
{
base.OnInitialized();
}
Inside this override we can register the
TextEditorModel
.We need access to the ITextEditorService to register a
TextEditorModel
. So, inject theITextEditorService
.
// using Luthetus.TextEditor.RazorLib;
[Inject]
private ITextEditorService TextEditorService { get; set; } = null!;
The
ITextEditorService
has public properties that encapsulate the API for a given datatype in theLuthetus.TextEditor
namespace. For example,TextEditorService.ModelApi
accesses theModelApi
property, which has all of the API related to theTextEditorModel
datatype.By invoking
TextEditorService.ModelApi.RegisterCustom(...);
, we can register a TextEditorModel. TheRegisterCustom(...)
method takes as parameters: an instance of TextEditorEditContext, and aTextEditorModel
. So we need to make theTextEditorModel
instance.In the override for
OnInitialized()
, create an instance of aTextEditorModel
.
// using Luthetus.TextEditor.RazorLib.TextEditors.Models;
var model = new TextEditorModel(
ResourceUri,
DateTime.UtcNow,
ExtensionNoPeriodFacts.TXT,
"public class MyClass\n{\n\t\n}\n",
decorationMapper: null,
compilerService: null);
Now, we need the
TextEditorEditContext
. InvokeTextEditorService.WorkerArbitrary.PostUnique(...)
. The first argument is a "name" for the work item. The second argument is a Func that will provide you aTextEditorEditContext
instance, and expects you to return aValueTask
.Inside the
TextEditorService.WorkerArbitrary.PostUnique(...)
Func argument, go on to in the body of the Func, invokeTextEditorService.Model.RegisterCustom(...)
. Pass theTextEditorEditContext
that the Func provided, and the instance of theTextEditorModel
.
protected override void OnInitialized()
{
// using Luthetus.TextEditor.RazorLib.TextEditors.Models;
var model = new TextEditorModel(
ResourceUri,
DateTime.UtcNow,
ExtensionNoPeriodFacts.TXT,
"public class MyClass\n{\n\t\n}\n",
decorationMapper: null,
compilerService: null);
TextEditorService.WorkerArbitrary.PostUnique(nameof(Index), editContext =>
{
TextEditorService.ModelApi.RegisterCustom(editContext, model);
return ValueTask.CompletedTask;
});
base.OnInitialized();
}
In order to render a
TextEditorModel
, we need a TextEditorViewModel. A file on one's operating system would relate to amodel
. Whereas, an open text editor would relate to aviewModel
.Many
TextEditorViewModel
can reference an underlyingTextEditorModel
. But, only 1model
for a given file on one's operating system can exist.
NOTE: I used a file system as an example but, a TextEditorModel can be made "out of thin air" just the same.
We need a TextEditorViewModelKey so we can maintain the TextEditorViewModel state between page changes.
Add the following line of code:
// using Luthetus.Common.RazorLib.Keys.Models;
public static Key<TextEditorViewModel> ViewModelKey { get; } = Key<TextEditorViewModel>.NewKey();
- Now, inside the
TextEditorService.WorkerArbitrary.PostUnique(...)
Func argument, invokeTextEditorService.ViewModelApi.Register(...)
after the line that you registered the model.
NOTE: The argument 'Category' to 'TextEditorService.ViewModelApi.Register(...)' can be passed as 'new Category("main")'. It acts only as a way to filter a list of view models.
TextEditorService.ViewModelApi.Register(
editContext,
ViewModelKey,
ResourceUri,
new Category("main"));
- My
Pages/Index.razor.cs
file as of this step looks as follows:
using Microsoft.AspNetCore.Components;
using Luthetus.Common.RazorLib.Keys.Models;
using Luthetus.TextEditor.RazorLib;
using Luthetus.TextEditor.RazorLib.Lexers.Models;
using Luthetus.TextEditor.RazorLib.TextEditors.Models;
namespace NugetPaTe490.ServerSide.Pages;
public partial class Index : ComponentBase
{
[Inject]
private ITextEditorService TextEditorService { get; set; } = null!;
public static ResourceUri ResourceUri { get; } = new("/index.txt");
public static Key<TextEditorViewModel> ViewModelKey { get; } = Key<TextEditorViewModel>.NewKey();
protected override void OnInitialized()
{
var model = new TextEditorModel(
ResourceUri,
DateTime.UtcNow,
ExtensionNoPeriodFacts.TXT,
"public class MyClass\n{\n\t\n}\n",
decorationMapper: null,
compilerService: null);
TextEditorService.WorkerArbitrary.PostUnique(nameof(Index), editContext =>
{
TextEditorService.ModelApi.RegisterCustom(editContext, model);
TextEditorService.ViewModelApi.Register(
editContext,
ViewModelKey,
ResourceUri,
new Category("main"));
return ValueTask.CompletedTask;
});
base.OnInitialized();
}
}
- In the .razor markup (
Pages/Index.razor
) render the Blazor component:
<TextEditorViewModelDisplay/>
This component takes only one required parameter, any others are for customization.
The required parameter named
TextEditorViewModelKey
can be given the property namedViewModelKey
, which was made in the codebehind.
@page "/"
@using Luthetus.TextEditor.RazorLib.TextEditors.Displays;
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<TextEditorViewModelDisplay TextEditorViewModelKey="ViewModelKey" />
- A plain text editor without syntax highlighting should render now when the app is ran.
Height of the text editor is 100% of the parent element.
For the Blazor default project template I'll provide a quick hack I did to get things to work, but this only demonstrates the idea of how you'd fix a height issue, not well written CSS.
@page "/"
@using Luthetus.TextEditor.RazorLib.TextEditors.Displays;
<PageTitle>Index</PageTitle>
<div style="height: 5em;">
<h1>Hello, world!</h1>
Welcome to your new app.
</div>
@* 10px for padding *@
<div style="height: calc(100% - 5em - 10px);">
<TextEditorViewModelDisplay TextEditorViewModelKey="ViewModelKey"/>
</div>
@inherits LayoutComponentBase
<PageTitle>Luthetus.Tutorials</PageTitle>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<article class="content px-4" style="height: calc(100% - 56px);">
@Body
</article>
</main>
</div>
The text editor automatically detects the user agent resize events and re-renders at a 3 second debounce (BrowserResizeInterop).
Now we can add in the C# Compiler Service.
Reference the
Luthetus.CompilerServices.CSharp
Nuget Package
Go to the file that you register your services and locate where you previously added the scoped ICompilerServiceRegistry, and IDecorationMapperRegistry. Instead of using the "default" implementations we can provide our own Type that will implement the interface.
/*using Luthetus.TextEditor.RazorLib.CompilerServices.Interfaces;
using Luthetus.TextEditor.RazorLib.Decorations.Models;*/
// NOTE: the next step creates the implementations
services
.AddScoped<ICompilerServiceRegistry, CompilerServiceRegistry>()
.AddScoped<IDecorationMapperRegistry, DecorationMapperRegistry>();
Add CompilerServiceRegistry.cs as follows:
using Luthetus.Common.RazorLib.Clipboards.Models;
using Luthetus.TextEditor.RazorLib;
using Luthetus.TextEditor.RazorLib.CompilerServices;
using Luthetus.TextEditor.RazorLib.TextEditors.Models;
using Luthetus.CompilerServices.CSharp.CompilerServiceCase;
namespace NugetPaTe490.ServerSide;
public class CompilerServiceRegistry : ICompilerServiceRegistry
{
private readonly Dictionary<string, ICompilerService> _map = new();
public IReadOnlyList<ICompilerService> CompilerServiceList => _map.Values.ToList();
public CompilerServiceRegistry(ITextEditorService textEditorService, IClipboardService clipboardService)
{
CSharpCompilerService = new CSharpCompilerService(textEditorService, clipboardService);
DefaultCompilerService = new CompilerServiceDoNothing();
_map.Add(ExtensionNoPeriodFacts.C_SHARP_CLASS, CSharpCompilerService);
}
public CSharpCompilerService CSharpCompilerService { get; }
public CompilerServiceDoNothing DefaultCompilerService { get; }
public ICompilerService GetCompilerService(string extensionNoPeriod)
{
if (_map.TryGetValue(extensionNoPeriod, out var compilerService))
return compilerService;
return DefaultCompilerService;
}
}
Add DecorationMapperRegistry.cs as follows:
using Luthetus.TextEditor.RazorLib.Decorations.Models;
using Luthetus.TextEditor.RazorLib.TextEditors.Models;
namespace NugetPaTe490.ServerSide;
public class DecorationMapperRegistry : IDecorationMapperRegistry
{
private Dictionary<string, IDecorationMapper> _map { get; } = new();
public DecorationMapperRegistry()
{
GenericDecorationMapper = new GenericDecorationMapper();
DefaultDecorationMapper = new TextEditorDecorationMapperDefault();
_map.Add(ExtensionNoPeriodFacts.C_SHARP_CLASS, GenericDecorationMapper);
}
public GenericDecorationMapper GenericDecorationMapper { get; }
public TextEditorDecorationMapperDefault DefaultDecorationMapper { get; }
public IDecorationMapper GetDecorationMapper(string extensionNoPeriod)
{
if (_map.TryGetValue(extensionNoPeriod, out var decorationMapper))
return decorationMapper;
return DefaultDecorationMapper;
}
}
In the
Pages/Index.razor.cs
file we now can provide theCSharpCompilerService
when instantiating aTextEditorModel
.Inject the
ICompilerServiceRegistry
and theIDecorationMapperRegistry
/*using Luthetus.TextEditor.RazorLib.CompilerServices;
using Luthetus.TextEditor.RazorLib.Decorations.Models;*/
[Inject]
private ICompilerServiceRegistry CompilerServiceRegistry { get; set; } = null!;
[Inject]
private IDecorationMapperRegistry DecorationMapperRegistry { get; set; } = null!;
- In
OnInitialized()
, prior to constructing theTextEditorModel
we can get the decoration mapper, and compiler service we wish to use.
var genericDecorationMapper = DecorationMapperRegistry.GetDecorationMapper(
ExtensionNoPeriodFacts.C_SHARP_CLASS);
var cSharpCompilerService = CompilerServiceRegistry.GetCompilerService(
ExtensionNoPeriodFacts.C_SHARP_CLASS);
Then pass in
genericDecorationMapper
andcSharpCompilerService
instead of the nulls when invoking theTextEditorModel
constructor.One last step, on the line immediately following,
TextEditorService.ModelApi.RegisterCustom(editContext, model);
add the line:cSharpCompilerService.RegisterResource(model.ResourceUri, shouldTriggerResourceWasModified: true);
My
Pages/Index.razor.cs
file as of this step is shown in the following code snippet:
using Microsoft.AspNetCore.Components;
using Luthetus.Common.RazorLib.Keys.Models;
using Luthetus.TextEditor.RazorLib;
using Luthetus.TextEditor.RazorLib.Lexers.Models;
using Luthetus.TextEditor.RazorLib.TextEditors.Models;
using Luthetus.TextEditor.RazorLib.CompilerServices;
using Luthetus.TextEditor.RazorLib.Decorations.Models;
namespace NugetPaTe490.ServerSide.Pages;
public partial class Index : ComponentBase
{
[Inject]
private ITextEditorService TextEditorService { get; set; } = null!;
[Inject]
private ICompilerServiceRegistry CompilerServiceRegistry { get; set; } = null!;
[Inject]
private IDecorationMapperRegistry DecorationMapperRegistry { get; set; } = null!;
public static ResourceUri ResourceUri { get; } = new("/index.txt");
public static Key<TextEditorViewModel> ViewModelKey { get; } = Key<TextEditorViewModel>.NewKey();
protected override void OnInitialized()
{
var genericDecorationMapper = DecorationMapperRegistry.GetDecorationMapper(
ExtensionNoPeriodFacts.C_SHARP_CLASS);
var cSharpCompilerService = CompilerServiceRegistry.GetCompilerService(
ExtensionNoPeriodFacts.C_SHARP_CLASS);
var model = new TextEditorModel(
ResourceUri,
DateTime.UtcNow,
ExtensionNoPeriodFacts.TXT,
"public class MyClass\n{\n\t\n}\n",
genericDecorationMapper,
cSharpCompilerService);
TextEditorService.WorkerArbitrary.PostUnique(nameof(Index), editContext =>
{
TextEditorService.ModelApi.RegisterCustom(editContext, model);
cSharpCompilerService.RegisterResource(model.ResourceUri, shouldTriggerResourceWasModified: true);
TextEditorService.ViewModelApi.Register(
editContext,
ViewModelKey,
ResourceUri,
new Category("main"));
return ValueTask.CompletedTask;
});
base.OnInitialized();
}
}
Now you should have a Text Editor with the CSharpCompilerService.
I followed this tutorial myself, and a mistake I made was that I never changed the "default" registry implementations -- I only made the classes. After fixing this, my service registrations look like the following:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using NugetPaTe490.ServerSide;
using NugetPaTe490.ServerSide.Data;
using Luthetus.Common.RazorLib.Installations.Models;
using Luthetus.Common.RazorLib.BackgroundTasks.Models;
using Luthetus.TextEditor.RazorLib.Installations.Models;
using Luthetus.TextEditor.RazorLib.CompilerServices;
using Luthetus.TextEditor.RazorLib.Decorations.Models;
var builder = WebApplication.CreateBuilder(args);
var luthetusHostingKind = LuthetusHostingKind.ServerSide;
var hostingInformation = new LuthetusHostingInformation(
luthetusHostingKind,
LuthetusPurposeKind.TextEditor,
new BackgroundTaskService());
builder.Services.AddLuthetusTextEditor(hostingInformation);
builder.Services
.AddScoped<ICompilerServiceRegistry, CompilerServiceRegistry>()
.AddScoped<IDecorationMapperRegistry, DecorationMapperRegistry>();
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. |
-
net8.0
- Luthetus.Common (>= 4.8.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Luthetus.TextEditor:
Package | Downloads |
---|---|
Luthetus.ExtendCompilerServices
An optional, opinionated implementation for various ICompilerService features. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
4.9.0 | 38 | 4/23/2025 |
4.8.0 | 90 | 4/5/2025 |
4.7.0 | 162 | 3/29/2025 |
4.6.0 | 492 | 3/25/2025 |
4.5.0 | 158 | 3/20/2025 |
4.3.0 | 171 | 3/10/2025 |
4.2.0 | 221 | 3/6/2025 |
4.1.0 | 104 | 2/7/2025 |
4.0.0 | 125 | 1/30/2025 |
3.9.0 | 112 | 1/22/2025 |
3.8.0 | 111 | 1/20/2025 |
3.7.0 | 107 | 1/18/2025 |
3.6.0 | 141 | 12/31/2024 |
3.5.0 | 117 | 12/21/2024 |
3.4.0 | 130 | 12/6/2024 |
3.3.0 | 121 | 11/28/2024 |
3.2.0 | 132 | 10/7/2024 |
3.1.0 | 119 | 10/3/2024 |
2.7.0 | 133 | 9/21/2024 |
2.6.0 | 128 | 9/19/2024 |
2.5.0 | 171 | 9/5/2024 |
2.4.0 | 148 | 9/2/2024 |
2.3.0 | 139 | 8/28/2024 |
2.2.0 | 158 | 8/21/2024 |
2.1.0 | 153 | 8/14/2024 |
2.0.0 | 139 | 8/13/2024 |
1.4.0 | 211 | 10/6/2023 |
1.3.0 | 213 | 8/20/2023 |
1.2.0 | 150 | 8/19/2023 |
1.1.0 | 172 | 8/18/2023 |
1.0.0 | 191 | 8/16/2023 |