Commit 84e08b62 authored by Jesse McGrew's avatar Jesse McGrew
Browse files

Working on Blazor REPL and project editor

parent c1a62e02fdc6
......@@ -66,7 +66,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Package", "Package", "{3E28
Package\zip\Zip.Packaging.targets = Package\zip\Zip.Packaging.targets
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zilf.Common.Tests", "test\Zilf.Common.Tests\Zilf.Common.Tests.csproj", "{337F098C-4DD4-420D-825C-4A40DF99AD89}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Zilf.Common.Tests", "test\Zilf.Common.Tests\Zilf.Common.Tests.csproj", "{337F098C-4DD4-420D-825C-4A40DF99AD89}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Zilf.Playground", "src\Zilf.Playground\Zilf.Playground.csproj", "{D38CAE84-836D-416D-83F6-DE2F2502658C}"
EndProject
......
......@@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="JunitXml.TestLogger" Version="2.1.78" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="16.9.1025">
<PrivateAssets>all</PrivateAssets>
......
......@@ -116,7 +116,7 @@ namespace ZilfAnalyzers
// has a constant value?
var constantValue = semanticModel.GetConstantValue(expressionToReplace);
if (constantValue.HasValue)
if (constantValue.HasValue && constantValue.Value != null)
{
format = (string)constantValue.Value;
newArgs = ImmutableList<ExpressionSyntax>.Empty;
......@@ -178,7 +178,7 @@ namespace ZilfAnalyzers
var formatConstValue = semanticModel.GetConstantValue(formatExpr);
if (!formatConstValue.HasValue)
if (!formatConstValue.HasValue || formatConstValue.Value == null)
return false;
formatStr = (string)formatConstValue.Value;
......
......@@ -142,9 +142,13 @@ namespace ZilfAnalyzers
i.ConstantAccessSyntax.WithTriviaFrom(i.ExpressionToReplace));
if (i.NewMessageArgs.Any())
{
ArgumentSyntax? ancestor = i.ExpressionToReplace.FirstAncestorOrSelf<ArgumentSyntax>();
Debug.Assert(ancestor != null);
docEditor.InsertAfter(
i.ExpressionToReplace.FirstAncestorOrSelf<ArgumentSyntax>(),
ancestor,
i.NewMessageArgs.Select(a => SyntaxFactory.Argument(a).WithAdditionalAnnotations(Formatter.Annotation)));
}
}
AddUsingIfNeeded(docEditor);
......@@ -335,6 +339,7 @@ namespace ZilfAnalyzers
{
case FixAllScope.Document:
{
Debug.Assert(fixAllContext.Document != null);
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(fixAllContext.Document).ConfigureAwait(false);
diagnosticsToFix.Add(new KeyValuePair<Project, ImmutableArray<Diagnostic>>(fixAllContext.Project, diagnostics));
fixAllTitle = string.Format(TitleFormat, "document", fixAllContext.Document.Name);
......
......@@ -115,7 +115,7 @@ namespace ZilfAnalyzers
{
var constValue = context.SemanticModel.GetConstantValue(varDecl.Initializer.Value);
if (!constValue.HasValue)
if (!constValue.HasValue || constValue.Value == null)
continue;
// check for duplicate code
......
......@@ -115,7 +115,8 @@ namespace ZilfAnalyzers
// get format string and split it into prefix + rest
semanticModel = await document.GetSemanticModelAsync(cancellationToken);
root = await document.GetSyntaxRootAsync(cancellationToken);
var formatStr = (string)semanticModel!.GetConstantValue(FindFormatExpr(root!), cancellationToken).Value;
var formatStr = (string?)semanticModel!.GetConstantValue(FindFormatExpr(root!), cancellationToken).Value;
Debug.Assert(formatStr != null);
var match = MessageConstantAnalyzer.PrefixedMessageFormatRegex.Match(formatStr);
var prefix = match.Groups["prefix"].Value;
var rest = match.Groups["rest"].Value;
......
......@@ -24,7 +24,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" PrivateAssets="all" />
<!--<PackageReference Update="NETStandard.Library" PrivateAssets="all" />-->
</ItemGroup>
......
#nullable enable
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Zilf.Playground
{
internal sealed class JSInterop
{
private readonly IJSRuntime js;
public JSInterop(IJSRuntime js)
{
this.js = js;
}
public void DownloadBytesAsFile(byte[] bytes, string filename, string contentType)
{
var jsu = (IJSUnmarshalledRuntime)js;
jsu.InvokeUnmarshalled<string, string, byte[], bool>("ZilfJsInterop.BlazorDownloadFileFast", filename, contentType, bytes);
}
public async Task ScrollToBottomAsync(ElementReference element)
{
await js.InvokeVoidAsync("ZilfJsInterop.scrollToBottom", element);
}
public async Task<string> PromptAsync(string prompt, string value)
{
return await js.InvokeAsync<string>("prompt", prompt, value);
}
public async Task AddEventListenerAsync(ElementReference element, string eventName, string jsHandler, params object[] extraHandlerArgs)
{
await js.InvokeVoidAsync("ZilfJsInterop.addEventListener", element, eventName, jsHandler, extraHandlerArgs);
}
}
}
@page "/doc"
<h3>Documentation</h3>
<ul>
<li><a href="http://faq.zilf.io/">Getting Started</a></li>
<li><a href="http://g.zilf.io/">ZIL Group on Facebook</a></li>
</ul>
@page "/"
@using Zapf
@using Zilf.Common
@using Zilf.Compiler
@using Zilf.Diagnostics
<h1>ZIL Playground</h1>
<div>
<div style="margin:10px 0;">
Theme:
<select @onchange="ChangeTheme">
<option value="vs">Visual Studio</option>
<option value="vs-dark">Visual Studio Dark</option>
<option value="hc-black">High Contrast Black</option>
</select>
</div>
@*<div style="margin:10px 0;">
New Value: <input type="text" @bind="ValueToSet" style="width: 400px;" /> <button @onclick="SetValue">Set Value</button>
</div>
<div style="margin:10px 0;">
<button @onclick="GetValue">Get Value</button>
</div>
<div style="margin:10px 0;">
<button @onclick="AddCommand">Add Command (Ctrl+Enter)</button>
</div>
<div style="margin:10px 0;">
<button @onclick="AddAction">Add Action (Ctrl+D)</button>
</div>
<div style="margin:10px 0;">
See the console for results.
</div>*@
<div style="margin:10px 0;">
<button @onclick="Compile">Compile</button>
@layout ContainerlessLayout
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">ZIL Forever</h1>
<p class="lead">
It&rsquo;s scary, ugly, and hard to understand&mdash;and until recently, it was dead.
But it&rsquo;s back on its feet again, and you&rsquo;re gonna love it.
</p>
<p class="text-muted">
Before video games, there was interactive fiction.
The genre was defined by classics like <a href="https://en.wikipedia.org/wiki/Zork">Zork</a>,
<a href="https://en.wikipedia.org/wiki/Planetfall">Planetfall</a>,
and <a href="https://en.wikipedia.org/wiki/Enchanter_(video_game)">Enchanter</a>,
which were written in Infocom&rsquo;s custom programming language:
the Zork Implementation Language, or ZIL.
</p>
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more &raquo;</a></p>
</div>
</div>
<MonacoEditor @ref="_editor"
Id="code-editor"
ConstructionOptions="EditorConstructionOptions"
OnDidInit="EditorOnDidInit"
OnContextMenu="EditorOnContextMenu"
/>
<CompilerOutput CompileResult="@CompileResult"
AssembleResult="@AssembleResult"
FileSystem="@FileSystem" />
@code {
private MonacoEditor _editor { get; set; }
private string ValueToSet { get; set; }
private FrontEndResult CompileResult { get; set; }
private AssemblyResult? AssembleResult { get; set; }
private InMemoryFileSystem FileSystem { get; set; }
private StandaloneEditorConstructionOptions EditorConstructionOptions(MonacoEditor editor)
{
return new StandaloneEditorConstructionOptions
{
Language = "zil",
GlyphMargin = true,
Value = "<VERSION ZIP>\n" +
"\n" +
"<ROUTINE GO ()\n" +
" <TELL \"Hello world!\" CR>\n" +
" <QUIT>>",
};
}
private async Task EditorOnDidInit(MonacoEditorBase editor)
{
await _editor.AddCommand((int)KeyMode.CtrlCmd | (int)KeyCode.KEY_H, (editor, keyCode) =>
{
Console.WriteLine("Ctrl+H : Initial editor command is triggered.");
System.Diagnostics.Debugger.Break();
});
//var newDecorations = new ModelDeltaDecoration[]
//{
// new ModelDeltaDecoration
// {
// Range = new BlazorMonaco.Bridge.Range(3,1,3,1),
// Options = new ModelDecorationOptions
// {
// IsWholeLine = true,
// ClassName = "decorationContentClass",
// GlyphMarginClassName = "decorationGlyphMarginClass"
// }
// }
//};
//decorationIds = await _editor.DeltaDecorations(null, newDecorations);
// You can now use 'decorationIds' to change or remove the decorations
}
//private string[] decorationIds;
private void EditorOnContextMenu(EditorMouseEvent eventArg)
{
Console.WriteLine("OnContextMenu : " + System.Text.Json.JsonSerializer.Serialize(eventArg));
}
private async Task ChangeTheme(ChangeEventArgs e)
{
Console.WriteLine($"setting theme to: {e.Value.ToString()}");
await MonacoEditor.SetTheme(e.Value.ToString());
}
private async Task SetValue()
{
Console.WriteLine($"setting value to: {ValueToSet}");
await _editor.SetValue(ValueToSet);
}
private async Task GetValue()
{
var val = await _editor.GetValue();
Console.WriteLine($"value is: {val}");
}
private async Task AddCommand()
{
await _editor.AddCommand((int)KeyMode.CtrlCmd | (int)KeyCode.Enter, (editor, keyCode) =>
{
Console.WriteLine("Ctrl+Enter : Editor command is triggered.");
});
}
private async Task AddAction()
{
await _editor.AddAction("testAction", "Test Action", new int[] { (int)KeyMode.CtrlCmd | (int)KeyCode.KEY_D, (int)KeyMode.CtrlCmd | (int)KeyCode.KEY_B }, null, null, "navigation", 1.5, (editor, keyCodes) =>
{
Console.WriteLine("Ctrl+D : Editor action is triggered.");
});
}
private async Task Compile()
{
var fileSystem = new InMemoryFileSystem
{
["main.zil"] = await _editor.GetValue(),
};
var compiler = new FrontEnd { FileSystem = fileSystem, Logger = NullDiagnosticLogger.Instance };
var compResult = compiler.Compile("main.zil", "main.zap", false);
<div class="container">
<div class="row">
<div class="col-md-4">
<h2>Portable</h2>
<p>
ZILF games run anywhere there&rsquo;s a <a href="https://en.wikipedia.org/wiki/Z-machine">Z-machine</a>
interpreter, including most computers, phones, and web browsers. ZILF itself is built with
<a href="https://dotnet.microsoft.com/">.NET</a> and runs on most computers too&mdash;Windows,
Linux, and macOS. You can even try it out right here, on the web.
</p>
<p><a class="btn btn-secondary" href="project/new" role="button">Try it now &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Retro</h2>
<p>
ZIL is based on <a href="https://mdl-language.readthedocs.io/en/latest/">MDL</a>, a Lisp-like
language developed for AI research in the 1970s. And it feels like it. All caps? Check.
Command line interface? Oh, yeah. Cryptic names? We got &rsquo;em.
</p>
<p><a class="btn btn-secondary" href="repl" role="button">Nerd it up &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Open</h2>
<p>
ZILF is free, open source software distributed under the GNU General Public License. You
can read the code, report issues, and submit patches at the Heptapod project site.
</p>
<p><a class="btn btn-secondary" href="https://foss.heptapod.net/zilf/zilf/" role="button">View project &raquo;</a></p>
</div>
</div>
CompileResult = compResult;
AssembleResult = null;
FileSystem = fileSystem;
<hr>
if (compResult.Success)
{
var assembler = new Zapf.ZapfAssembler { FileSystem = fileSystem };
AssembleResult = assembler.Assemble("main.zap", "main.z#");
}
}
}
\ No newline at end of file
</div> <!-- /container -->
@page "/oldindex"
@using Zapf
@using Zilf.Compiler
@using Zilf.Diagnostics
@inject TemplateService Templates
<h1>ZIL Playground</h1>
<div>
<div style="margin:10px 0;">
<label for="theme">Theme:</label>
<select id="theme" @onchange="ChangeTheme">
<option value="vs">Visual Studio</option>
<option value="vs-dark">Visual Studio Dark</option>
<option value="hc-black">High Contrast Black</option>
</select>
</div>
<div style="margin:10px 0;">
<label for="project">Project:</label>
<select id="project" @onchange="ChangeProject">
@foreach (var name in AvailableTemplates)
{
<option value="@name">@name</option>
}
</select>
</div>
@*<FileSystemBrowser FileSystem="FileSystem"
OnChange="FileSystemBrowserOnChange"
/>*@
<div style="margin:10px 0;">
<button @onclick="Compile">Compile</button>
</div>
</div>
<MonacoEditor @ref="_editor"
Id="code-editor"
ConstructionOptions="EditorConstructionOptions"
OnDidInit="EditorOnDidInit"
OnContextMenu="EditorOnContextMenu"
/>
<CompilerOutput CompileResult="@CompileResult"
AssembleResult="@AssembleResult"
FileSystem="@FileSystem" />
@code {
private MonacoEditor _editor { get; set; }
private string ValueToSet { get; set; }
private FrontEndResult CompileResult { get; set; }
private AssemblyResult? AssembleResult { get; set; }
private InMemoryFileSystem FileSystem { get; } = new InMemoryFileSystem();
private IEnumerable<string> AvailableTemplates { get; set; } = Enumerable.Empty<string>();
protected override async Task OnInitializedAsync()
{
AvailableTemplates = await Templates.GetTemplateNamesAsync();
}
private StandaloneEditorConstructionOptions EditorConstructionOptions(MonacoEditor editor)
{
return new StandaloneEditorConstructionOptions
{
Language = "zil",
GlyphMargin = true,
Value = "<VERSION ZIP>\n" +
"\n" +
"<ROUTINE GO ()\n" +
" <TELL \"Hello world!\" CR>\n" +
" <QUIT>>",
};
}
private async Task EditorOnDidInit(MonacoEditorBase editor)
{
await _editor.AddCommand((int)KeyMode.CtrlCmd | (int)KeyCode.KEY_H, (editor, keyCode) =>
{
Console.WriteLine("Ctrl+H : Initial editor command is triggered.");
System.Diagnostics.Debugger.Break();
});
//var newDecorations = new ModelDeltaDecoration[]
//{
// new ModelDeltaDecoration
// {
// Range = new BlazorMonaco.Bridge.Range(3,1,3,1),
// Options = new ModelDecorationOptions
// {
// IsWholeLine = true,
// ClassName = "decorationContentClass",
// GlyphMarginClassName = "decorationGlyphMarginClass"
// }
// }
//};
//decorationIds = await _editor.DeltaDecorations(null, newDecorations);
// You can now use 'decorationIds' to change or remove the decorations
}
//private string[] decorationIds;
private void EditorOnContextMenu(EditorMouseEvent eventArg)
{
Console.WriteLine("OnContextMenu : " + System.Text.Json.JsonSerializer.Serialize(eventArg));
}
private async Task ChangeTheme(ChangeEventArgs e)
{
Console.WriteLine($"setting theme to: {e.Value.ToString()}");
await MonacoEditor.SetTheme(e.Value.ToString());
}
private async Task SetValue()
{
Console.WriteLine($"setting value to: {ValueToSet}");
await _editor.SetValue(ValueToSet);
}
private async Task GetValue()
{
var val = await _editor.GetValue();
Console.WriteLine($"value is: {val}");
}
private async Task AddCommand()
{
await _editor.AddCommand((int)KeyMode.CtrlCmd | (int)KeyCode.Enter, (editor, keyCode) =>
{
Console.WriteLine("Ctrl+Enter : Editor command is triggered.");
});
}
private async Task AddAction()
{
await _editor.AddAction("testAction", "Test Action", new int[] { (int)KeyMode.CtrlCmd | (int)KeyCode.KEY_D, (int)KeyMode.CtrlCmd | (int)KeyCode.KEY_B }, null, null, "navigation", 1.5, (editor, keyCodes) =>
{
Console.WriteLine("Ctrl+D : Editor action is triggered.");
});
}
private async Task Compile()
{
FileSystem.SetText("main.zil", await _editor.GetValue());
var compiler = new FrontEnd { FileSystem = FileSystem, Logger = NullDiagnosticLogger.Instance };
var compResult = compiler.Compile("main.zil", "main.zap", false);
CompileResult = compResult;
AssembleResult = null;
if (compResult.Success)
{
var assembler = new Zapf.ZapfAssembler { FileSystem = FileSystem };
AssembleResult = assembler.Assemble("main.zap", "main.z#");
}
}
private Task ChangeProject(ChangeEventArgs e)
{
return Task.CompletedTask;
//if (e.Value is string name)
//{
// var (template, fileContents) = await Templates.LoadTemplateAsync(name);
// FileSystem.Clear();
// foreach (var (path, content) in fileContents)
// FileSystem.SetText(path, content);
// await _editor.SetValue(fileContents[0].content);
//}
}
}
@page "/project/{ProjectId:guid}/edit"
@inject WorkspaceService Workspace
@inject NavigationManager Nav
@layout ProjectLayout
<h3>Editing @Path</h3>
<textarea @bind="FileContent" class="w-100 h-75" ></textarea>
@code {
public static string GetLink(Project project, string path) => GetLink(project.Guid.ToString(), path);
public static string GetLink(string projectId, string path) =>
$"/project/{projectId}/edit?file={path}";
[Parameter]
public Guid ProjectId { get; set; }
[Parameter]
public string Path { get; set; }
[System.Diagnostics.CodeAnalysis.MaybeNull]
private ProjectFile File => Workspace.Project.Files.SingleOrDefault(f => f.Path == Path);
public string FileContent
{
get => File?.Content;
set
{
var f = File;
if (f != null)
f.Content = value;
}
}
[CascadingParameter]
public ProjectLayout ProjectLayout { get; set; }
protected override void OnParametersSet()
{
var uri = new Uri(Nav.Uri);
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query);
Path = query.GetValueOrDefault("file");
if (Path == null && Workspace.Project.Files.Count > 0)
{
Nav.NavigateTo(GetLink(Workspace.Project, Workspace.Project.MainFile.Path));
return;
}
ProjectLayout.ActivePath = Path;
}
}
@page "/project/new"
@inject TemplateService Templates
@inject NavigationManager Nav
@inject WorkspaceService Workspace
@layout ContainerlessLayout
<div class="px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
<h1 class="display-4">Select template</h1>
<p class="lead">Start a new project without having to write out all the boilerplate, or learn ZIL by hacking on one of the example games.</p>
</div>
<div class="container">
@if (Showcase.Length == 0)
{
<div class="d-flex justify-content-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
}
else
{
<div class="card-deck mb-3 text-center">
@foreach (var (name, template) in Showcase)
{
<ProjectTemplateCard IsDefault="@(name == "$default")" Template="@template" OnClick="@(e => LoadTemplate(name))" />
}
</div>
}
</div>
@code {
private KeyValuePair<string, Template>[] Showcase { get; set; } = { };
protected override async Task OnInitializedAsync()
{
Showcase = await Templates.GetShowcaseAsync();
}