Skip to content

Commit 6044030

Browse files
authored
Add button to copy current URL (#93)
2 parents 227d2e6 + 77836b4 commit 6044030

15 files changed

Lines changed: 135 additions & 40 deletions

.globalconfig

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
# CA2012: Use ValueTasks correctly
1+
# Roslyn.Diagnostics.Analyzers
2+
dotnet_analyzer_diagnostic.category-ApiDesign.severity = none
3+
dotnet_analyzer_diagnostic.category-RoslynDiagnosticsMaintainability.severity = suggestion
4+
5+
# CA2012: Use ValueTasks correctly
26
dotnet_diagnostic.CA2012.severity = warning
37

48
# CA2016: Forward the 'CancellationToken' parameter to methods

Directory.Build.props

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,17 @@
2222
-->
2323
<PublishTrimmed>false</PublishTrimmed>
2424
<UsingBrowserRuntimeWorkload>false</UsingBrowserRuntimeWorkload>
25-
26-
<!-- Workaround a bug in the compiler toolset package (trying to load tasks\net472\../bincore\csc.dll). -->
27-
<RoslynCompilerType Condition="'$(MSBuildRuntimeType)' == 'Full'">Framework</RoslynCompilerType>
2825
</PropertyGroup>
2926

3027
<ItemGroup>
3128
<PackageReference Include="Microsoft.Net.Compilers.Toolset">
3229
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
3330
<PrivateAssets>all</PrivateAssets>
3431
</PackageReference>
32+
<PackageReference Include="Roslyn.Diagnostics.Analyzers">
33+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
34+
<PrivateAssets>all</PrivateAssets>
35+
</PackageReference>
3536
</ItemGroup>
3637

3738
<ItemGroup>

Directory.Packages.props

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
55
<!-- We are not using package source mapping. -->
66
<NoWarn>$(NoWarn);NU1507</NoWarn>
7-
<NetCoreVersion>10.0.0-preview.5.25277.114</NetCoreVersion>
7+
<NetCoreVersion>10.0.0-preview.7.25380.108</NetCoreVersion>
88
<AspNetCoreVersion>$(NetCoreVersion)</AspNetCoreVersion>
9-
<RoslynVersion>5.0.0-1.25357.1</RoslynVersion>
10-
<FluentUIVersion>4.12.0</FluentUIVersion>
9+
<RoslynVersion>5.0.0-2.25451.1</RoslynVersion>
10+
<FluentUIVersion>4.12.1</FluentUIVersion>
1111
<NuGetVersion>6.14.0</NuGetVersion>
1212
</PropertyGroup>
1313
<ItemGroup>
14-
<PackageVersion Include="AwesomeAssertions" Version="9.0.0" />
14+
<PackageVersion Include="AwesomeAssertions" Version="9.1.0" />
1515
<PackageVersion Include="Blazored.LocalStorage" Version="4.5.0" />
1616
<!-- When upating BlazorMonaco, check that VIM mode works. See https://github.com/jjonescz/DotNetLab/pull/57. -->
1717
<PackageVersion Include="BlazorMonaco" Version="3.2.0" />
1818
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
19-
<PackageVersion Include="ICSharpCode.Decompiler" Version="9.1.0.7988" />
19+
<PackageVersion Include="ICSharpCode.Decompiler" Version="10.0.0.8079-preview1" />
2020
<PackageVersion Include="Knapcode.MiniZip" Version="0.21.1" />
2121
<PackageVersion Include="MetadataReferenceService.BlazorWasm" Version="0.0.1" />
2222
<PackageVersion Include="Microsoft.AspNetCore.App.Ref" Version="$(AspNetCoreVersion)" />
@@ -32,19 +32,21 @@
3232
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(NetCoreVersion)" />
3333
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components" Version="$(FluentUIVersion)" />
3434
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="$(FluentUIVersion)" />
35-
<PackageVersion Include="Microsoft.Net.Compilers.Razor.Toolset" Version="10.0.0-preview.25353.4" />
35+
<PackageVersion Include="Microsoft.Net.Compilers.Razor.Toolset" Version="10.0.0-preview.25429.2" />
3636
<PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="$(RoslynVersion)" />
3737
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
3838
<PackageVersion Include="Microsoft.NETCore.App.Ref" Version="$(NetCoreVersion)" />
3939
<PackageVersion Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
4040
<PackageVersion Include="NuGet.Resolver" Version="$(NuGetVersion)" />
4141
<PackageVersion Include="NuGet.Versioning" Version="$(NuGetVersion)" />
42-
<PackageVersion Include="protobuf-net" Version="3.2.52" />
42+
<PackageVersion Include="protobuf-net" Version="3.2.56" />
43+
<PackageVersion Include="Roslyn.Diagnostics.Analyzers" Version="$(RoslynVersion)" />
4344
<PackageVersion Include="System.IO.Hashing" Version="$(NetCoreVersion)" />
4445
<PackageVersion Include="Xunit.Combinatorial" Version="2.0.24" />
45-
<PackageVersion Include="xunit.v3" Version="2.0.3" />
46-
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.1" />
47-
<!-- Pinned transitive dependencies -->
46+
<PackageVersion Include="xunit.v3" Version="3.0.1" />
47+
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
48+
</ItemGroup>
49+
<ItemGroup Label="Pinned Transitive Dependencies">
4850
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
4951
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="$(NetCoreVersion)" />
5052
</ItemGroup>

global.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"sdk": {
3-
"version": "10.0.100-preview.5.25277.114",
4-
"workloadVersion": "10.0.100-preview.5.25306.3"
3+
"version": "10.0.100-preview.7.25380.108",
4+
"workloadVersion": "10.0.100-preview.7.25411.1"
55
}
66
}

