Tweaking Sublime Text themes to take advantage of Retina displays on Windows

Jan 6, 2015

Sublime Text already supports Retina displays on OS X, however it seems that the story is currently a bit different on Windows.

Update – Monday, November 14, 2016: To make this a whole lot easier, I’ve forked buymeasoda/soda-theme into moodmosaic/soda-theme. If you clone moodmosaic/soda-theme, it will show nicely on Retina displays on Windows.

Problem

You experience fuzzy fonts when using Sublime Text with Retina displays on Windows, even after updating to a newer version of Sublime Text.

Manual solution

  • Navigate to the theme’s root folder
  • Search for images matching the pattern *@2x.*
  • Copy and paste each image, in-place, by removing the @2x part from the filename, thus replacing any existing one.

The above technique has been tested with Sublime Text's Default Theme, and Soda Theme (both dark and light variants).

Soda Theme requires the following images to be excluded from the above process: tab-active, tab-hover, tab-inactive, tabset-background.

Automated solution

If done manually, this can take some time. – Here is a way to automate this with F#:

If you don't have the F# compiler and runtime, install it from here.

On a clean system without any edition of Visual Studio, the compiler, runtime, and F# Interactive will be deployed without IDE tooling.

Save the following F# program to a file:

module Sublime.Text.Windows

open System
open System.IO

type Theme = { Path : string; DpiAgnostics : string list }

let patch theme =
    Directory.GetFiles(theme.Path, "*@2x.*", SearchOption.AllDirectories)
    |> Seq.filter
        (fun candidate ->
            theme.DpiAgnostics
            |> Seq.exists
                (fun agnostic ->
                    Path.GetFileNameWithoutExtension(candidate) = agnostic)
            |> not)
    |> Seq.iter
        (fun hiDpi ->
            let lowDpi = hiDpi.Replace("@2x", String.Empty)
            File.Delete lowDpi
            File.Copy(hiDpi, lowDpi))
    ()

Assuming the above program is saved as Program.fs in X:\Tools.

  • Before opening Sublime Text, add the compiler and F# Interactive executables’ folder to PATH. – On my machine they are located in C:\Program Files (x86)\Microsoft SDKs\F#\x.x\Framework\vx.x.
  • Open Sublime Text
  • Install SublimeREPL.
  • Go to Tools > SublimeREPl > F#
  • In the F# Interactive prompt type, #I @"X:\Tools", followed by #load "Program.fs".

You should see something like:

namespace FSI_0002.Sublime.Text.Windows
  type Theme =
    {Path: string;
     DpiAgnostics: string list;}
  val patch : theme:Theme -> unit

Once you open Sublime.Text.Windows, execute the patch function by supplying a value for theme.

Soda Theme

let sodaTheme = {
    Path = @"replace_this_with_the_soda_theme_path";
    DpiAgnostics = [
            "tab-active@2x"
          ; "tab-hover@2x"
          ; "tab-inactive@2x"
          ; "tabset-background@2x" ]
} // In one line for SublimeREPL.
patch sodaTheme

Default Theme

Default Theme is .zipped in \Packages\Theme - Default.sublime-package and has to be unzipped first, patched, and then zipped back again.

let defaultTheme = {
    Path = @"replace_this_with_the_unzipped_theme_path";
    DpiAgnostics = []
} // In one line for SublimeREPL.
patch defaultTheme

Enjoy Sublime Text with Retina displays on Windows!

In-memory Drain abstractions applied to A Functional Architecture with F#

Jul 28, 2014

This post shows how to apply the Drain filter abstraction to the master branch of the Pluralsight course about A Functional Architecture with F# by Mark Seemann.

As the diff-output shows, applying filtering with Drains cuts the maintenance of multiple homogenous abstractions, and makes the code cleaner and easier to reason about.

For or a better diff-output highlighting experience, there is also a Gist available here.

To apply the diff, access the source code by getting a Pluralsight subscription which is totally worth it for this course.

GitHub README layouts

Jun 1, 2014

In most common scenarios, README files can have one of the following layouts.

Basic

  • Introduction
  • Where to download?
  • License
  • Versioning scheme
  • Build instructions
  • Contributing guidelines
  • Credits

The basic layout might seems too much in the beginning, but I think this is quite essential and valuable information that each open source project could have.

Being able to scan through each of these sections saves a lot of time.

Extended

  • Basic +
  • What problem does the project address?
  • Who cares?
  • How does it work?

Including these additional sections in the README almost eliminates the need to write an introductory blog post about the project.

Mature

  • Extended +
  • Open source maturity level1
  • Documentation
  • Questions
  • Who uses it?
  • Additional resources

As the project becomes bigger and successful, these additional sections tend to minimize potential duplicate questions on Stack Overflow and questions as issues on GitHub.


1 The description for the maturity levels of open source code can be found here.

AutoFixture via Edge.js sample adapter-functions

May 19, 2014

