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

Make Monaco faster and refactor code base #32477

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
162 changes: 57 additions & 105 deletions src/common/FilePreviewCommon/Assets/Monaco/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,7 @@
<html>
<head>
<script>
// Set variables

// Get URL parameters:
// `code` contains the code of the file in base64 encoded
// `theme` can be "vs" for light theme or "vs-dark" for dark theme
// `lang` is the language of the file
// `wrap` if the editor is wrapping or not

var theme = ("[[PT_THEME]]" == "dark") ? "vs-dark" : "vs";
var lang = "[[PT_LANG]]";
var wrap = ([[PT_WRAP]] == 1) ? true : false;

var base64code = "[[PT_CODE]]";

var stickyScroll = ([[PT_STICKY_SCROLL]] == 1) ? true : false;

var fontSize = [[PT_FONT_SIZE]];
var contextMenu = ([[PT_CONTEXTMENU]] == 1) ? true : false;

var editor;

// Code taken from https://stackoverflow.com/a/30106551/14774889
var code = decodeURIComponent(atob(base64code).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));

function runToggleTextWrapCommand() {
if (wrap) {
editor.updateOptions({ wordWrap: 'off' })
} else {
editor.updateOptions({ wordWrap: 'on' })
}
wrap = !wrap;
}

function runCopyCommand() {
editor.focus();
document.execCommand('copy');
}

</script>
<!-- Set browser to Edge-->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
Expand Down Expand Up @@ -73,75 +34,66 @@

