An interesting CSS hack for highlighting S-expressions

The Community-Scheme-Wiki has a pretty interesting way of highlighting lispy code

Scheme being a Lisp dialect, it makes sense to highlight the S-expressions, i.e. “things between parenthesis”. The Community Scheme Wiki does exactly that. As you move your mouse over the code, it will highlight the s-expression you’re in and the ones around in different colors, allowing you to quickly make sense of the code.

An example can be found on this page, which renders like this as you move your mouse:

basic highlighting

half highlighting

full highlighting

How do they do this, you ask? Let’s check the HTML:

<span class="comment">;;; SAFE? tests if the queen in a file is safe from attack.</span>
<span class="paren">(<span class="keyword">define</span> <span class="paren">(safe? file board)</span>
    <span class="paren">(<span class="keyword">define</span> <span class="paren">(get-queen-by-file file board)</span>
        <span class="paren">(find-first <span class="paren">(<span class="keyword">lambda</span> <span class="paren">(queen)</span>
                <span class="paren">(= <span class="paren">(queen-file queen)</span> file)</span>)</span>
            board)</span>)</span>

(Tags linking to documentation were removed for clarity’s sake)

Nothing particularly fishy going on here. We can only see that each S-expression is wrapped into a span of the paren class.

So what is that class? Let’s dig into the CSS:

/* The paren stuff */

/* Top level */
PRE.scheme > SPAN.paren:hover { background-color: #FFCFCF }

/* Paren level 1 */
PRE.scheme > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFCF }

/* Paren level 2 */
PRE.scheme > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFCFFF }

/* Paren level 3 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFFF }

/* Paren level 4 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #FFCFFF }

/* Paren level 5 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #FFFFCF }

/* Paren level 6 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren
> SPAN.paren:hover { background-color: #B4E1EA }

/* Paren level 7 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #BDEAB4 }

/* Paren level 8 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #EAD4B4 }

/* Paren level 9 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #F4D0EC }

/* Paren level 10 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #D0D9F4 }

/* Paren level 11 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren
> SPAN.paren:hover { background-color: #FFCFCF }

/* Paren level 12 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFCF }

/* Paren level 13 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFCFFF }

/* Paren level 14 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFFF }

/* Paren level 15 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #FFCFFF }

/* Paren level 16 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren
> SPAN.paren:hover { background-color: #FFFFCF }

/* Paren level 17 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #BDEAB4 }

/* Paren level 18 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #EAD4B4 }

/* Paren level 19 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #F4D0EC }

/* Paren level 20 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #D0D9F4 }

/* Paren level 21 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren
> SPAN.paren:hover { background-color: #FFCFCF }

/* Paren level 22 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFCF }

/* Paren level 23 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFCF }

PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:before { content: "{{23 levels of indentation?! Yiakes!}}" }

/* extend here if more nestings are needed */

So they are actually using the fact that CSS can recognize whether a class is nested into the same class, and display it in a different manner for each level when you :hover over that span. The interesting part is that the browser also keeps the S-expressions under the current hovered one highlighted in their specific color.

Hats off!

Deep sleep on MacBook Air Late 2010

Apple’s latest MacBook Air boasts to last 30 days on battery when sleeping. Classical sleep will power down most of the machine (display, processor, hard drive, etc) but keep the RAM powered on in order to keep the state of the OS. However, the RAM drains too much power to realistically allow more than a week or so on a single charge.

The MBA feat is achieved through “deep sleep”, i.e. writing the RAM contents to a file on the hard drive, which allows powering down the RAM without losing state (also known as hibernation). The only problem is that waking the computer up requires loading back all of this data on RAM, which takes a few seconds.

Apple even uses a trickier approach by default: the computer sleeps normally at first, and switches to deep sleep after a certain amount of time.

This is very easy to see on the MBA: close the lid and reopen it a few minutes later, waking up is super fast. Leave it sleeping for a day, and it will take about ten seconds before you can do anything with it.

While this is a clever solution, the parameters aren’t that well adjusted — the computer goes to deep sleep too fast in my opinion.

This can be modified through pmset. First, run it to see what are the active parameters:

Air:~ florent$ pmset -g
Active Profiles:
Battery Power		-1*
AC Power		-1
Currently in use:
 deepsleepdelay	4200
 halfdim	0
 hibernatefile	/var/vm/sleepimage
 disksleep	10
 sleep		10
 hibernatemode	3
 displaysleep	5
 ttyskeepawake	1
 deepsleep	1
 acwake		0
 lidwake	1

The interesting one is deepsleepdelay (edit: see footnotes). It represents the time in seconds before switching from classical sleep to deep sleep. 4200 seconds, 70 minutes, is way too short. Let’s set it to 24hrs instead:

Air:~ florent$ sudo pmset -a deepsleepdelay 86400

There it is! Deep sleep will still work, but won’t be as annoying.

Edit July 2, 2011:
It looks like Apple changed the name of that setting from deepsleepdelay to standbydelay during the update to OS X 10.6.8. The command line has to be changed accordingly:

Air:~ florent$ sudo pmset -a standbydelay 86400

Semibold keyboard shortcut in Pages

So… Turns out there is no shortcut to turn text to semibold in Apple Pages, from the iWork suite. There are shortcuts for bold and italic respectively, but not semibold (or light / ultralight for that matter) even for the fonts that support it.

The closest thing to a solution that I found is through Character Style:
– Select a piece of text, make it semibold
– In the Styles Drawer, under Character Styles, click on the little arrow next to “none” and “Create New Character Style From Selection
– Assign a Hot Key to the newly created Character Style, by clicking the arrow next to it and Hot Key.

There are a few drawbacks to this approach, mainly that the character style you’re defining will be tied to a particular font, and that the only shortcuts allowed by the Hot Key setting are F1 to F6. If anyone has a better way, I’m all ears!

Automatically restart applications on OS X

I use GimmeSomeTune to provide hotkeys and some other goodies for iTunes. It works alright, but is veeeery crashy — usually every dozen hours or so on my machine.

How to fix that? Let’s relaunch it as soon as it crashes. Simple!

In a terminal:
for (( ; ; )); do open -W /Applications/Multimedia/GimmeSomeTune.app/; done

open is the bash command to launch applications on OS X. It works with all kinds of files: open somefile.avi will open that file in your default video player, VLC for example. The -W flag tells open to wait until the application exits before returning any value. By putting it all in a for loop, we effectively ensure that bash will launch GimmeSomeTune, wait until it crashes, then relaunch it, and so on.

Edit: this is a bad way of doing things. A better way is described here.

nginx as a reverse-proxy to Apache+Sinatra

I was recently developing a Sinatra app that wanted to host from home — setting it up Heroku would have meant migrating from SQLite to Postgres, and I’m lazy. The problem was that I already happened to have an Apache server at home to serve some other content, specifically some calendars through the WebDAV module.

The solution I used was simple: instead of having Apache listening on port 80, I set up nginx to listen to port 80 and redirect to either Apache (set to listen on port 8080 instead) or Sinatra (port 9393) depending on the URL.

 

Nginx configuration was dead simple (after a few tries). The gist of it being these few lines:

location / {
    proxy_pass   http://127.0.0.1:8080/;
}
location /reorg {
    proxy_pass http://127.0.0.1:9393;
}

The configuration in its entirety is available here.

 

After setting up this solution, I realized Apache’s mod_proxy would have done the trick without the need for nginx. And that mod_rack (Phusion Passenger) could probably have eliminated the need for running a specific ruby process for Sinatra at all. Live and learn!