If you build Node.js applications, you can use AutoFixture via Edge.js. The sample adapter-functions from the screencast are availabe as a gist.

Mountebank Stubs with F#

May 2, 2014

The previous post described what Mountebank is, as well as how to use Mountebank imposters as Mocks via HTTP and F#.

This post describes how to use Mountebank imposters as Stubs via HTTP, xUnit.net, and F#.

Scenario

The Stub example in Mountebank API documentation simulates a RESTful endpoint that creates a Customer, the first time it’s called, and returns a 400 Bad Request the second time it’s called with the same Customer because the email address already exists.

Intercepting imposter Stub setup

When using Mountebank imposters as Stubs we typically care about configuring the Stub’s return values - the rest of the fixture setup and fixture teardown phases are always the same.

If fixture setup and fixture teardown phases are always the same, they could be extracted into an Interceptor class.

In xUnit.net we can do this by inheriting from BeforeAfterTestAttribute:

type UseImposterStubAttribute (mountebankHost, mountebankPort, imposterJson) =
    inherit BeforeAfterTestAttribute()

    override this.Before (methodUnderTest : MethodInfo) =
        Http.Request(
            UriBuilder(
                "http", 
                mountebankHost, 
                mountebankPort, 
                "imposters/").ToString(),
            headers = [ 
              "Content-Type", HttpContentTypes.Json; 
              "Accept"      , HttpContentTypes.Json ],
            httpMethod = "POST",
            body = TextRequest imposterJson)
        |> ignore

    override this.After (methodUnderTest : MethodInfo) =
        Http.Request(
            UriBuilder(
                "http", 
                mountebankHost, 
                mountebankPort, 
                "imposters/" + ParsePortFrom(imposterJson)).ToString(),
            headers = [ 
                "Content-Type", HttpContentTypes.Json; 
                "Accept"      , HttpContentTypes.Json ],
            httpMethod = "DELETE")
        |> ignore

Using imposter Stubs with xUnit.net

With xUnit.net and the UseImposterStubAttribute, the original Stub example can be written as:

[<Fact; UseImposterStub(
    "192.168.1.4", 
    2525, 
    @"
    {
      ""port"":4545,
      ""protocol"":""http"",
      ""stubs"":[
        {
          ""responses"":[
            {
              ""is"":{
                ""statusCode"":201,
                ""headers"":{
                  ""Location"":""http://localhost:4545/customers/123"",
                  ""Content-Type"":""application/xml""
                },
                ""body"":""<customer><email>customer@test.com</email></customer>""
              }
            },
            {
              ""is"":{
                ""statusCode"":400,
                ""headers"":{
                  ""Content-Type"":""application/xml""
                },
                ""body"":""<error>email already exists</error>""
              }
            }
          ]
        }
      ]
    }"
)>]
let createDuplicateCustomerThrows () =
    let expected = 201
    let mutable secondRequestHasFailed = false
    
    let actual = 
        Create "<customer><email>customer@test.com</email></customer>"
    try
        Create "<customer><email>customer@test.com</email></customer>" |> ignore
    with
    | e -> if e.Message.Contains("email already exists") 
           then secondRequestHasFailed <- true
           
    verify <@ actual.StatusCode = expected && secondRequestHasFailed @>

The UseImposterStubAttribute arguments are

  • the Mountebank server host
  • the Mountebank server port number
  • the imposter (protocol, port, and return values) specified in the JSON setup

When running the test, the output on the Mountebank server console is:

info: [mb:2525] POST /imposters/
info: [http:4545] Open for business...
info: [http:4545] 192.168.1.5:62897 => POST /imposters/
info: [http:4545] 192.168.1.5:62898 => POST /imposters/
info: [mb:2525] DELETE /imposters/4545
  • During the setup phase an imposter Stub is created via HTTP POST using the the Mountebank URL and imposter protocol (http) and port (4545) defined in the UseImposterStubAttribute.
  • During the teardown phase the imposter Stub is removed.

The important part is that the UseImposterStubAttribute removes the repetitive imposter setup and teardown steps from the actual test.

TCP Stubs

Brandon Byars, the creator of mountebank, provided an example of how to create TCP imposter Stubs with Java.

It is now easy to do something similar with F#, xUnit.net and the UseImposterStubAttribute:

[<Fact; UseImposterStub(
    "192.168.1.4", 
    2525, 
    @"
    {
      ""protocol"":""tcp"",
      ""port"":""4546"",
      ""mode"":""binary"",
      ""stubs"":[
        {
          ""responses"":[
            {
              ""is"":{
                ""data"": ""<encoded string>""
              }
            }
          ]
        }
      ]
    }"
)>]
let queryWithCancelledTravelPlan () = 
    // F#-pseudocode conversion from the original Java sample.
    let expected = { travelPlan with Status = "Cancelled" }
    let actual = sut.Query("http://travelPlans/1234&date=2013-02-15");
    verify <@ actual .. expected @>

The complete source code is available on this gist - any comments or suggestions are always welcome.

subscribe via RSS