<body>
<!-- Container for the editor -->
<div id="container"></div>
<div style="font-size:xx-large;position: absolute;top: 50%; right: 50%;background-color: transparent;transform: translate(50%,-50%);">...</div>
<div id="container" style="z-index: 100;"></div>
<!-- Script -->
<script src="http://[[PT_URL]]/monacoSRC/min/vs/loader.js"></script>
<script src="http://[[PT_URL]]/monacoSpecialLanguages.js" type="module"></script>
<script src="/monacoSRC/min/vs/loader.js"></script>
<script src="/monacoSpecialLanguages.js" type="module"></script>
<script type="module">
import { registerAdditionalLanguages } from 'http://[[PT_URL]]/monacoSpecialLanguages.js';
import { customTokenColors } from 'http://[[PT_URL]]/customTokenColors.js';
require.config({ paths: { vs: 'http://[[PT_URL]]/monacoSRC/min/vs' } });
require(['vs/editor/editor.main'], async function () {
await registerAdditionalLanguages(monaco)

// Creates a theme to handle custom tokens
monaco.editor.defineTheme('theme', {
base: theme, // Sets the base theme to "vs" or "vs-dark" depending on the user's preference
inherit: true,
rules: customTokenColors,
colors: {} // `colors` is a required attribute
});

// Creates the editor
// For all parameters: https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html
editor = monaco.editor.create(document.getElementById('container'), {
value: code, // Sets content of the editor
language: lang, // Sets language of the code
readOnly: true, // Sets to readonly
theme: 'theme', // Sets editor theme
minimap: { enabled: false }, // Disables minimap
lineNumbersMinChars: '3', // Width of the line numbers
contextmenu: contextMenu,
scrollbar: {
// Deactivate shadows
shadows: false,

// Render scrollbar automatically
vertical: 'auto',
horizontal: 'auto',
},
stickyScroll: { enabled: stickyScroll },
fontSize: fontSize,
wordWrap: (wrap ? 'on' : 'off') // Word wraps
import { registerAdditionalLanguages } from "/monacoSpecialLanguages.js";
window.addEventListener("DOMContentLoaded", function () {
require.config({ paths: { vs: '/monacoSRC/min/vs' } });
require(['vs/editor/editor.main'], async function () {
await registerAdditionalLanguages(monaco)
// Creates the editor
// For all parameters: https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html
editor = monaco.editor.create(document.getElementById('container'), {
readOnly: true, // Sets to readonly
minimap: {enabled: false}, // Disables minimap
lineNumbersMinChars: "3", //Width of the line numbers
scrollbar: {
// Deactivate shadows
shadows: false,

// Render scrollbar automatically
vertical: 'auto',
horizontal: 'auto',
},
});
window.onresize = function (){
editor.layout();
};

// Code taken from stackoverflow.com/a/30106551/14774889
onContextMenu();
// Add switch wrap button to context menu
editor.addAction({
id: 'text-wrap',

label: 'Toggle text wrapping',

// A precondition for this action.
precondition: null,

// A rule to evaluate on top of the precondition in order to dispatch the keybindings.
keybindingContext: null,

contextMenuGroupId: 'cutcopypaste',

contextMenuOrder: 100,

// Method that will be executed when the action is triggered.
// @param editor The editor instance is passed in as a convenience
run: function (ed) {
if(wrap){
editor.updateOptions({ wordWrap: "off" })
}else{
editor.updateOptions({ wordWrap: "on" })
}
wrap = !wrap;
}
});
});
window.onresize = () => {
editor.layout();
};

// Add toggle wrap button to context menu
editor.addAction({
id: 'text-wrap',

label: 'Toggle text wrapping',

// A precondition for this action.
precondition: null,

// A rule to evaluate on top of the precondition in order to dispatch the keybindings.
keybindingContext: null,

contextMenuGroupId: 'cutcopypaste',

contextMenuOrder: 100,

// Method that will be executed when the action is triggered.
// @param editor The editor instance is passed in as a convenience
run: function (ed) {
runToggleTextWrapCommand();
}
});

onContextMenu();
});

function onContextMenu() {
Expand Down
27 changes: 19 additions & 8 deletions src/common/FilePreviewCommon/MonacoHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public static class MonacoHelper
new XmlFormatter(),
}.AsReadOnly();

public const string DefaultLightTheme = "vs";
public const string DefaultDarkTheme = "vs-dark";

private static string? _monacoDirectory;

public static string GetRuntimeMonacoDirectory()
Expand Down Expand Up @@ -105,17 +108,25 @@ public static string GetLanguage(string fileExtension)
}
}

public static string ReadIndexHtml()
public static string GetSetThemeCommand(string theme)
{
string html;
return $"editor.updateOptions({{\"theme\": \"{theme}\"}});";
}

using (StreamReader htmlFileReader = new StreamReader(new FileStream(MonacoDirectory + "\\index.html", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
html = htmlFileReader.ReadToEnd();
htmlFileReader.Close();
}
public static string GetSetLanguageCommand(string language)
{
return $"monaco.editor.setModelLanguage(editor.getModel(), \"{language}\");";
}

return html;
public static string GetSetContentCommand(string rawContent)
{
string content = rawContent.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r");
return $"editor.getModel().setValue(`{content}`);";
}

public static string GetSetWordWrapCommand(bool wordWrap)
{
return $"editor.updateOptions({{\"wordWrap\": \"{(wordWrap ? "on" : "off")}\"}});";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public Uri? Source
typeof(BrowserControl),
new PropertyMetadata(null, new PropertyChangedCallback((d, e) => ((BrowserControl)d).OnIsDevFilePreviewChanged())));

public Queue<string> JavascriptCommandQueue { get; set; } = [];

// Will actually be true for Markdown files as well.
public bool IsDevFilePreview
{
Expand Down Expand Up @@ -97,6 +99,16 @@ public BrowserControl()
{
this.InitializeComponent();
Environment.SetEnvironmentVariable("WEBVIEW2_USER_DATA_FOLDER", TempFolderPath.Path, EnvironmentVariableTarget.Process);

PreviewBrowser.NavigationCompleted += async (sender, _) =>
{
foreach (string command in JavascriptCommandQueue)
{
await sender.ExecuteScriptAsync(command);
}

JavascriptCommandQueue.Clear();
};
}

public void Dispose()
Expand Down
16 changes: 15 additions & 1 deletion src/modules/peek/Peek.FilePreviewer/FilePreview.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -112,6 +113,8 @@ private async void Previewer_PropertyChanged(object? sender, System.ComponentMod

public IUnsupportedFilePreviewer? UnsupportedFilePreviewer => Previewer as IUnsupportedFilePreviewer;

public Queue<string> JavascriptCommandQueue { get; set; } = [];

public IFileSystemItem Item
{
get => (IFileSystemItem)GetValue(ItemProperty);
Expand Down Expand Up @@ -179,6 +182,9 @@ private async Task OnItemPropertyChanged()
imagePreviewer.ScalingFactor = ScalingFactor;
}

BrowserPreview.JavascriptCommandQueue.Clear();
JavascriptCommandQueue.Clear();

await UpdatePreviewAsync(_cancellationTokenSource.Token);
}

Expand Down Expand Up @@ -208,6 +214,12 @@ private async Task UpdatePreviewAsync(CancellationToken cancellationToken)
cancellationToken.ThrowIfCancellationRequested();
await Previewer.LoadPreviewAsync(cancellationToken);

if (Previewer is IBrowserPreviewer browserPreviewer)
{
BrowserPreview.JavascriptCommandQueue = browserPreviewer.JavascriptCommandQueue;
JavascriptCommandQueue = browserPreviewer.JavascriptCommandQueue;
}

cancellationToken.ThrowIfCancellationRequested();
await UpdateTooltipAsync(cancellationToken);
}
Expand Down Expand Up @@ -248,6 +260,8 @@ partial void OnPreviewerChanging(IPreviewer? value)
{
value.PropertyChanged += Previewer_PropertyChanged;
}

JavascriptCommandQueue.Clear();
}

private void BrowserPreview_DOMContentLoaded(Microsoft.Web.WebView2.Core.CoreWebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2DOMContentLoadedEventArgs args)
Expand Down Expand Up @@ -345,7 +359,7 @@ private async Task UpdateTooltipAsync(CancellationToken cancellationToken)
var sb = new StringBuilder(fileNameFormatted, 256);

cancellationToken.ThrowIfCancellationRequested();
string fileType = await Task.Run(Item.GetContentTypeAsync);
string fileType = await Item.GetContentTypeAsync().ConfigureAwait(true);
string fileTypeFormatted = string.IsNullOrEmpty(fileType) ? string.Empty : "\n" + ReadableStringHelper.FormatResourceString("PreviewTooltip_FileType", fileType);
sb.Append(fileTypeFormatted);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;

namespace Peek.FilePreviewer.Previewers.Interfaces
{
Expand All @@ -12,6 +13,8 @@ public interface IBrowserPreviewer : IPreviewer, IPreviewTarget

public bool IsDevFilePreview { get; }

public Queue<string> JavascriptCommandQueue { get; set; }

public bool CustomContextMenu { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static HashSet<string> GetExtensions()
/// <summary>
/// Prepares temp html for the previewing
/// </summary>
public static string PreviewTempFile(string fileText, string extension, string tempFolder, bool tryFormat, bool wrapText, bool stickyScroll, int fontSize)
public static (Uri Uri, string VsCodeLangSet, string FileContent) PreviewTempFile(string fileText, string extension, bool tryFormat)
{
// TODO: check if file is too big, add MaxFileSize to settings
return InitializeIndexFileAndSelectedFile(fileText, extension, tempFolder, tryFormat, wrapText, stickyScroll, fontSize);
Expand All @@ -73,24 +73,7 @@ private static string InitializeIndexFileAndSelectedFile(string fileContent, str
}
}

string base64FileCode = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(fileContent));
string theme = ThemeManager.GetWindowsBaseColor().ToLowerInvariant();

// prepping index html to load in
string html = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.ReadIndexHtml();

html = html.Replace("[[PT_LANG]]", vsCodeLangSet, StringComparison.InvariantCulture);
html = html.Replace("[[PT_WRAP]]", wrapText ? "1" : "0", StringComparison.InvariantCulture);
html = html.Replace("[[PT_CONTEXTMENU]]", "0", StringComparison.InvariantCulture);
html = html.Replace("[[PT_STICKY_SCROLL]]", stickyScroll ? "1" : "0", StringComparison.InvariantCulture);
html = html.Replace("[[PT_THEME]]", theme, StringComparison.InvariantCulture);
html = html.Replace("[[PT_FONT_SIZE]]", fontSize.ToString(CultureInfo.InvariantCulture), StringComparison.InvariantCulture);
html = html.Replace("[[PT_CODE]]", base64FileCode, StringComparison.InvariantCulture);
html = html.Replace("[[PT_URL]]", Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.VirtualHostName, StringComparison.InvariantCulture);

string filename = tempFolder + "\\" + Guid.NewGuid().ToString() + ".html";
File.WriteAllText(filename, html);
return filename;
return (new Uri("http://" + Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.VirtualHostName + "/index.html"), vsCodeLangSet, fileContent);
}
}
}
Loading
Loading