Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/6.0.3xx] Backport Improve console output of dotnet watch ⌚ (#23318) #23400

Merged
merged 5 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions src/BuiltInTools/dotnet-watch/DotNetWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public async Task WatchAsync(DotNetWatchContext context, CancellationToken cance
var args = string.Join(" ", processSpec.Arguments);
_reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

_reporter.Output("Started");
_reporter.Output("Started", emoji: "🚀");

Task<FileItem?> fileSetTask;
Task finishedTask;
Expand Down Expand Up @@ -141,7 +141,7 @@ await _staticFileHandler.TryHandleFileChange(context, fileItem, combinedCancella
// Process exited. Redo evaludation
context.RequiresMSBuildRevaluation = true;
// Now wait for a file to change before restarting process
context.ChangedFile = await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _reporter.Warn("Waiting for a file to change before restarting dotnet..."));
context.ChangedFile = await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _reporter.Warn("Waiting for a file to change before restarting dotnet...", emoji: "⏳"));
}
else
{
Expand Down
4 changes: 2 additions & 2 deletions src/BuiltInTools/dotnet-watch/Filters/DotNetBuildFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async ValueTask ProcessAsync(DotNetWatchContext context, CancellationToke
WorkingDirectory = context.ProcessSpec.WorkingDirectory,
};

_reporter.Output("Building...");
_reporter.Output("Building...", emoji: "🔧");
var exitCode = await _processRunner.RunAsync(processSpec, cancellationToken);
context.FileSet = await _fileSetFactory.CreateAsync(cancellationToken);
if (exitCode == 0)
Expand All @@ -48,7 +48,7 @@ public async ValueTask ProcessAsync(DotNetWatchContext context, CancellationToke

// If the build fails, we'll retry until we have a successful build.
using var fileSetWatcher = new FileSetWatcher(context.FileSet, _reporter);
await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _reporter.Warn("Waiting for a file to change before restarting dotnet..."));
await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _reporter.Warn("Waiting for a file to change before restarting dotnet...", emoji: ""));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ private void OnOutput(object sender, DataReceivedEventArgs eventArgs)
// From emperical observation, it's noted that failing to launch a browser results in either Process.Start returning a null-value
// or for the process to have immediately exited.
// We can use this to provide a helpful message.
_reporter.Output($"Unable to launch the browser. Navigate to {launchUrl}");
_reporter.Output($"Unable to launch the browser. Navigate to {launchUrl}", emoji: "🌐");
}
}
else if (_watchContext?.BrowserRefreshServer is { } browserRefresh)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public async ValueTask<bool> TryHandleFileChange(DotNetWatchContext context, Fil
HotReloadEventSource.Log.HotReloadEnd(HotReloadEventSource.StartType.CompilationHandler);
if (applyState)
{
_reporter.Output($"Hot reload of changes succeeded.");
_reporter.Output($"Hot reload of changes succeeded.", emoji: "🔥");
}

return applyState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ private static async Task<ImmutableArray<string>> GetHotReloadCapabilitiesAsync(
try
{
var capabilities = await hotReloadCapabilitiesTask;
reporter.Verbose($"Hot reload capabilities: {string.Join(" ", capabilities)}.");
reporter.Verbose($"Hot reload capabilities: {string.Join(" ", capabilities)}.", emoji: "🔥");

return capabilities;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ public static HotReloadProfile InferHotReloadProfile(ProjectGraph projectGraph,
// We saw a previous project that was AspNetCore. This must he a blazor hosted app.
if (aspnetCoreProject is not null && aspnetCoreProject != currentNode.ProjectInstance)
{
reporter.Verbose($"HotReloadProfile: BlazorHosted. {aspnetCoreProject.FullPath} references BlazorWebAssembly project {currentNode.ProjectInstance.FullPath}.");
reporter.Verbose($"HotReloadProfile: BlazorHosted. {aspnetCoreProject.FullPath} references BlazorWebAssembly project {currentNode.ProjectInstance.FullPath}.", emoji: "🔥");
return HotReloadProfile.BlazorHosted;
}

reporter.Verbose("HotReloadProfile: BlazorWebAssembly.");
reporter.Verbose("HotReloadProfile: BlazorWebAssembly.", emoji: "🔥");
return HotReloadProfile.BlazorWebAssembly;
}
}
Expand All @@ -50,7 +50,7 @@ public static HotReloadProfile InferHotReloadProfile(ProjectGraph projectGraph,
}
}

reporter.Verbose("HotReloadProfile: Default.");
reporter.Verbose("HotReloadProfile: Default.", emoji: "🔥");
return HotReloadProfile.Default;
}
}
Expand Down
83 changes: 83 additions & 0 deletions src/BuiltInTools/dotnet-watch/HotReload/RudeEditDialog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Tools.Internal;

