Skip to content
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
3 changes: 3 additions & 0 deletions .globalconfig
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ dotnet_diagnostic.IDE0059.severity = warning

# IDE0060: Remove unused parameter
dotnet_diagnostic.IDE0060.severity = warning

# IDE0380: Remove unnecessary 'unsafe' modifier
dotnet_diagnostic.IDE0380.severity = warning
11 changes: 9 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
}
},
"isBackground": true,
"problemMatcher": "$msCompile"
"problemMatcher": "$msCompile",
"group": {
"kind": "build"
}
},
{
"label": "wasm: watch",
Expand All @@ -41,7 +44,11 @@
}
},
"isBackground": true,
"problemMatcher": "$msCompile"
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
6 changes: 3 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
<NoWarn>$(NoWarn);NU1507</NoWarn>
<NetCoreVersion>11.0.0-preview.5.26302.115</NetCoreVersion>
<AspNetCoreVersion>$(NetCoreVersion)</AspNetCoreVersion>
<RoslynBuild>26309.3</RoslynBuild>
<RoslynVersion>5.9.0-1.$(RoslynBuild)</RoslynVersion>
<RazorVersion>10.4.0-preview.$(RoslynBuild)</RazorVersion>
<RoslynBuild>26351.2</RoslynBuild>
<RoslynVersion>5.10.0-1.$(RoslynBuild)</RoslynVersion>
<RazorVersion>11.1.0-preview.$(RoslynBuild)</RazorVersion>
Comment thread
jjonescz marked this conversation as resolved.
<FluentUIVersion>4.14.2</FluentUIVersion>
<NuGetVersion>7.6.0</NuGetVersion>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/App/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace DotNetLab;
public partial class App
{
public const string Domain = "lab.razor.fyi";
public const string DesktopAppLink = "https://apps.microsoft.com/detail/9PCPMM329DZT";
public const string NativeAppsLink = "https://github.com/jjonescz/DotNetLab/blob/main/docs/native-apps.md";

public static void RegisterRootComponents(Action<Type, string> adder)
{
Expand Down
29 changes: 27 additions & 2 deletions src/App/Lab/Page.razor
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@
}

<div>
@* Keyboard button *@
<FluentButton OnClick="ToggleInputVirtualKeyboardAsync"
Appearance="@(disableInputVirtualKeyboard ? Appearance.Accent : Appearance.Neutral)"
Title="@(disableInputVirtualKeyboard ? "Enable the on-screen keyboard for the input editor" : "Disable the on-screen keyboard for the input editor")">
<FluentIcon Value="@(new Icons.Regular.Size20.Keyboard())" Color="Color.Neutral" />
</FluentButton>

@* Orientation button *@
@if (orientation == Orientation.Horizontal)
{
Expand Down Expand Up @@ -321,7 +328,7 @@
{
<FluentMessageBar Intent="MessageIntent.Info" AllowDismiss="false">
JIT disassembler is not available on this platform
(it's only available in the <a href="@App.DesktopAppLink" target="_blank" rel="noopener noreferrer">desktop app</a>).<br/>
(it's only available in a <a href="@App.NativeAppsLink" target="_blank" rel="noopener noreferrer">native app</a>).<br/>
Displaying cached output@(compiled?.CacheInfo?.Timestamp is { } cacheTimestamp ? $" ({FormatCacheTimestamp(cacheTimestamp)})" : "").
</FluentMessageBar>
}
Expand Down Expand Up @@ -386,6 +393,7 @@
private bool useVim;
private bool enableMemoryUsageView;
private bool displayHintSquiggles;
private bool disableInputVirtualKeyboard;
private bool showRenderedHtml;
private Orientation orientation;
private string? workerError;
Expand Down Expand Up @@ -533,6 +541,15 @@
Worker.Failed += OnWorkerFailed;

displayHintSquiggles = await LocalStorage.TryLoadOptionAsync(nameof(displayHintSquiggles), defaultValue: false);
disableInputVirtualKeyboard = await LocalStorage.TryLoadOptionAsync(nameof(disableInputVirtualKeyboard), defaultValue: false);

module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/DotNetLab.App/Lab/Page.razor.js");
await module.InvokeVoidAsync("setVirtualKeyboardDisabled", outputEditor.Id, true);

if (disableInputVirtualKeyboard)
{
await module.InvokeVoidAsync("setVirtualKeyboardDisabled", inputEditor.Id, true);
}
}

private async Task EditorInitAsync()
Expand All @@ -553,7 +570,6 @@
await RegisterWordWrapActionAsync();
await RegisterFormatActionAsync();
await RegisterSquigglyHintActionAsync();
module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/DotNetLab.App/Lab/Page.razor.js");
dotNetObjectReference = DotNetObjectReference.Create(this);
unregisterEventListeners = await module.InvokeAsync<Action>("registerEventListeners", dotNetObjectReference);

Expand Down Expand Up @@ -586,6 +602,14 @@
}
}

private async Task ToggleInputVirtualKeyboardAsync()
{
disableInputVirtualKeyboard = !disableInputVirtualKeyboard;
StateHasChanged();
await LocalStorage.SetItemAsync(nameof(disableInputVirtualKeyboard), disableInputVirtualKeyboard);
await module.InvokeVoidAsync("setVirtualKeyboardDisabled", inputEditor.Id, disableInputVirtualKeyboard);
}

async ValueTask IAsyncDisposable.DisposeAsync()
{
NavigationManager.LocationChanged -= OnLocationChanged;
Expand All @@ -611,6 +635,7 @@
{
try
{
await module.InvokeVoidAsync("dispose");
await module.DisposeAsync();
}
catch (JSDisconnectedException) { }
Expand Down
69 changes: 69 additions & 0 deletions src/App/Lab/Page.razor.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,75 @@ export function restoreMonacoEditorViewState(editorId, state) {
blazorMonaco.editor.getEditor(editorId)?.restoreViewState(state);
}

/** @type {Map<string, MutationObserver[]>} */
const virtualKeyboardObservers = new Map();

export function setVirtualKeyboardDisabled(editorId, disabled) {
virtualKeyboardObservers.get(editorId)?.forEach((o) => o.disconnect());
virtualKeyboardObservers.delete(editorId);

const root = document.getElementById(editorId);
if (!root) {
console.warn(`setVirtualKeyboardDisabled: could not find editor container #${editorId}`);
return;
}

if (!disabled) {
root.querySelector('.native-edit-context')?.removeAttribute('inputmode');
return;
}

const observers = [];
virtualKeyboardObservers.set(editorId, observers);

let current = null;
let currentAttrObserver = null;

const ensureInputMode = (el) => {
if (el.getAttribute('inputmode') !== 'none') {
el.setAttribute('inputmode', 'none');
}
};

const attach = () => {
const el = root.querySelector('.native-edit-context');

// Skip early if the element is missing or unchanged.
if (!el || el === current) {
return;
}
current = el;

// Stop watching the previous (now replaced) element.
if (currentAttrObserver) {
currentAttrObserver.disconnect();
observers.splice(observers.indexOf(currentAttrObserver), 1);
}

ensureInputMode(el);

// Re-apply if Monaco clears `inputmode` on this element (cheap: one attribute).
currentAttrObserver = new MutationObserver(() => ensureInputMode(el));
currentAttrObserver.observe(el, { attributes: true, attributeFilter: ['inputmode'] });
observers.push(currentAttrObserver);
};

// Watch only for the input element being (re)created.
const rootObserver = new MutationObserver(attach);
rootObserver.observe(root, { childList: true, subtree: true });
observers.push(rootObserver);

attach();
}

export function dispose() {
for (const observers of virtualKeyboardObservers.values()) {
observers.forEach((o) => o.disconnect());
}

virtualKeyboardObservers.clear();
}

export function copyUrlToClipboard(urlPrefix) {
navigator.clipboard.writeText(urlPrefix ? `${urlPrefix}${location.hash}` : location.href);
}
Expand Down
15 changes: 15 additions & 0 deletions src/Compiler/TreeFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,21 @@ static TextSpan getSpan(object obj, Type type)
return (TextSpan)type.GetProperty(nameof(SyntaxList<>.Span))!.GetValue(obj)!;
}

if (RoslynAccessors.TryGetBoundNodeSyntax(obj, out var syntax))
{
return syntax.Span;
}

if (RoslynAccessors.TryGetInternalSymbolSyntax(obj, out syntax))
{
return syntax.Span;
}

if (obj is ISymbol { DeclaringSyntaxReferences: [{ } syntaxRef, ..] })
{
return syntaxRef.GetSyntax().Span;
}

return default;
}

Expand Down
24 changes: 24 additions & 0 deletions src/RoslynAccess/RoslynAccessors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@ public static IEnumerable<string> GetFeatureNames()
.Select(f => (string)f.GetRawConstantValue()!);
}

