Inside F#

Brian's thoughts on F# and .NET

Archive for November, 2010

Source code for F# Depth Colorizer extension

Posted by Brian on November 21, 2010

I’ve published the source code for the F# Depth Colorizer.  You can get the code from github.  The code is available under the Apache 2.0 License.

Here’s a quick overview of what’s there.  There is a Visual Studio 2010 solution with two projects. 

  • The “MyFSParser” project is a library that parses F# source code and yields “range” information about where source code constructs begin and end.  It is basically a free-standing version of the front half of the F# compiler, followed by about 350 lines of my own code for munging and publishing range information from the abstract syntax tree (AST) produced by the F# compiler front end.
  • The “VSIXColorizer” project is a Visual Studio extension that plug into the editor, listens for changes in the test buffer, re-parses the source, gets the range information, tags editor ‘spans’ with the semantic depth information, and then draws adornments on the screen for the portions of the file in view.  It’s also about 350 lines of code.

Of course all the code is written in F#.  This is a nice sample that demonstrates both the simple Visual Studio extensibility model that makes it easy to craft editor extensions, as well as the kinds of things you can do when you leverage the F# compiler source to do the “heavy lifting” to parse F# code for you.

In order to provide a little more guidance, I made a couple screencasts, about 10 minutes each, where I give a (rambling) walkthrough of each of the projects.  You won’t learn all the fine details about how the compiler code or my code works, but you will get an overview of the structure and see some of the important methods/functions and how the pieces fit together.  Here is the video about the parser and here is the video about the colorizer.

I’ve already started working on another editor extension that reuses the parser library code, and so in another week or two hopefully I’ll share that too.  I hope this is good “seed code” to help you leverage the F# compiler source and Visual Studio extensibility to do your own cool stuff!

Posted in Uncategorized | 6 Comments »

A quick standalone app to edit colors for the F# colorizer

Posted by Brian on November 20, 2010

What kind of fool posts a cool extension for colorizing F# source code structure, but then only lets you configure the colors by editing the registry (and then having to restart Visual Studio to see if the new colors look ok)?

Oh, that was me.  :)

Like I said in the previous blog, I don’t have the skills to design a good GUI to edit the colors or figure out the most discoverable place in VS to edit them.  But maybe you do!  And so in the spirit of sharing source, here’s a crummy (but working) standalone app for experimenting with the colors.  A screenshot is suggestive:

FSColorEditor

Basically, you can click on one of the rectangles, and then use the buttons to change the R/G/B values of the left-edge color or the main color.  And once you have colors you like, you can click a different button which prints out a .reg file you can use to install those colors.  (The app only reads the registry, it does not write it, so you have to copy-paste the text from the console window, save it to a .reg file, and run it if you want to install your updated colors.)

Anyway, the code is below.  Just throw this in an F# Console Application, add references to the WPF assemblies (PresentationCore, PresentationFramework, System.Xaml, System.Xml, UIAutomationTypes, and WindowsBase), press Ctrl-F5 to build and run and you’re off to the races.  Feel free to steal all this code to build your own better app/extension, or just to use to build your own attractive color schemes.  The code is just a quick ugly hack, but hopefully you can work with it.

And yes, I’ll be releasing the code for the actual editor colorizer extension soon, I just want to finish tidying it up a little.  (Thanks to those who have already left feedback and pointed out minor bugs!)

Quickie code for app to manage editing colors:

open System.Windows 
open System.Windows.Controls
open System.Windows.Data
open System.Windows.Media 
open System.Windows.Shapes 
open Microsoft.Win32 
 
type IIndexable<'a> =
    abstract member Item : int -> 'a
 
let makeBinding(sourceObj:obj, sourcePath:string, converter:'T->'U, 
                targetObj:DependencyObject, targetDependencyProperty:DependencyProperty) =
    let b = new System.Windows.Data.Binding()
    b.Source <- sourceObj
    b.Path <- new System.Windows.PropertyPath(sourcePath)
    b.Converter <- 
        { new System.Windows.Data.IValueConverter with
            member this.Convert(v, targetType, param, culInfo) =
                box(converter (unbox v))
            member this.ConvertBack(v, targetType, param, culInfo) =
                raise <| new System.NotImplementedException() }
    BindingOperations.SetBinding(targetObj, targetDependencyProperty, b) |> ignore
 