namespace Microsoft.DotNet.Watcher.Tools
{
public class RudeEditDialog
{
private readonly IReporter _reporter;
private readonly IRequester _requester;
private readonly IConsole _console;
private bool? _restartImmediatelySessionPreference; // Session preference

public RudeEditDialog(IReporter reporter, IRequester requester, IConsole console)
{
_reporter = reporter;
_requester = requester;
_console = console;

var alwaysRestart = Environment.GetEnvironmentVariable("DOTNET_WATCH_RESTART_ON_RUDE_EDIT");

if (alwaysRestart == "1" || string.Equals(alwaysRestart, "true", StringComparison.OrdinalIgnoreCase))
{
_reporter.Verbose($"DOTNET_WATCH_RESTART_ON_RUDE_EDIT = '{alwaysRestart}'. Restarting without prompt.");
_restartImmediatelySessionPreference = true;
}
}

public async Task EvaluateAsync(CancellationToken cancellationToken)
{
if (_restartImmediatelySessionPreference.HasValue)
{
await GetRudeEditResult(_restartImmediatelySessionPreference.Value, cancellationToken);
return;
}

var key = await _requester.GetKeyAsync(
"Do you want to restart your app - Yes (y) / No (n) / Always (a) / Never (v)?",
KeyPressed,
cancellationToken);

switch (key)
{
case ConsoleKey.Escape:
case ConsoleKey.Y:
await GetRudeEditResult(restartImmediately: true, cancellationToken);
return;
case ConsoleKey.N:
await GetRudeEditResult(restartImmediately: false, cancellationToken);
return;
case ConsoleKey.A:
_restartImmediatelySessionPreference = true;
await GetRudeEditResult(restartImmediately: true, cancellationToken);
return;
case ConsoleKey.V:
_restartImmediatelySessionPreference = false;
await GetRudeEditResult(restartImmediately: false, cancellationToken);
return;
}

bool KeyPressed(ConsoleKey key)
{
return key is ConsoleKey.Y or ConsoleKey.N or ConsoleKey.A or ConsoleKey.V;
}
}

private Task GetRudeEditResult(bool restartImmediately, CancellationToken cancellationToken)
{
if (restartImmediately)
{
return Task.CompletedTask;
}

_reporter.Output("Hot reload suspended. To continue hot reload, press \"Ctrl + R\".", emoji: "🔥");

return Task.Delay(-1, cancellationToken);
}
}
}
95 changes: 0 additions & 95 deletions src/BuiltInTools/dotnet-watch/HotReload/RudeEditPreference.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public async ValueTask<bool> TryHandleFileChange(DotNetWatchContext context, Fil
return false;
}
await HandleBrowserRefresh(context.BrowserRefreshServer, file, cancellationToken);
_reporter.Output("Hot reload of scoped css succeeded.");
_reporter.Output("Hot reload of scoped css succeeded.", emoji: "🔥");
HotReloadEventSource.Log.HotReloadEnd(HotReloadEventSource.StartType.ScopedCssHandler);
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public async ValueTask<bool> TryHandleFileChange(DotNetWatchContext context, Fil
_reporter.Verbose($"Handling file change event for static content {file.FilePath}.");
await HandleBrowserRefresh(context.BrowserRefreshServer, file, cancellationToken);
HotReloadEventSource.Log.HotReloadEnd(HotReloadEventSource.StartType.StaticHandler);
_reporter.Output("Hot reload of static file succeeded.");
_reporter.Output("Hot reload of static file succeeded.", emoji: "🔥");
return true;
}

Expand Down
37 changes: 27 additions & 10 deletions src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,35 @@ public class HotReloadDotNetWatcher : IAsyncDisposable
private readonly DotNetWatchOptions _dotNetWatchOptions;
private readonly IWatchFilter[] _filters;
private readonly RudeEditDialog _rudeEditDialog;
private readonly string _workingDirectory;