src/App/Lab/InitialCode.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ public class Customer
8080

8181
// IMPORTANT: Keep in sync with `Compiler.Compile`.
8282
public static readonly InitialCode Configuration = new("Configuration.cs", """
83+
// 💡 TIP: Instead of this, you can use directives in C# files directly, for example:
84+
// #:property Configuration=Debug
85+
8386
Config.CSharpParseOptions(options => options
8487
.WithLanguageVersion(LanguageVersion.Preview)
8588
.WithPreprocessorSymbols("DEBUG")

src/App/Lab/Page.razor

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
</div>
2626

2727
<div>
28+
@* Copy URL button *@
29+
<FluentButton OnClick="() => CopyUrlToClipboardAsync()" Title="Copy URL to clipboard (Ctrl+;)"
30+
IconStart="@(urlCopied ? new Icons.Regular.Size20.Checkmark() : new Icons.Regular.Size20.ClipboardLink())" />
31+
2832
@* Compile button *@
2933
<FluentButton Appearance="Appearance.Accent" OnClick="() => CompileAsync()" Loading="compilationInProgress"
3034
Title="Compile (Ctrl+S)" IconStart="@(new Icons.Regular.Size20.FlashPlay())" Disabled="!initialized">
@@ -47,7 +51,7 @@
4751
<div style="flex-grow: 1">
4852
<div style="flex-grow: 1; min-width: 10rem">
4953
@* Outdated output info *@
50-
@if (outputOutdated)
54+
@if (IsOutputOutdated)
5155
{
5256
<FluentStack title="Output is outdated, click Compile or press Ctrl+S"
5357
Style="font-size: 0.8rem; font-style: normal; width: auto"
@@ -57,7 +61,7 @@
5761
</FluentStack>
5862
}
5963
@* Cached info *@
60-
else if (compiled is (_, _, { } cacheInfo))
64+
else if (compiled is { CacheInfo: { } cacheInfo })
6165
{
6266
var title = cacheInfo.Timestamp.HasValue
6367
? "The currently displayed output has been fetched from a server cache."
@@ -234,10 +238,12 @@
234238
private Input? currentInput;
235239
private EditorState? currentOutput;
236240
private string? selectedOutputType;
241+
private int urlCopiedToken;
242+
private bool urlCopied;
237243
private bool compilationInProgress;
238-
private bool outputOutdated;
244+
private DateTimeOffset inputChanged;
239245
private bool outputLoading;
240-
private (CompilationInput Input, CompiledAssembly Output, CacheInfo? CacheInfo)? compiled;
246+
private (CompilationInput Input, CompiledAssembly Output, CacheInfo? CacheInfo, DateTimeOffset Start, DateTimeOffset End)? compiled;
241247
private Settings settings = null!;
242248
private bool wordWrap;
243249
private bool useVim;
@@ -317,6 +323,14 @@
317323
get => AllOutputs.Select(o => o.Type).JoinToString(",");
318324
}
319325

326+
/// <remarks>
327+
/// We don't check equality of last compiled input and current input
328+
/// because we might not have current input fully loaded
329+
/// (parts can live only in the editor until Compile is clicked).
330+
/// </remarks>
331+
private bool IsOutputOutdated
332+
=> compiled is not { Start: var compileStart } || compileStart < inputChanged;
333+
320334
protected override void OnInitialized()
321335
{
322336
NavigationManager.LocationChanged += OnLocationChanged;
@@ -379,6 +393,24 @@
379393
StateHasChanged();
380394
}
381395

396+
[JSInvokable]
397+
public async Task CopyUrlToClipboardAsync()
398+
{
399+
await SaveStateToUrlAsync();
400+
await module.InvokeVoidAsync("copyUrlToClipboard");
401+
urlCopied = true;
402+
StateHasChanged();
403+
var token = Interlocked.Increment(ref urlCopiedToken);
404+
_ = Task.Delay(TimeSpan.FromSeconds(2)).ContinueWith(_ =>
405+
{
406+
if (token == urlCopiedToken)
407+
{
408+
urlCopied = false;
409+
StateHasChanged();
410+
}
411+
});
412+
}
413+
382414
private StandaloneEditorConstructionOptions InputConstructionOptions(StandaloneCodeEditor editor)
383415
{
384416
return EditorConstructionOptions(editor, output: false);
@@ -656,10 +688,10 @@
656688
var state = await SaveStateToUrlAsync();
657689

658690
// Compile.
691+
var startTime = DateTimeOffset.Now;
659692
var input = state.ToCompilationInput();
660693
var output = await Worker.CompileAsync(input, languageServicesEnabled: LanguageServices.Enabled);
661-
compiled = (input, output, null);
662-
outputOutdated = false;
694+
compiled = (input, output, CacheInfo: null, Start: startTime, End: DateTimeOffset.Now);
663695

664696
await DisplaySquigglesAsync();
665697

@@ -696,8 +728,17 @@
696728

697729
public async Task DisplaySquigglesAsync()
698730
{
699-
// Prevent possibly delayed live diagnostics from a previous document version from overwriting the compiler diagnostics.
700-
LanguageServices.CancelDiagnostics();
731+
if (LanguageServices.Enabled)
732+
{
733+
if (IsOutputOutdated)
734+
{
735+
// Avoid overwriting live diagnostics which are newer.
736+
return;
737+
}
738+
739+
// Prevent possibly delayed live diagnostics from a previous document version from overwriting the compiler diagnostics.
740+
LanguageServices.CancelDiagnostics();
741+
}
701742

702743
foreach (var input in inputs)
703744
{
@@ -1007,13 +1048,13 @@
10071048
private async Task TrySetCompiledFromCacheAsync(CompilationInput input, CompiledAssembly output, CacheInfo info, bool updateOutput)
10081049
{
10091050
// If the local compilation finishes before the cache is loaded, don't overwrite it.
1010-
if (compiled is (var existingInput, _, null) && Equals(input, existingInput))
1051+
if (compiled is { Input: var existingInput, CacheInfo: null } && Equals(input, existingInput))
10111052
{
10121053
return;
10131054
}
10141055

1015-
compiled = (input, output, info);
1016-
outputOutdated = false;
1056+
var timestamp = DateTimeOffset.Now;
1057+
compiled = (input, output, info, Start: timestamp, End: timestamp);
10171058
StateHasChanged();
10181059
await DisplaySquigglesAsync();
10191060

@@ -1055,7 +1096,7 @@
10551096
/// </summary>
10561097
public Task OnWorkspaceChangedAsync()
10571098
{
1058-
outputOutdated = true;
1099+
inputChanged = DateTimeOffset.Now;
10591100

10601101
if (!LanguageServices.Enabled)
10611102
{
@@ -1094,7 +1135,7 @@
10941135

10951136
private void OnDidChangeModelContent(ModelContentChangedEvent args)
10961137
{
1097-
outputOutdated = true;
1138+
inputChanged = DateTimeOffset.Now;
10981139
LanguageServices.OnDidChangeModelContent(args);
10991140
}
11001141

@@ -1111,7 +1152,7 @@
11111152

11121153
public void OnSettingsInputChanged()
11131154
{
1114-
outputOutdated = true;
1155+
inputChanged = DateTimeOffset.Now;
11151156
StateHasChanged();
11161157
}
11171158

src/App/Lab/Page.razor.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
if (e.ctrlKey && e.key === 's') {
44
e.preventDefault();
55
dotNetObj.invokeMethodAsync('CompileAndRenderAsync');
6+
} else if (e.ctrlKey && e.key === ';') {
7+
e.preventDefault();
8+
9+
// Instead of just copying the URL directly in JavaScript,
10+
// invoke the.NET method so the URL is updated to reflect the current state and
11+
// the UI displays "copied" checkmark afterwards.
12+
dotNetObj.invokeMethodAsync('CopyUrlToClipboardAsync');
613
}
714
};
815

@@ -21,3 +28,7 @@ export function saveMonacoEditorViewState(editorId) {
2128
export function restoreMonacoEditorViewState(editorId, state) {
2229
blazorMonaco.editor.getEditor(editorId)?.restoreViewState(state);
2330
}
31+
32+
export function copyUrlToClipboard() {
33+
navigator.clipboard.writeText(window.location.href);
34+
}

src/App/Lab/SavedState.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,10 @@ _ when WellKnownSlugs.ShorthandToState.TryGetValue(slug, out var wellKnownState)
106106

107107
{
108108
// Unset cache info (the loaded state might not be cached).
109-
if (compiled is (var input, var output, { }))
109+
if (compiled is { Input: var input, Output: var output, CacheInfo: { } })
110110
{
111-
compiled = (input, output, null);
111+
var timestamp = DateTimeOffset.Now;
112+
compiled = (input, output, CacheInfo: null, Start: timestamp, End: timestamp);
112113
}
113114
}
114115

src/App/Lab/TemplateCache.cs

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

src/App/Utils/Monaco/CustomMonacoTheme.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ public static async Task DefineAsync(IJSRuntime jsRuntime)
1414
{
1515
Base = BuiltInMonacoTheme.Light,
1616
Inherit = true,
17-
Colors = [],
17+
Colors = new()
18+
{
19+
// Avoid colorizing unmatched brackets as red (which overrides even semantic colors which are more correct).
20+
{ "editorBracketHighlight.unexpectedBracket.foreground", "#222222" }, // same as "punctuation"
21+
},
1822
Rules =
1923
[
2024
new() { Token = "comment", Foreground = "008000" },
@@ -100,7 +104,11 @@ public static async Task DefineAsync(IJSRuntime jsRuntime)
100104
{
101105
Base = BuiltInMonacoTheme.Dark,
102106
Inherit = true,
103-
Colors = [],
107+
Colors = new()
108+
{
109+
// Avoid colorizing unmatched brackets as red (which overrides even semantic colors which are more correct).
110+
{ "editorBracketHighlight.unexpectedBracket.foreground", "#d4d4d4" }, // same as "punctuation"
111+
},
104112
Rules =
105113
[
106114
new() { Token = "comment", Foreground = "6a9955" },

0 commit comments

Comments
 (0)