F# Web Snippets

F# Web Snippets is a tool for formatting your F# source code for web pages or blogs. It does colorization of the code and, more interestingly, adds tool tips with types and other information. This is especially important for languages like F# where types are not written explicitly in the source code. When reading F# code in Visual Studio, you can use tool tips to get information about the type, which is very useful when understanding code. However, if you post a code snippet to a web, the information about types is gone! F# Web Snippets generates snippets that include tool tips for identifiers in the same way as Visual Studio.

The following two code snippets were generated from a single script file. When you place the cursor over an identifier (such as Hello, Console or name), you should see a tool tip with additional information:

1: /// Says hello to the specified person/entity
2: type Hello(name) = 
3:   /// Display 'Hello <name>' message in console
4:   member x.SayHello() = 
5:     Console.WriteLine("Hello " + name)F# Web Snippets

Note that the two snippets were generated from a single file, so we can see full information about the Hello type (declared in the first snippet) in the second snippet, including its members and documentation extracted from the /// comment:

1: let hello = new Hello("World")
2: hello.SayHello()F# Web Snippets

Get F# Web Snippets

There is a WinForms version and web based version of the tool. The WinForms based version has some additional features, runs faster so it is a better choice if you plan to use it seriously. The web based version is mainly for a demonstration (processing a simple script takes about 50% CPU time for a while, so please be nice to my server!)

Using F# Web Snippets


Click for a large version

The tool uses F# compiler service to do the formatting and to get tool tip texts. This means that you'll see exactly the same information as in Visual Studio. In order to use it, you need to have an F# script file (.fsx file) that the compiler can type-check (so that it can generate correct tool tips). Obviously, the tool needs a complete and correct script file and it cannot process a snippet without the full script context.

Marking snippets

To make it easier to generate snippets from the script file, the tool uses meta-comments to mark parts of code that you want to process. For example, the following script file generates snippet that is displayed in the screenshot on the right.

 1: let isEven n = n%2 = 0
 2: let formatInt n = (string n) + "N"
 3: 
 4: // [snippet:Filtering and projection]
 5: let res = 
 6:   [ 1 .. 10 ] 
 7:   |> List.filter isEven
 8:   |> List.map formatInt
 9: // [/snippet]

The meta-comment syntax is quite simple - you can enclose any block of code between [snippet:<Title>] and [/snippet] tags. As the screenshot demonstrates, F# Web Snippets tool will generate HTML only for snippets marked using meta-comments. It also picks the title of a snippet from the comment so that you can easily see which snippet is which. However, because we provided a complete script file, the tool can generate tool tips even for items that are not declared in the snippets (e.g. formatInt function in the screenshot).

Omitting blocks of code

When presenting a code snippet, it is often useful to hide some part of the code. For example when we have a type declaration and want to demonstrate just the structure of the type without looking at the actual implementation. When using F# Web Snippets, we can use another form of meta-comments to hide a block of code:

1: /// Mutable counter that can be incremented and decremented
2: type Counter(init) = 
3:   (private fields omitted)
4:   member x.Increment = (...)
5:   member x.Decrement = (...)F# Web Snippets

When hiding a block of code, we can provide a replacement text that will be displayed in the gray box (the look can be, of course, changed by modifying a CSS style). When you place a mouse pointer over the box, you'll see the original code (without unnecessary indentation and line breaks). The following snippet shows the code that we need to write to produce the above snippet:

 1: /// Mutable counter that can be incremented and decremented
 2: type Counter(init) = 
 3:   (*[omit:(private fields omitted)]*)
 4:   let mutable n = init(*[/omit]*)
 5:   member x.Increment = (*[omit:(...)]*)
 6:     n <- n + 1
 7:     n(*[/omit]*)
 8:   member x.Decrement = (*[omit:(...)]*)
 9:     n <- n - 1
10:     n(*[/omit]*)F# Web Snippets

The syntax of the meta-comments is again quite simple. One notable difference is that snippets are marked using a // comment that needs to be a single thing on the whole line. When specifying omission, we may want to specify a region that starts in the middle of a line and spans over a part of a line or over several lines and then ends somewhere in the middle of another line. To enable this, we need to use inline comments, which are written as (* ... *). In the starting tag, we specify the replacement text, so we can write [omit:<Replacement>]. Note that the replacement text doesn't have to be wrapped in parentheses (this is just a convention that I used in the previous example). The ending tag is simply written as [/omit].

Tool configuration


Click for a large version