type MyWindow() as this =
    inherit Window()
 
    static let selectedRectangleNumber = 
        DependencyProperty.Register("SelectedRectangleNumber", typeof<int>, typeof<MyWindow>, 
            new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender, 
                new PropertyChangedCallback(fun depObj ea -> 
                    match box depObj, box ea.NewValue with 
                    | (:? MyWindow as w), (:? int as x) -> w.TriggerSelectedRectangleNumberChanged(x) 
                    | _ -> ())))
 
    let selectedChanged = new Event<Handler<int>,int>()
    let getNum() = this.GetValue(MyWindow.SelectedRectangleNumber) :?> int
    let setNum(n) = this.SetValue(MyWindow.SelectedRectangleNumber, n)
 
    let colors =
        try
            let key = Registry.CurrentUser.OpenSubKey(
                @"Software\Microsoft\VisualStudio\10.0\Text Editor\FSharpDepthColorizer")
            Array.init 10 (fun i -> key.GetValue(sprintf "Depth%d" i) :?> string)
            |> Array.map (fun s -> let [|r1;g1;b1;r2;g2;b2|] = s.Split[|','|] |> Array.map byte
                                   r1,g1,b1,r2,g2,b2)
        with e ->
            [|  // greyscale colors
            190uy,190uy,190uy,230uy,230uy,230uy
            170uy,170uy,170uy,210uy,210uy,210uy
            184uy,184uy,184uy,224uy,224uy,224uy
            164uy,164uy,164uy,204uy,204uy,204uy
            178uy,178uy,178uy,218uy,218uy,218uy
            158uy,158uy,158uy,198uy,198uy,198uy
            172uy,172uy,172uy,212uy,212uy,212uy
            152uy,152uy,152uy,192uy,192uy,192uy
            166uy,166uy,166uy,206uy,206uy,206uy
            146uy,146uy,146uy,186uy,186uy,186uy
            |]
    // why the SolidColorBrush below? 
    // to have a DependencyObject to data-bind... there is probably a cleaner way, but oh well
    let edgeColors = colors |> Array.map (fun (r,g,b,_,_,_) -> SolidColorBrush(Color.FromRgb(r,g,b)))
    let mainColors = colors |> Array.map (fun (_,_,_,r,g,b) -> SolidColorBrush(Color.FromRgb(r,g,b)))
    let brushes = 
        let makeGradientStop(b:SolidColorBrush, pct) =
            let gs = new GradientStop(b.Color, pct)
            makeBinding(b, "Color", (fun (c:Color) -> c), gs, GradientStop.ColorProperty)
            gs
        { new IIndexable<Brush> with 
            member this.Item depth =
                upcast new LinearGradientBrush(
                    new GradientStopCollection( 
                        [|
                        makeGradientStop(edgeColors.[depth], 0.0)
                        makeGradientStop(mainColors.[depth], 0.01)
                        makeGradientStop(mainColors.[depth], 1.0)
                        |] ), new Point(0.0, 0.5), new Point(1.0, 0.5)) }
 
    let printCurrentSettings() =
        printfn "Windows Registry Editor Version 5.00"
        printfn ""
        printfn "[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0\Text Editor\FSharpDepthColorizer]"
        for i in 0..9 do
            let ec = edgeColors.[i].Color
            let mc = mainColors.[i].Color
            printfn "\"Depth%d\"=\"%d,%d,%d,%d,%d,%d\"" i ec.R ec.G ec.B mc.R mc.G mc.B 
 
    let mkButton(colors:SolidColorBrush[], text, dr, dg, db) =
        let b = new Button()
        b.Content <- text
        b.Click.Add(fun _ -> colors.[getNum()].Color <- 
                                Color.FromRgb(colors.[getNum()].Color.R + byte dr, 
                                              colors.[getNum()].Color.G + byte dg, 
                                              colors.[getNum()].Color.B + byte db))
        b
 
    let mkButtonPanel(colors:SolidColorBrush[],text,dr,dg,db,f) =
        let panel = new StackPanel(Orientation = Orientation.Horizontal)
        let tb = new TextBlock()
        selectedChanged.Publish.Add(fun _ ->
            makeBinding(colors.[getNum()], "Color", (fun (c:Color) -> f(c).ToString()), 
                        tb, System.Windows.Controls.TextBlock.TextProperty))
        panel.Children.Add(mkButton(colors, text+" down",-dr,-dg,-db)) |> ignore
        panel.Children.Add(tb) |> ignore
        panel.Children.Add(mkButton(colors, text+" up",dr,dg,db)) |> ignore
        panel
 
    do
        let leftPanel = new StackPanel(Orientation = Orientation.Vertical)
        leftPanel.Children.Add(mkButtonPanel(edgeColors,"edge R",2,0,0,(fun c->c.R))) |> ignore
        leftPanel.Children.Add(mkButtonPanel(edgeColors,"edge G",0,2,0,(fun c->c.G))) |> ignore
        leftPanel.Children.Add(mkButtonPanel(edgeColors,"edge B",0,0,2,(fun c->c.B))) |> ignore
        leftPanel.Children.Add(mkButtonPanel(mainColors,"main R",2,0,0,(fun c->c.R))) |> ignore
        leftPanel.Children.Add(mkButtonPanel(mainColors,"main G",0,2,0,(fun c->c.G))) |> ignore
        leftPanel.Children.Add(mkButtonPanel(mainColors,"main B",0,0,2,(fun c->c.B))) |> ignore
        let printButton = new Button(Content="Print current settings to console")
        printButton.Click.Add(fun _ -> printCurrentSettings())
        leftPanel.Children.Add(printButton) |> ignore
        let mainPanel = new StackPanel(Orientation = Orientation.Horizontal)
        mainPanel.Children.Add(leftPanel) |> ignore
        let canvas = new Canvas()
        for i in 0..mainColors.Length-1 do
            let rect = new Rectangle(Width=600., Height=500., Fill = brushes.[i])
            rect.MouseDown.Add(fun _ -> setNum(i))
            Canvas.SetLeft(rect, (float i)*30.)
            Canvas.SetTop(rect, (float i)*30.)
            Canvas.SetZIndex(rect, i)
            canvas.Children.Add(rect) |> ignore
        let rect = new Rectangle(Width=600., Height=40.)
        selectedChanged.Publish.Add(fun _ -> rect.Fill <- brushes.[getNum()])
        Canvas.SetLeft(rect, 0.)
        Canvas.SetTop(rect, (float mainColors.Length)*30. + 20.)
        Canvas.SetZIndex(rect, mainColors.Length)
        canvas.Children.Add(rect) |> ignore
        mainPanel.Children.Add(canvas) |> ignore
        this.Content <- mainPanel
 
    member private this.TriggerSelectedRectangleNumberChanged(x:int) = selectedChanged.Trigger(this, x)
    static member SelectedRectangleNumber = selectedRectangleNumber 
 
