Powertoys: Use Monaco to load developer based files for Preview Pane

Created on 10 Mar 2020  Â·  9Comments  Â·  Source: microsoft/PowerToys

Summary of the new feature/enhancement

There are many plain text files which are not automatically previewed in the explorer preview pane. I think developers experience this the most given the number of source code file extensions there are.

While a previewer can be defined for them, doing so is cumbersome as it involves either direct registry edits, or my preferred powershell line:

Set-ItemProperty Registry::HKEY_CLASSES_ROOT\.ext[,.ext1,…,extN] -Name PerceivedType -Value text

A PowerToy to easily apply the text preview to a large number of file types would be convenient.

Additional option: have a pre-seeded list of the top 50-100 most common source file extensions.

Additional option: add right-click explorer menu item "Use text preview for files of this type"

Additional option (an admitted stretch): use VS Code for previewing service to allow for syntax highlighting.

Proposed technical implementation details (optional)

Help Wanted Idea-New PowerToy

Most helpful comment

https://github.com/microsoft/monaco-editor could be a solid implementation item

All 9 comments

we may be able to see if monico would be an option. issue is speed and memory footprint

Great idea leveraging the work for #914

That's a nice idea, but maybe a syntax highlighted preview handler would be even better, and optionally letting to use the explorer's text preview handler.

I was playing with the https://github.com/microsoft/PowerToys/tree/master/src/modules/previewpane PreviewHandlerCommon project and followed the instructions, then using ScintillaNET in a preview handler for source code was very easy to implement, I also tried using AvalonEdit, but both failed because they use TEMP directory and access is denied to that path, I couldn't find out why, though, maybe preview handler rules has something to do.

ScintillaNET needs to extract SciteLexer to that TEMP directory, and AvalonEdit uses that TEMP too, since it is a WPF component it requires to use ElementHost.

However, ScintillaNET allows to use a custom SciLexer.dll, so finally I could make it work.

Here a snapshot:
imagen

And my proof of concept source code is as follows:

SourceCodePreviewHandler.cs:

using System;
using System.Runtime.InteropServices;
using Common;

namespace SourceCodePreviewHandler
{
    /// <summary>
    /// Implementation of preview handler for common source code files
    /// </summary>
    [Guid("F5FD01B1-3DDE-4F68-AFD0-1E55859B0FE9")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComVisible(true)]
    public class SourceCodePreviewHandler : FileBasedPreviewHandler
    {
        private SourceCodePreviewHandlerControl sourceCodePreviewHandlerControl;

        public string PreviewHandlerGUID;

        // Call common source code files rendering method here.
        public override void DoPreview()
        {
            this.sourceCodePreviewHandlerControl.DoPreview(this.FilePath);
        }

        protected override IPreviewHandlerControl CreatePreviewHandlerControl()
        {

            this.sourceCodePreviewHandlerControl = new SourceCodePreviewHandlerControl();
            this.sourceCodePreviewHandlerControl.PreviewHandlerGUID = this.GetType().GUID.ToString().ToUpper();
            return this.sourceCodePreviewHandlerControl;
        }
    }
}

SourceCodePreviewHandlerControl.cs:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.Integration;
using Common;
using Microsoft.Win32;
using ScintillaNET;

namespace SourceCodePreviewHandler
{
    public class SourceCodePreviewHandlerControl : FormHandlerControl
    {

        private Scintilla textArea;

        public string PreviewHandlerGUID;

        public SourceCodePreviewHandlerControl()
        {
            // initialization
        }