public HotReloadDotNetWatcher(IReporter reporter, IFileSetFactory fileSetFactory, DotNetWatchOptions dotNetWatchOptions, IConsole console)
public HotReloadDotNetWatcher(IReporter reporter, IRequester requester, IFileSetFactory fileSetFactory, DotNetWatchOptions dotNetWatchOptions, IConsole console, string workingDirectory)
{
Ensure.NotNull(reporter, nameof(reporter));
Ensure.NotNull(requester, nameof(requester));
Ensure.NotNullOrEmpty(workingDirectory, nameof(workingDirectory));

_reporter = reporter;
_processRunner = new ProcessRunner(reporter);
_dotNetWatchOptions = dotNetWatchOptions;
_console = console;
_workingDirectory = workingDirectory;

_filters = new IWatchFilter[]
{
new DotNetBuildFilter(fileSetFactory, _processRunner, _reporter),
new LaunchBrowserFilter(dotNetWatchOptions),
new BrowserRefreshFilter(dotNetWatchOptions, _reporter),
};
_rudeEditDialog = new(reporter, _console);
_rudeEditDialog = new(reporter, requester, _console);
}

public async Task WatchAsync(DotNetWatchContext context, CancellationToken cancellationToken)
{
var processSpec = context.ProcessSpec;

_reporter.Output("Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload. " +
"Press \"Ctrl + R\" to restart.");
" 💡 Press \"Ctrl + R\" to restart.", emoji: "🔥");

var forceReload = new CancellationTokenSource();

Expand Down Expand Up @@ -112,7 +116,7 @@ public async Task WatchAsync(DotNetWatchContext context, CancellationToken cance
var args = string.Join(" ", processSpec.Arguments);
_reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

_reporter.Output("Started");
_reporter.Output("Started", emoji: "🚀");

Task<FileItem[]> fileSetTask;
Task finishedTask;
Expand All @@ -131,7 +135,7 @@ public async Task WatchAsync(DotNetWatchContext context, CancellationToken cance
{
if (MayRequireRecompilation(context, fileItems) is { } newFile)
{
_reporter.Output($"New file: {newFile.FilePath}. Rebuilding the application.");
_reporter.Output($"New file: {GetRelativeFilePath(newFile.FilePath)}. Rebuilding the application.");
break;
}
else if (fileItems.All(f => f.IsNewFile))
Expand All @@ -149,17 +153,17 @@ public async Task WatchAsync(DotNetWatchContext context, CancellationToken cance

if (fileItems.Length == 1)
{
_reporter.Output($"File changed: {fileItems[0].FilePath}.");
_reporter.Output($"File changed: {GetRelativeFilePath(fileItems[0].FilePath)}.");
}
else
{
_reporter.Output($"Files changed: {string.Join(", ", fileItems.Select(f => f.FilePath))}");
_reporter.Output($"Files changed: {string.Join(", ", fileItems.Select(f => GetRelativeFilePath(f.FilePath)))}");
}
var start = Stopwatch.GetTimestamp();
if (await hotReload.TryHandleFileChange(context, fileItems, combinedCancellationSource.Token))
{
var totalTime = TimeSpan.FromTicks(Stopwatch.GetTimestamp() - start);
_reporter.Verbose($"Hot reload change handled in {totalTime.TotalMilliseconds}ms.");
_reporter.Verbose($"Hot reload change handled in {totalTime.TotalMilliseconds}ms.", emoji: "🔥");
}
else
{
Expand Down Expand Up @@ -190,7 +194,7 @@ public async Task WatchAsync(DotNetWatchContext context, CancellationToken cance
if (finishedTask == processTask)
{
// Now wait for a file to change before restarting process
_reporter.Warn("Waiting for a file to change before restarting dotnet...");
_reporter.Warn("Waiting for a file to change before restarting dotnet...", emoji: "⏳");
await fileSetWatcher.GetChangedFileAsync(cancellationToken, forceWaitForNewUpdate: true);
}
else
Expand All @@ -209,7 +213,7 @@ public async Task WatchAsync(DotNetWatchContext context, CancellationToken cance
if (forceReload.IsCancellationRequested)
{
_console.Clear();
_reporter.Output("Restart requested.");
_reporter.Output("Restart requested.", emoji: "🔄");
}
}
}
Expand Down Expand Up @@ -294,6 +298,19 @@ private static void ConfigureExecutable(DotNetWatchContext context, ProcessSpec
}
}

private string GetRelativeFilePath(string path)
{
var relativePath = path;
if (path.StartsWith(_workingDirectory, StringComparison.Ordinal) && path.Length > _workingDirectory.Length)
{
relativePath = path.Substring(_workingDirectory.Length);

return $".{(relativePath.StartsWith(Path.DirectorySeparatorChar) ? string.Empty : Path.DirectorySeparatorChar)}{relativePath}";
}

return relativePath;
}

public async ValueTask DisposeAsync()
{
foreach (var filter in _filters)
Expand Down
Loading