[<System.STAThread>]
do
    (new Application()).Run(MyWindow()) |> ignore

Posted in Uncategorized | 4 Comments »

F# source code structural colorizer available

Posted by Brian on November 18, 2010

Last week I mentioned the editor extension I am working on, and now it’s available for you to try out!  You can obtain the F# Depth Colorizer from the Visual Studio Gallery.  It’s a one-click install; once installed you’ll see it listed when you bring up the “Tools\Extension Manager” menu in Visual Studio:

FSDCEM

Once installed, the structure of your code will be highlighted in the editor, like this

FSDCGreyScaleScreenshot

or like this

FSDCRedBlueScreenshot

rather than plain old code like this

FSDCDisabledScreenshot

Get the idea?  As suggested by the first two screenshots, the colors are configurable.  A good UI for configuring the colors was beyond me, so the colors are configurable via a registry setting.  (EDIT: see also here) If you don’t have the registry entry, you get the “greyscale” colors from the first screenshot by default.  Those greyscale colors could be represented via the following registry entry:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0\Text Editor\FSharpDepthColorizer]
"Depth0"="190,190,190,230,230,230"
"Depth1"="170,170,170,210,210,210"
"Depth2"="184,184,184,224,224,224"
"Depth3"="164,164,164,204,204,204"
"Depth4"="178,178,178,218,218,218"
"Depth5"="158,158,158,198,198,198"
"Depth6"="172,172,172,212,212,212"
"Depth7"="152,152,152,192,192,192"
"Depth8"="166,166,166,206,206,206"
"Depth9"="146,146,146,186,186,186"

