[Peek]Add wrap and formatting options for Monaco previewer (#29378)

* add options for monaco previewer

* fix formatting
This commit is contained in:
Davide Giacometti
2023-10-24 15:32:35 +02:00
committed by GitHub
parent 9693fd7035
commit 5a06bcb473
14 changed files with 313 additions and 24 deletions

View File

@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
namespace Peek.Common.Extensions
{
public static class ApplicationExtensions
{
/// <summary>
/// Get registered services at the application level from anywhere
public static T GetService<T>(this Application application)
where T : class
{
return (application as IApp)!.GetService<T>();
}
}
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Peek.Common
{
public interface IApp
{
public T GetService<T>()
where T : class;
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Peek.FilePreviewer.Models
{
public interface IPreviewSettings
{
public bool SourceCodeWrapText { get; }
public bool SourceCodeTryFormat { get; }
}
}

View File

@@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Abstractions;
using System.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Settings.UI.Library;
namespace Peek.FilePreviewer.Models
{
public class PreviewSettings : IPreviewSettings
{
private const int MaxNumberOfRetry = 5;
private readonly ISettingsUtils _settingsUtils;
private readonly IFileSystemWatcher _watcher;
private readonly object _loadingSettingsLock = new();
public bool SourceCodeWrapText { get; private set; }
public bool SourceCodeTryFormat { get; private set; }
public PreviewSettings()
{
_settingsUtils = new SettingsUtils();
SourceCodeWrapText = false;
SourceCodeTryFormat = false;
LoadSettingsFromJson();
_watcher = Helper.GetFileWatcher(PeekSettings.ModuleName, PeekPreviewSettings.FileName, () => LoadSettingsFromJson());
}
private void LoadSettingsFromJson()
{
lock (_loadingSettingsLock)
{
var retry = true;
var retryCount = 0;
while (retry)
{
try
{
retryCount++;
if (!_settingsUtils.SettingsExists(PeekSettings.ModuleName, PeekPreviewSettings.FileName))
{
Logger.LogInfo("Peek preview-settings.json was missing, creating a new one");
var defaultSettings = new PeekPreviewSettings();
_settingsUtils.SaveSettings(defaultSettings.ToJsonString(), PeekSettings.ModuleName, PeekPreviewSettings.FileName);
}
var settings = _settingsUtils.GetSettingsOrDefault<PeekPreviewSettings>(PeekSettings.ModuleName, PeekPreviewSettings.FileName);
if (settings != null)
{
SourceCodeWrapText = settings.SourceCodeWrapText.Value;
SourceCodeTryFormat = settings.SourceCodeTryFormat.Value;
}
retry = false;
}
catch (IOException e)
{
if (retryCount > MaxNumberOfRetry)
{
retry = false;
Logger.LogError($"Failed to deserialize preview settings, Retrying {e.Message}", e);
}
else
{
Thread.Sleep(500);
}
}
catch (Exception ex)
{
retry = false;
Logger.LogError("Failed to read changed preview settings", ex);
}
}
}
}
}
}

View File

@@ -3,7 +3,10 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml;
using Peek.Common.Extensions;
using Peek.Common.Models; using Peek.Common.Models;
using Peek.FilePreviewer.Models;
using Peek.FilePreviewer.Previewers.Archives; using Peek.FilePreviewer.Previewers.Archives;
using Peek.UI.Telemetry.Events; using Peek.UI.Telemetry.Events;
@@ -11,6 +14,13 @@ namespace Peek.FilePreviewer.Previewers
{ {
public class PreviewerFactory public class PreviewerFactory
{ {
private readonly IPreviewSettings _previewSettings;
public PreviewerFactory()
{
_previewSettings = Application.Current.GetService<IPreviewSettings>();
}
public IPreviewer Create(IFileSystemItem file) public IPreviewer Create(IFileSystemItem file)
{ {
if (ImagePreviewer.IsFileTypeSupported(file.Extension)) if (ImagePreviewer.IsFileTypeSupported(file.Extension))
@@ -23,7 +33,7 @@ namespace Peek.FilePreviewer.Previewers
} }
else if (WebBrowserPreviewer.IsFileTypeSupported(file.Extension)) else if (WebBrowserPreviewer.IsFileTypeSupported(file.Extension))
{ {
return new WebBrowserPreviewer(file); return new WebBrowserPreviewer(file, _previewSettings);
} }
else if (ArchivePreviewer.IsFileTypeSupported(file.Extension)) else if (ArchivePreviewer.IsFileTypeSupported(file.Extension))
{ {

View File

@@ -5,6 +5,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text.Json; using System.Text.Json;
using Common.UI; using Common.UI;
using ManagedCommon; using ManagedCommon;
@@ -23,11 +24,11 @@ namespace Peek.FilePreviewer.Previewers
JsonDocument languageListDocument = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.GetLanguages(); JsonDocument languageListDocument = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.GetLanguages();
JsonElement languageList = languageListDocument.RootElement.GetProperty("list"); JsonElement languageList = languageListDocument.RootElement.GetProperty("list");
foreach (JsonElement e in languageList.EnumerateArray()) foreach (JsonElement e in languageList.EnumerateArray())
{
if (e.TryGetProperty("extensions", out var extensions))
{ {
for (int j = 0; j < extensions.GetArrayLength(); j++) if (e.TryGetProperty("extensions", out var extensions))
{ {
for (int j = 0; j < extensions.GetArrayLength(); j++)
{
set.Add(extensions[j].ToString()); set.Add(extensions[j].ToString());
} }
} }
@@ -44,15 +45,32 @@ namespace Peek.FilePreviewer.Previewers
/// <summary> /// <summary>
/// Prepares temp html for the previewing /// Prepares temp html for the previewing
/// </summary> /// </summary>
public static string PreviewTempFile(string fileText, string extension, string tempFolder) public static string PreviewTempFile(string fileText, string extension, string tempFolder, bool tryFormat, bool wrapText)
{ {
// TODO: check if file is too big, add MaxFileSize to settings // TODO: check if file is too big, add MaxFileSize to settings
return InitializeIndexFileAndSelectedFile(fileText, extension, tempFolder); return InitializeIndexFileAndSelectedFile(fileText, extension, tempFolder, tryFormat, wrapText);
} }
private static string InitializeIndexFileAndSelectedFile(string fileContent, string extension, string tempFolder) private static string InitializeIndexFileAndSelectedFile(string fileContent, string extension, string tempFolder, bool tryFormat, bool wrapText)
{ {
string vsCodeLangSet = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.GetLanguage(extension); string vsCodeLangSet = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.GetLanguage(extension);
if (tryFormat)
{
var formatter = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.Formatters.SingleOrDefault(f => f.LangSet == vsCodeLangSet);
if (formatter != null)
{
try
{
fileContent = formatter.Format(fileContent);
}
catch (Exception ex)
{
Logger.LogError($"Failed to apply formatting", ex);
}
}
}
string base64FileCode = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(fileContent)); string base64FileCode = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(fileContent));
string theme = ThemeManager.GetWindowsBaseColor().ToLowerInvariant(); string theme = ThemeManager.GetWindowsBaseColor().ToLowerInvariant();
@@ -60,7 +78,7 @@ namespace Peek.FilePreviewer.Previewers
string html = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.ReadIndexHtml(); string html = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.ReadIndexHtml();
html = html.Replace("[[PT_LANG]]", vsCodeLangSet, StringComparison.InvariantCulture); html = html.Replace("[[PT_LANG]]", vsCodeLangSet, StringComparison.InvariantCulture);
html = html.Replace("[[PT_WRAP]]", "1", StringComparison.InvariantCulture); // TODO: add to settings html = html.Replace("[[PT_WRAP]]", wrapText ? "1" : "0", StringComparison.InvariantCulture);
html = html.Replace("[[PT_THEME]]", theme, StringComparison.InvariantCulture); html = html.Replace("[[PT_THEME]]", theme, StringComparison.InvariantCulture);
html = html.Replace("[[PT_CODE]]", base64FileCode, StringComparison.InvariantCulture); html = html.Replace("[[PT_CODE]]", base64FileCode, StringComparison.InvariantCulture);
html = html.Replace("[[PT_URL]]", Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.VirtualHostName, StringComparison.InvariantCulture); html = html.Replace("[[PT_URL]]", Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.VirtualHostName, StringComparison.InvariantCulture);

View File

@@ -13,13 +13,14 @@ using Peek.Common.Extensions;
using Peek.Common.Helpers; using Peek.Common.Helpers;
using Peek.Common.Models; using Peek.Common.Models;
using Peek.FilePreviewer.Models; using Peek.FilePreviewer.Models;
using Windows.Foundation;
namespace Peek.FilePreviewer.Previewers namespace Peek.FilePreviewer.Previewers
{ {
public partial class WebBrowserPreviewer : ObservableObject, IBrowserPreviewer, IDisposable public partial class WebBrowserPreviewer : ObservableObject, IBrowserPreviewer, IDisposable
{ {
private static readonly HashSet<string> _supportedFileTypes = new HashSet<string> private readonly IPreviewSettings _previewSettings;
private static readonly HashSet<string> _supportedFileTypes = new()
{ {
// Web // Web
".html", ".html",
@@ -43,8 +44,9 @@ namespace Peek.FilePreviewer.Previewers
private bool disposed; private bool disposed;
public WebBrowserPreviewer(IFileSystemItem file) public WebBrowserPreviewer(IFileSystemItem file, IPreviewSettings previewSettings)
{ {
_previewSettings = previewSettings;
File = file; File = file;
Dispatcher = DispatcherQueue.GetForCurrentThread(); Dispatcher = DispatcherQueue.GetForCurrentThread();
} }
@@ -109,7 +111,7 @@ namespace Peek.FilePreviewer.Previewers
if (IsDevFilePreview && !isHtml && !isMarkdown) if (IsDevFilePreview && !isHtml && !isMarkdown)
{ {
var raw = await ReadHelper.Read(File.Path.ToString()); var raw = await ReadHelper.Read(File.Path.ToString());
Preview = new Uri(MonacoHelper.PreviewTempFile(raw, File.Extension, TempFolderPath.Path)); Preview = new Uri(MonacoHelper.PreviewTempFile(raw, File.Extension, TempFolderPath.Path, _previewSettings.SourceCodeTryFormat, _previewSettings.SourceCodeWrapText));
} }
else if (isMarkdown) else if (isMarkdown)
{ {

View File

@@ -8,7 +8,9 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Peek.Common;
using Peek.FilePreviewer; using Peek.FilePreviewer;
using Peek.FilePreviewer.Models;
using Peek.UI.Telemetry.Events; using Peek.UI.Telemetry.Events;
using Peek.UI.Views; using Peek.UI.Views;
@@ -17,7 +19,7 @@ namespace Peek.UI
/// <summary> /// <summary>
/// Provides application-specific behavior to supplement the default Application class. /// Provides application-specific behavior to supplement the default Application class.
/// </summary> /// </summary>
public partial class App : Application public partial class App : Application, IApp
{ {
public static int PowerToysPID { get; set; } public static int PowerToysPID { get; set; }
@@ -46,6 +48,7 @@ namespace Peek.UI
// Core Services // Core Services
services.AddTransient<NeighboringItemsQuery>(); services.AddTransient<NeighboringItemsQuery>();
services.AddSingleton<IUserSettings, UserSettings>(); services.AddSingleton<IUserSettings, UserSettings>();
services.AddSingleton<IPreviewSettings, PreviewSettings>();
// Views and ViewModels // Views and ViewModels
services.AddTransient<TitleBar>(); services.AddTransient<TitleBar>();
@@ -57,7 +60,7 @@ namespace Peek.UI
UnhandledException += App_UnhandledException; UnhandledException += App_UnhandledException;
} }
public static T GetService<T>() public T GetService<T>()
where T : class where T : class
{ {
if ((App.Current as App)!.Host.Services.GetService(typeof(T)) is not T service) if ((App.Current as App)!.Host.Services.GetService(typeof(T)) is not T service)

View File

@@ -8,8 +8,10 @@ using ManagedCommon;
using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry;
using Microsoft.UI; using Microsoft.UI;
using Microsoft.UI.Windowing; using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Input;
using Peek.Common.Constants; using Peek.Common.Constants;
using Peek.Common.Extensions;
using Peek.FilePreviewer.Models; using Peek.FilePreviewer.Models;
using Peek.UI.Extensions; using Peek.UI.Extensions;
using Peek.UI.Helpers; using Peek.UI.Helpers;
@@ -45,7 +47,7 @@ namespace Peek.UI
Logger.LogError($"HandleThemeChange exception. Please install .NET 4.", e); Logger.LogError($"HandleThemeChange exception. Please install .NET 4.", e);
} }
ViewModel = App.GetService<MainWindowViewModel>(); ViewModel = Application.Current.GetService<MainWindowViewModel>();
NativeEventWaiter.WaitForEventLoop(Constants.ShowPeekEvent(), OnPeekHotkey); NativeEventWaiter.WaitForEventLoop(Constants.ShowPeekEvent(), OnPeekHotkey);
@@ -68,11 +70,11 @@ namespace Peek.UI
} }
} }
private void PeekWindow_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args) private void PeekWindow_Activated(object sender, WindowActivatedEventArgs args)
{ {
if (args.WindowActivationState == Microsoft.UI.Xaml.WindowActivationState.Deactivated) if (args.WindowActivationState == WindowActivationState.Deactivated)
{ {
var userSettings = App.GetService<IUserSettings>(); var userSettings = Application.Current.GetService<IUserSettings>();
if (userSettings.CloseAfterLosingFocus) if (userSettings.CloseAfterLosingFocus)
{ {
Uninitialize(); Uninitialize();

View File

@@ -48,7 +48,7 @@ namespace Peek.UI
if (!_settingsUtils.SettingsExists(PeekModuleName)) if (!_settingsUtils.SettingsExists(PeekModuleName))
{ {
Logger.LogInfo("Hosts settings.json was missing, creating a new one"); Logger.LogInfo("Peek settings.json was missing, creating a new one");
var defaultSettings = new PeekSettings(); var defaultSettings = new PeekSettings();
defaultSettings.Save(_settingsUtils); defaultSettings.Save(_settingsUtils);
} }

View File

@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Settings.UI.Library
{
public class PeekPreviewSettings : ISettingsConfig
{
public const string FileName = "preview-settings.json";
public BoolProperty SourceCodeWrapText { get; set; }
public BoolProperty SourceCodeTryFormat { get; set; }
public PeekPreviewSettings()
{
SourceCodeWrapText = new BoolProperty(false);
SourceCodeTryFormat = new BoolProperty(false);
}
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
public string GetModuleName()
{
return PeekSettings.ModuleName;
}
public bool UpgradeSettingsConfiguration()
{
return false;
}
}
}

View File

@@ -40,6 +40,25 @@
</controls:SettingsCard> </controls:SettingsCard>
</custom:SettingsGroup> </custom:SettingsGroup>
<custom:SettingsGroup x:Uid="Peek_Preview_GroupSettings" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
<controls:SettingsExpander
x:Uid="Peek_SourceCode_Header"
HeaderIcon="{ui:FontIcon Glyph=&#xE99A;}"
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
<controls:SettingsExpander.Items>
<controls:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<CheckBox x:Uid="Peek_SourceCode_WrapText" IsChecked="{x:Bind ViewModel.SourceCodeWrapText, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<custom:CheckBoxWithDescriptionControl
x:Uid="Peek_SourceCode_TryFormat"
IsChecked="{x:Bind ViewModel.SourceCodeTryFormat, Mode=TwoWay}"
IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</custom:SettingsGroup>
</StackPanel> </StackPanel>
</custom:SettingsPageControl.ModuleContent> </custom:SettingsPageControl.ModuleContent>
<custom:SettingsPageControl.PrimaryLinks> <custom:SettingsPageControl.PrimaryLinks>

View File

@@ -3838,4 +3838,22 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="EnabledModules.Text" xml:space="preserve"> <data name="EnabledModules.Text" xml:space="preserve">
<value>Enabled modules</value> <value>Enabled modules</value>
</data> </data>
<data name="Peek_Preview_GroupSettings.Header" xml:space="preserve">
<value>Preview</value>
</data>
<data name="Peek_SourceCode_Header.Description" xml:space="preserve">
<value>.cpp, .py, .json, .xml, .csproj, ...</value>
</data>
<data name="Peek_SourceCode_Header.Header" xml:space="preserve">
<value>Source code files (Monaco)</value>
</data>
<data name="Peek_SourceCode_TryFormat.Description" xml:space="preserve">
<value>Applies to json and xml. Files remain unchanged.</value>
</data>
<data name="Peek_SourceCode_TryFormat.Header" xml:space="preserve">
<value>Try to format the source for preview</value>
</data>
<data name="Peek_SourceCode_WrapText.Content" xml:space="preserve">
<value>Wrap text</value>
</data>
</root> </root>

View File

@@ -9,6 +9,7 @@ using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers; using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces; using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Settings.UI.Library;
namespace Microsoft.PowerToys.Settings.UI.ViewModels namespace Microsoft.PowerToys.Settings.UI.ViewModels
{ {
@@ -20,6 +21,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private readonly ISettingsUtils _settingsUtils; private readonly ISettingsUtils _settingsUtils;
private readonly PeekSettings _peekSettings; private readonly PeekSettings _peekSettings;
private readonly PeekPreviewSettings _peekPreviewSettings;
private GpoRuleConfigured _enabledGpoRuleConfiguration; private GpoRuleConfigured _enabledGpoRuleConfiguration;
private bool _enabledStateIsGPOConfigured; private bool _enabledStateIsGPOConfigured;
@@ -46,6 +48,15 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_peekSettings = new PeekSettings(); _peekSettings = new PeekSettings();
} }
if (_settingsUtils.SettingsExists(PeekSettings.ModuleName, PeekPreviewSettings.FileName))
{
_peekPreviewSettings = _settingsUtils.GetSettingsOrDefault<PeekPreviewSettings>(PeekSettings.ModuleName, PeekPreviewSettings.FileName);
}
else
{
_peekPreviewSettings = new PeekPreviewSettings();
}
InitializeEnabledValue(); InitializeEnabledValue();
SendConfigMSG = ipcMSGCallBackFunc; SendConfigMSG = ipcMSGCallBackFunc;
@@ -137,15 +148,48 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
} }
} }
public bool SourceCodeWrapText
{
get => _peekPreviewSettings.SourceCodeWrapText.Value;
set
{
if (_peekPreviewSettings.SourceCodeWrapText.Value != value)
{
_peekPreviewSettings.SourceCodeWrapText.Value = value;
OnPropertyChanged(nameof(SourceCodeWrapText));
SavePreviewSettings();
}
}
}
public bool SourceCodeTryFormat
{
get => _peekPreviewSettings.SourceCodeTryFormat.Value;
set
{
if (_peekPreviewSettings.SourceCodeTryFormat.Value != value)
{
_peekPreviewSettings.SourceCodeTryFormat.Value = value;
OnPropertyChanged(nameof(SourceCodeTryFormat));
SavePreviewSettings();
}
}
}
private void NotifySettingsChanged() private void NotifySettingsChanged()
{ {
// Using InvariantCulture as this is an IPC message // Using InvariantCulture as this is an IPC message
SendConfigMSG( SendConfigMSG(
string.Format( string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}", "{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
PeekSettings.ModuleName, PeekSettings.ModuleName,
JsonSerializer.Serialize(_peekSettings))); JsonSerializer.Serialize(_peekSettings)));
}
private void SavePreviewSettings()
{
_settingsUtils.SaveSettings(_peekPreviewSettings.ToJsonString(), PeekSettings.ModuleName, PeekPreviewSettings.FileName);
} }
public void RefreshEnabledState() public void RefreshEnabledState()