The tool internally calls F# compiler and it will work correctly only when the script file type-checks without errors. This means that you may need to provide some information about the context. This can be done using a few options in the Windows Forms based version of the tool (the web based version supports only default .NET 2.0 script context). The ways to configure the tool are:

  • File name - This is not used as the source for the parser - if you modify the content in the text box, it will use the modified version. A file name is needed because F# may need to resolve files referenced using #load and #r commands (these are available only when the file name ends with .fsx)
  • #r and #load - You can use the standard #load and #r commands in the source code to load other F# source files and reference additional .NET libraries. This is usually the easiest option to make the script type check.
  • Defined symbols - Here you can provide pre-processor symbols that are defined (so that the colorizer can properly recognize inactive code marked with #if)
  • Command line - The "ultimate" option that allows you to provide any command line parameters to the compiler. You'll need this if you want to reference some non-standard mscorlib library (e.g. Silverlight or XNA), which requires the --noframework flag. The format should be the same as for compiler options (if a path contains space, it can be wrapped in double quotes, e.g. -r:"C:\Program Files\a.dll")
  • Standard - Contains some common command line options for the tool. Since the tool is compiled using .NET 2.0, you need to use the drop down if you want to type-check against .NET 4.0 runtime. The other options are experimental. You can customize the settings here by editing the standard.xml file in the application directory.

There are also a couple of settings that configure the HTML generator, so if you want to change the look of the formatted snippet, here is what you can do:

  • CSS and JavaScript - The generated code uses CSS classes (such as c for comments, k for keywords etc.), so colors can be configured by editing the default style.css file (or by adding styles to your blog). The JavaScript code for showing tool tips can be modified in tips.js (and you also need to reference it from your blog).
  • Add line numbers - If the option is checked, the tool will add line numbers to the generated snippet (which makes the snippet nicer, but more difficult to copy & paste).
  • Prefix - Here you can set a unique prefix that will be used for id attributes in the generated HTML (you need to change this if you want to include snippets from multiple script files in a single page - to avoid name clashes in the generated HTML).
val isEven : int -> bool

Full name: Untitled.isEven
val n : int

  type: int
  implements: System.IComparable
  implements: System.IFormattable
  implements: System.IConvertible
  implements: System.IComparable<int>
  implements: System.IEquatable<int>
  inherits: System.ValueType
val formatInt : int -> string

Full name: Untitled.formatInt
Multiple items
val string : 'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------

type string = System.String

Full name: Microsoft.FSharp.Core.string

  type: string
  implements: System.IComparable
  implements: System.ICloneable
  implements: System.IConvertible
  implements: System.IComparable<string>
  implements: seq<char>
  implements: System.Collections.IEnumerable
  implements: System.IEquatable<string>
val res : string list

Full name: Untitled.res

  type: string list
  implements: System.Collections.IStructuralEquatable
  implements: System.IComparable<List<string>>
  implements: System.IComparable
  implements: System.Collections.IStructuralComparable
  implements: System.Collections.Generic.IEnumerable<string>
  implements: System.Collections.IEnumerable
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------

type List<'T> =
  | ( [] )
  | ( :: ) of 'T * 'T list
  with
    interface System.Collections.IEnumerable
    interface System.Collections.Generic.IEnumerable<'T>
    member Head : 'T
    member IsEmpty : bool
    member Item : index:int -> 'T with get
    member Length : int
    member Tail : 'T list
    static member Cons : head:'T * tail:'T list -> 'T list
    static member Empty : 'T list
  end

Full name: Microsoft.FSharp.Collections.List<_>

  type: List<'T>
  implements: System.Collections.IStructuralEquatable
  implements: System.IComparable<List<'T>>
  implements: System.IComparable
  implements: System.Collections.IStructuralComparable
  implements: System.Collections.Generic.IEnumerable<'T>
  implements: System.Collections.IEnumerable
val filter : ('T -> bool) -> 'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.filter
val map : ('T -> 'U) -> 'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
type Hello =
  class
    new : name:string -> Hello
    member SayHello : unit -> unit
  end

Full name: Untitled.Hello

Says hello to the specified person/entity
val name : string

  type: string
  implements: IComparable
  implements: ICloneable
  implements: IConvertible
  implements: IComparable<string>
  implements: seq<char>
  implements: Collections.IEnumerable
  implements: IEquatable<string>
val x : Hello
member Hello.SayHello : unit -> unit

Full name: Untitled.Hello.SayHello

Display 'Hello <name>' message in console
type Console =
  class
    static member BackgroundColor : System.ConsoleColor with get, set
    static member Beep : unit -> unit
    static member Beep : int * int -> unit
    static member BufferHeight : int with get, set
    static member BufferWidth : int with get, set
    static member CapsLock : bool
    static member Clear : unit -> unit
    static member CursorLeft : int with get, set
    static member CursorSize : int with get, set
    static member CursorTop : int with get, set
    static member CursorVisible : bool with get, set
    static member Error : System.IO.TextWriter
    static member ForegroundColor : System.ConsoleColor with get, set
    static member In : System.IO.TextReader
    static member InputEncoding : System.Text.Encoding with get, set
    static member KeyAvailable : bool
    static member LargestWindowHeight : int
    static member LargestWindowWidth : int
    static member MoveBufferArea : int * int * int * int * int * int -> unit
    static member MoveBufferArea : int * int * int * int * int * int * char * System.ConsoleColor * System.ConsoleColor -> unit
    static member NumberLock : bool
    static member OpenStandardError : unit -> System.IO.Stream
    static member OpenStandardError : int -> System.IO.Stream
    static member OpenStandardInput : unit -> System.IO.Stream
    static member OpenStandardInput : int -> System.IO.Stream
    static member OpenStandardOutput : unit -> System.IO.Stream
    static member OpenStandardOutput : int -> System.IO.Stream
    static member Out : System.IO.TextWriter
    static member OutputEncoding : System.Text.Encoding with get, set
    static member Read : unit -> int
    static member ReadKey : unit -> System.ConsoleKeyInfo
    static member ReadKey : bool -> System.ConsoleKeyInfo
    static member ReadLine : unit -> string
    static member ResetColor : unit -> unit
    static member SetBufferSize : int * int -> unit
    static member SetCursorPosition : int * int -> unit
    static member SetError : System.IO.TextWriter -> unit
    static member SetIn : System.IO.TextReader -> unit
    static member SetOut : System.IO.TextWriter -> unit
    static member SetWindowPosition : int * int -> unit
    static member SetWindowSize : int * int -> unit
    static member Title : string with get, set
    static member TreatControlCAsInput : bool with get, set
    static member WindowHeight : int with get, set
    static member WindowLeft : int with get, set
    static member WindowTop : int with get, set
    static member WindowWidth : int with get, set
    static member Write : bool -> unit
    static member Write : char -> unit
    static member Write : char [] -> unit
    static member Write : float -> unit
    static member Write : decimal -> unit
    static member Write : float32 -> unit
    static member Write : int -> unit
    static member Write : uint32 -> unit
    static member Write : int64 -> unit
    static member Write : uint64 -> unit
    static member Write : obj -> unit
    static member Write : string -> unit
    static member Write : string * obj -> unit
    static member Write : string * obj [] -> unit
    static member Write : string * obj * obj -> unit
    static member Write : char [] * int * int -> unit
    static member Write : string * obj * obj * obj -> unit
    static member Write : string * obj * obj * obj * obj -> unit
    static member WriteLine : unit -> unit
    static member WriteLine : bool -> unit
    static member WriteLine : char -> unit
    static member WriteLine : char [] -> unit
    static member WriteLine : decimal -> unit
    static member WriteLine : float -> unit
    static member WriteLine : float32 -> unit
    static member WriteLine : int -> unit
    static member WriteLine : uint32 -> unit
    static member WriteLine : int64 -> unit
    static member WriteLine : uint64 -> unit
    static member WriteLine : obj -> unit
    static member WriteLine : string -> unit
    static member WriteLine : string * obj -> unit
    static member WriteLine : string * obj [] -> unit
    static member WriteLine : char [] * int * int -> unit
    static member WriteLine : string * obj * obj -> unit
    static member WriteLine : string * obj * obj * obj -> unit
    static member WriteLine : string * obj * obj * obj * obj -> unit
  end

Full name: System.Console
Multiple overloads
Console.WriteLine() : unit
Console.WriteLine(value: string) : unit
Console.WriteLine(value: obj) : unit
Console.WriteLine(value: uint64) : unit
Console.WriteLine(value: int64) : unit
Console.WriteLine(value: uint32) : unit
Console.WriteLine(value: int) : unit
Console.WriteLine(value: float32) : unit
Console.WriteLine(value: float) : unit
Console.WriteLine(value: decimal) : unit
   (+9 other overloads)
val hello : Hello
member Hello.SayHello : unit -> unit

Display 'Hello <name>' message in console
type Counter =
  class
    new : init:int -> Counter
    member Decrement : int
    member Increment : int
  end

Full name: Untitled.Counter

Mutable counter that can be incremented and decremented
val init : int

  type: int
  implements: System.IComparable
  implements: System.IFormattable
  implements: System.IConvertible
  implements: System.IComparable<int>
  implements: System.IEquatable<int>
  inherits: System.ValueType
let mutable n = init
val x : Counter
member Counter.Increment : int

Full name: Untitled.Counter.Increment
n <- n + 1
n
member Counter.Decrement : int

Full name: Untitled.Counter.Decrement
n <- n - 1
n
val mutable n : int

  type: int
  implements: System.IComparable
  implements: System.IFormattable
  implements: System.IConvertible
  implements: System.IComparable<int>
  implements: System.IEquatable<int>
  inherits: System.ValueType
property Counter.Increment: int
property Counter.Decrement: int

Published: September 28, 2010 03:20