Alternatively, the red-blue colors from the second screenshot are:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0\Text Editor\FSharpDepthColorizer]
"Depth0"="240,180,180,240,220,220"
"Depth1"="180,180,240,220,220,240"
"Depth2"="240,172,172,240,216,212"
"Depth3"="172,172,240,212,216,240"
"Depth4"="240,164,164,240,212,204"
"Depth5"="164,164,240,204,212,240"
"Depth6"="240,156,156,240,208,196"
"Depth7"="156,156,240,196,208,240"
"Depth8"="240,148,148,240,204,188"
"Depth9"="148,148,240,188,204,240"

and more generally, the extension will look for the those registry keys.  (You can save these registry examples to a file “foo.reg” and then run the “foo.reg” file to install those keys, or use the “regedit” tool to inspect/change the values.  Be careful when modifying your registry!)

The DepthN values are strings of six numbers, representing two sets of RGB values, e.g.

DepthN = R1, G1, B1, R2, G2, B2

where N is the depth of the nesting of the source code structure, the RGB1 values are the color of the left edge of the highlight (intention is to provide more visual contrast at the edge), and the RGB2 values are the main color for highlighting that nesting depth.  There must be 10 depths, if you go deeper, the tool cycles around:

FSDCDeepNestingScreenshot

where the colors are getting darker until it’s gone 10 deep and cycles back around to using the lightest pink color from that color set.

I’ll publish the source code for this VSIX extension soon. (EDIT: It’s posted, see here.)   For now, I’d encourage you to try it out; you can leave feedback on my blog or on the gallery.  Enjoy!

(Much thanks to Noah who helped me write the editor portion of the extension.  Thanks to many people who did beta testing and offered useful feedback.)

Posted in Uncategorized | 14 Comments »

The F# compiler source release: making it easy to write cool Visual Studio extensions

Posted by Brian on November 12, 2010

In case you’ve been hiding under a rock and haven’t heard, the F# compiler was recently released under the Apache 2.0 license; see Don’s blog for the details.  This source code release gives you the freedom to use the F# compiler code how you like, and so of course as a developer who likes using Visual Studio, I decided to take a crack at a Visual Studio extension that I’ve always wanted to write.

The basic idea is an editor extension that uses background colorization to show the “control flow depth” of F# code.  A screenshot is a little suggestive:

FSParseDepthScreenshot

but in order to really describe it, I made a video (about 6 minutes long), linked below.

The idea is simple; the F# compiler code already knows how to do all the heavy lifting of parsing source code and creating a parse tree (an abstract syntax tree, or “AST”), and the AST knows all the info about code structure and e.g. that this expression starts in this line/column and ends in this other line/column.  So I just take the tree and turn it into some useful “depth and span” information, which I then use from a Visual Studio editor extension to add color adornments in the editor.

This is still under development, but I plan to publish the extension on the Visual Studio Gallery, and publish the source code when I’m done.

Anyway, check out the video here.  I’m interested to hear what people think, and hope this inspires others to try their hand at leveraging the F# compiler code to write cool tools and extensions.

Posted in Uncategorized | 4 Comments »

 
Follow

Get every new post delivered to your Inbox.

Join 30 other followers