public static bool TryGetBoundNodeSyntax(object o, [NotNullWhen(returnValue: true)] out SyntaxNode? syntax)
{
if (o is BoundNode boundNode)
{
syntax = boundNode.Syntax;
return true;
}

syntax = null;
return false;
}

public static bool TryGetInternalSymbolEquality(object? x, object? y, out bool areEqual)
{
if (x is ISymbolInternal xSymbol && y is ISymbolInternal ySymbol)
Expand Down Expand Up @@ -184,6 +196,18 @@ public static bool TryGetInternalSymbolName(object o, [NotNullWhen(returnValue:
return false;
}

public static bool TryGetInternalSymbolSyntax(object o, [NotNullWhen(returnValue: true)] out SyntaxNode? syntax)
{
if (o is Symbol { DeclaringSyntaxReferences: [{ } syntaxRef, ..] })
{
syntax = syntaxRef.GetSyntax();
return true;
}

syntax = null;
return false;
}

public static Func<object>? TryGetSourceInternalSymbolGetMembers(object o)
{
if (o is NamespaceOrTypeSymbol s && s.DeclaringCompilation != null)
Expand Down
47 changes: 47 additions & 0 deletions src/Shared/Attributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace DotNetLab;

[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public sealed class GenericArgumentsAttribute(params Type[] genericArguments) : Attribute
{
public Type[] GenericArguments { get; } = genericArguments;
}

[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public sealed class MethodInstantiationAttribute : Attribute
{
public MethodInstantiationAttribute(string typeName, string methodName, Type[]? genericTypeArguments, Type[]? genericMethodArguments)
{
TypeName = typeName;
MethodName = methodName;
GenericTypeArguments = genericTypeArguments ?? Type.EmptyTypes;
GenericMethodArguments = genericMethodArguments ?? Type.EmptyTypes;
}

public MethodInstantiationAttribute(string typeName, string methodName, string[]? genericTypeArguments, string[]? genericMethodArguments)
{
TypeName = typeName;
MethodName = methodName;
GenericTypeArguments = (genericTypeArguments ?? [])
.Select(Type.GetType).Where(t => t != null).ToArray()!;
GenericMethodArguments = (genericMethodArguments ?? [])
.Select(Type.GetType).Where(t => t != null).ToArray()!;
Comment thread
jjonescz marked this conversation as resolved.
}

public MethodInstantiationAttribute(string methodName, Type[]? genericMethodArguments)
{
MethodName = methodName;
GenericMethodArguments = genericMethodArguments ?? Type.EmptyTypes;
}

public MethodInstantiationAttribute(string methodName, string[]? genericMethodArguments)
{
MethodName = methodName;
GenericMethodArguments = (genericMethodArguments ?? [])
.Select(Type.GetType).Where(t => t != null).ToArray()!;
Comment thread
jjonescz marked this conversation as resolved.
}

public string? TypeName { get; }
public string MethodName { get; }
public Type[] GenericTypeArguments { get; } = Type.EmptyTypes;
public Type[] GenericMethodArguments { get; } = Type.EmptyTypes;
}
4 changes: 2 additions & 2 deletions src/WebAssembly/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ file sealed class WebAssemblyAppHostEnvironment(IWebAssemblyHostEnvironment webA

public DesktopAppLink DesktopAppLink { get; } = new()
{
Url = "https://github.com/jjonescz/DotNetLab/blob/main/docs/native-apps.md",
Url = App.NativeAppsLink,
Title = "Native apps available",
Description = "Faster version of .NET Lab running on full .NET.",
Comment thread
jjonescz marked this conversation as resolved.
};
Expand Down Expand Up @@ -76,7 +76,7 @@ public string GetText(
language = null;
return $"""
JIT disassembler is not available on this platform.
Please use the desktop app instead ({App.DesktopAppLink}).
Please use a native app instead ({App.NativeAppsLink}).

""";
}
Expand Down
4 changes: 2 additions & 2 deletions src/WebAssembly/WebAssembly.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
<IndexHtmlLoadingInfoFooter>
<div style="margin-top: 1em">
Consider using more capable and performant
<a href="https://apps.microsoft.com/detail/9PCPMM329DZT" target="_blank" rel="noopener noreferrer">
<a href="https://apps.microsoft.com/detail/9PCPMM329DZT" target="_blank" rel="noopener noreferrer" style="white-space: nowrap">
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 1794.1 1782.3" style="height: 1em; margin: 0 0.25em 0 0.25em">
<path d="M1380.2 451.5v-40c0-108.6-51.7-210.6-139.2-274.9 44.5 59 68.5 130.9 68.4 204.8v110.1h-71V341.4C1238.5 152.9 1085.6 0 897.1 0S555.6 152.9 555.6 341.4v110.1H0v1200.7c0 71.8 58.2 130.1 130.1 130.1h1534c71.8 0 130.1-58.2 130.1-130.1V451.5h-414zM626.4 341.4c-.1-149.4 121-270.6 270.5-270.6h.2c148.1 3.2 267.4 122.5 270.7 270.7v110.1H768.1v-40c-.1-149.4 121-270.6 270.5-270.7h.2c12.5 0 24.9.9 37.2 2.8-33.9-15.6-70.7-23.7-108-23.8-87.5-.1-169.7 42.3-220.4 113.6-32.9 53.6-50.3 115.2-50.2 178v40h-71V341.4z" fill="#e8e8e8"/>
<rect x="520.9" y="740.5" width="347.2" height="347.3" fill="#f25022"/>
<rect x="926" y="740.5" width="347.2" height="347.3" fill="#7fba00"/>
<rect x="520.9" y="1145.6" width="347.2" height="347.2" fill="#00a4ef"/>
<rect x="926" y="1145.6" width="347.2" height="347.2" fill="#ffb900"/>
</svg>Windows</a> or
<a href="https://play.google.com/store/apps/details?id=me.janjones.dotnetlab" target="_blank" rel="noopener noreferrer">
<a href="https://play.google.com/store/apps/details?id=me.janjones.dotnetlab" target="_blank" rel="noopener noreferrer" style="white-space: nowrap">
<svg viewBox="0 0 28.99 31.99" xmlns="http://www.w3.org/2000/svg" style="height: 1em; margin: 0 0.25em 0 0.25em">
<g data-name="Capa 2"><g data-name="Capa 1"><path d="M13.54 15.28.12 29.34a3.66 3.66 0 0 0 5.33 2.16l15.1-8.6Z" style="fill:#ea4335"/>
<path d="m27.11 12.89-6.53-3.74-7.35 6.45 7.38 7.28 6.48-3.7a3.54 3.54 0 0 0 1.5-4.79 3.62 3.62 0 0 0-1.5-1.5z" style="fill:#fbbc04"/>
Expand Down
Loading