        public override void DoPreview<T>(T dataSource)
        {
            this.InvokeOnControlThread(() =>
            {
                try
                {
                    // get SciLexer Dynamic Link Library path, must be in preview handler's path
                    string SciLexerPath = (string)Registry.GetValue(@"HKEY_CLASSES_ROOT\CLSID\{"+PreviewHandlerGUID+@"}\InprocServer32", "CodeBase", null);
                    SciLexerPath = Path.GetDirectoryName(SciLexerPath.Replace(@"file:///", ""));

                    if (Environment.Is64BitProcess)
                    {
                        SciLexerPath += @"\SciLexer.x64.dll";
                    }
                    else
                    {
                        SciLexerPath += @"\SciLexer.x86.dll";
                    }

                    // ScintillaNET embeds SciLexer.dll and extracts it to TEMP folder, but preview handler somehow access is denied
                    Scintilla.SetModulePath(SciLexerPath);

                    string filePath = dataSource as string;
                    string fileText = File.ReadAllText(filePath);

                    this.textArea = new Scintilla
                    {
                        Dock = DockStyle.Fill,
                        WrapMode = WrapMode.None,
                        IndentationGuides = IndentView.LookBoth
                    };

                    this.textArea.Margins[0].Width = 0;

                    this.textArea.StyleResetDefault();
                    this.textArea.Styles[Style.Default].Font = "Consolas";
                    this.textArea.Styles[Style.Default].Size = 10;
                    this.textArea.StyleClearAll();
                    // Configure the CPP (C#) lexer styles
                    this.textArea.Styles[Style.Cpp.Default].ForeColor = Color.Silver;
                    this.textArea.Styles[Style.Cpp.Comment].ForeColor = Color.FromArgb(0, 128, 0); // Green
                    this.textArea.Styles[Style.Cpp.CommentLine].ForeColor = Color.FromArgb(0, 128, 0); // Green
                    this.textArea.Styles[Style.Cpp.CommentLineDoc].ForeColor = Color.FromArgb(128, 128, 128); // Gray
                    this.textArea.Styles[Style.Cpp.Number].ForeColor = Color.Olive;
                    this.textArea.Styles[Style.Cpp.Word].ForeColor = Color.Blue;
                    this.textArea.Styles[Style.Cpp.Word2].ForeColor = Color.Blue;
                    this.textArea.Styles[Style.Cpp.String].ForeColor = Color.FromArgb(163, 21, 21); // Red
                    this.textArea.Styles[Style.Cpp.Character].ForeColor = Color.FromArgb(163, 21, 21); // Red
                    this.textArea.Styles[Style.Cpp.Verbatim].ForeColor = Color.FromArgb(163, 21, 21); // Red
                    this.textArea.Styles[Style.Cpp.StringEol].BackColor = Color.Pink;
                    this.textArea.Styles[Style.Cpp.Operator].ForeColor = Color.Purple;
                    this.textArea.Styles[Style.Cpp.Preprocessor].ForeColor = Color.Maroon;
                    this.textArea.Lexer = Lexer.Cpp;
                    this.textArea.ReadOnly = false;
                    this.textArea.Text = fileText;
                    this.textArea.ReadOnly = true;

                    // disable context menu because it steals focus, and makes explorer file navigation loses focus
                    this.textArea.ContextMenu = new ContextMenu();

                    this.Controls.Clear();
                    this.Controls.Add(textArea);
                    base.DoPreview(dataSource);
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message);
                    base.DoPreview(dataSource);
                }
            });
        }

    }
}

However, there is more room for improvements, like detection of appropriate lexer for the selected file, it could be great to allow choosing which source code files to highlight from the settings window in PowerToys, themes, and detection of explorer's dark mode.

Thanks for the PoC - impressive to see something working. Yes, syntax highlighting was indeed the ideal stretch goal for this feature. The idea of incorporating themes & dark mode would be very nice elements! From browsing the source on the SVG viewer, I see that it just uses a browser extension to do this, hence I think the idea from @crutkas of using the Monaco editor which power VS Code, which would open up the ability to use installed VS Code themes as an option. Thanks!

https://github.com/microsoft/monaco-editor could be a solid implementation item

This would streamline the work for so many developers and technical centric roles. Huge supporter of this. Might be able to help with the implementation but I only know JS.

I think some simple WPF / WinForm concept with buttons that flip known formats would be helpful.

@michael-hawker did you say you had a control we may be able to base this off of?

🦙 Yup, I have a C# UWP Projection of the Monaco Editor (hosted via WebView) that I did as a side project: https://github.com/hawkerm/monaco-editor-uwp. It should provide basic text highlighting for many languages out of the box (just as Monaco does as it's the same 🙂).

It's been on my backlog to ship some fixes I did earlier this year, but they're all focused for editor builders. The control is the same one we use in the Toolkit Sample App and my XAML Studio Garage project, so it's been pretty stable. I'm in the process of figuring out if we pull this wrapper into the Toolkit more officially though, but I'm trying to sync with the Monaco folks first before we do.

Ah, thinking if this needs to be for WPF instead of UWP. Alex has a branch using WebView2 (for WinUI 3) and the Uno folks have started splitting the project a bit to support different platforms in their fork here.

If we move the C# typings to a .NET Standard project, we should get closer to being able to use the WebView2 version from UWP but on top of the .NET WebView2 that'll work for WPF. Assuming the Monaco is good with broadening the audience for the language projection, I'm happy to help facilitate figuring out moving the project forward. (It'd be easier that way to add Blazor support too to centralize efforts on top of the C# language projection bits we already auto-generate).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

verglor picture verglor  Â·  3Comments

Marrib picture Marrib  Â·  3Comments

CombeeMike picture CombeeMike  Â·  3Comments

Martin-Hausleitner picture Martin-Hausleitner  Â·  3Comments

Satanarious picture Satanarious  Â·  3Comments