Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a4408ef
Showing
9 changed files
with
702 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# Terminal Keynote | ||
|
||
![Terminal Keynote Cover](https://raw.github.com/fxn/tkn/master/screenshots/terminal-keynote-cover.png) | ||
|
||
## Introduction | ||
|
||
Terminal Keynote is a quick and dirty script I wrote for presenting my talks at [BaRuCo 2012](http://baruco.org) and [RailsClub 2012](http://railsclub.ru). | ||
|
||
This is a total hack. It is procedural, uses a global variable, it has not been parametrized or generalized in anyway. It was tailor-made for what I exactly wanted but some people in the audience asked for the script. Even if it is quick and dirty I am very happy to share it so I have commented the source code and there you go! | ||
|
||
## Markup | ||
|
||
Fuck markup, this is text going to a terminal. If you want a list type "*"s. If you want bold face or colors use ANSI escape sequences. | ||
|
||
Slides are written in Ruby. See the examples folder. | ||
|
||
## Syntax Highlighting | ||
|
||
Terminal Keynote is text-based, but with style! Syntax highlighting is done on the fly with @tmm1's [pygments.rb](https://github.com/tmm1/pygments.rb). The script uses the "terminal256" formatter and "bw" style, the lexer is also hard-coded to "ruby". Since this was tailor-made it has not been factored out. | ||
|
||
## Master Slides | ||
|
||
There are four types of slides: | ||
|
||
### :code | ||
|
||
A slide with source code. Syntax highlighted on the fly. If you want to put a title or file name or something use source code comments and imagination. | ||
|
||
![Terminal Keynote Code](https://raw.github.com/fxn/tkn/master/screenshots/terminal-keynote-code.png) | ||
|
||
### :center | ||
|
||
A slide whose text is centered line by line. | ||
|
||
![Terminal Keynote Center](https://raw.github.com/fxn/tkn/master/screenshots/terminal-keynote-center.png) | ||
|
||
### :block | ||
|
||
A slide with text content whose formatting is preserved, but that is centered as a whole in the screen. Do that with CSS, ha! | ||
|
||
I find centering content in the screen as a block to be more aesthetically pleasant that flushing against the left margin. There is not way to flush against a margin. | ||
|
||
![Terminal Keynote Block](https://raw.github.com/fxn/tkn/master/screenshots/terminal-keynote-block.png) | ||
|
||
### Sections | ||
|
||
Sections have a title and draw kind of a fleuron. This is also hard-coded because it is what I wanted. | ||
|
||
Sections allow you to group slides in your Ruby slide deck, and since they yield to a block you can collapse/fold the ones you are not working in for focus. | ||
|
||
The nested structure is not modelled internally. The script only sees a flat linear sequence of slides. | ||
|
||
![Terminal Keynote Section](https://raw.github.com/fxn/tkn/master/screenshots/terminal-keynote-section.png) | ||
|
||
## Visual Effects | ||
|
||
There is a hard-coded visual effect: Once the exact characters of a given slide are computed, we print char by char with a couple milliseconds in between. That gives the illusion of an old-school running cursor effect. Configure block blinking cursor for maximum awesomeness. | ||
|
||
## Installation | ||
|
||
By now this is not going to be a gem, please clone the repo and hack your talk. In its current state it is just too tailor-made for anything but personal forks. Please keep the script together with the slides, that way you guarantee one year later the presentation will still run. | ||
|
||
If Terminal Keynote evolves it is going to do so in backwards incompatible ways for sure. So, let's wait. If the thing ever converges to something that can be packaged then I'll do it. | ||
|
||
## Keyboard Controls and Remotes | ||
|
||
* To go forward press any of " ", "n", "k", "l", PageDown (but see below). | ||
|
||
* To go backwards press any of "b", "p", "h", "j", PageUp (but see below). | ||
|
||
* Beginning of the talk: "^" | ||
|
||
* End of the talk: "$" | ||
|
||
My Logitech remote emits PageDown and PageUp. You get those as ANSI escape sequences "\e[5~" and "\e[6~" respectively. The script understands them, but you need to [configure them in Terminal.app](http://fplanque.com/dev/mac/mac-osx-terminal-page-up-down-home-end-of-line) and also tell it to pass them down to the shell selecting "send string to the shell" in the "Action" selector. | ||
|
||
## Font and Terminal Configuration | ||
|
||
I used Menlo, 32 points. That gives 18x52 in a screen resolution of 1024x768. | ||
|
||
For your setup: Find out the resolution of the projector of your conference (ask the organization in advance). Set the screen to that resolution, choose font size and maximize window. When you like how it looks, then run `stty size` and write down rows and cols. | ||
|
||
Then, define in your terminal application a profile for the theme you like, and initial dimensions to those rows and cols. That way the terminal will launch with those dimensions no matter the screen resolution and you can hack your talk in your day to day with the native resolution, knowing how is going to look in proportion. | ||
|
||
## Editor Snippets | ||
|
||
A snippet for your editor is basic to write slides quickly. The extras folder has a snippet for Sublime Text 2. | ||
|
||
## Cathode | ||
|
||
[Cathode](http://www.secretgeometry.com/apps/cathode/) is perfect for this thing. But because of how it draws the text it doesn't do bold faces and may not be able to render some colors or Unicode characters. YMMV. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
#!/usr/bin/env ruby | ||
# encoding: utf-8 | ||
|
||
# UTF-8 ALL THE THINGS. | ||
Encoding.default_external = 'utf-8' | ||
Encoding.default_internal = 'utf-8' | ||
|
||
require 'active_support/core_ext/string/strip' | ||
require 'pygments' | ||
|
||
|
||
# | ||
# --- DSL ------------------------------------------------------------- | ||
# | ||
|
||
def slide(content, format=:block) | ||
$slides << [content.strip_heredoc, format] | ||
end | ||
|
||
def section(content) | ||
$slides << [content, :section] | ||
yield | ||
end | ||
|
||
|
||
# | ||
# --- ANSI Escape Sequences ------------------------------------------- | ||
# | ||
|
||
# Clears the screen and leaves the cursor at the top left corner. | ||
def clear_screen | ||
"\e[2J\e[H" | ||
end | ||
|
||
# Puts the cursor at (row, col), 1-based. | ||
# | ||
# Note that characters start to get printed where the cursor is. So, to leave | ||
# a left margin of 8 characters you want col to be 9. | ||
def cursor_at(row, col) | ||
"\e[#{row};#{col}H" | ||
end | ||
|
||
|
||
# | ||
# --- Utilities ------------------------------------------------------- | ||
# | ||
|
||
# Returns the width of the content, defined as the maximum length of its lines | ||
# discarding trailing newlines if present. | ||
def width(content) | ||
content.each_line.map do |line| | ||
ansi_length(line.chomp) | ||
end.max | ||
end | ||
|
||
# Quick hack to compute the length of a string ignoring the characters that | ||
# represent ANSI escape sequences. This only supports a handful of them, the | ||
# ones that I want to use. | ||
def ansi_length(str) | ||
str.gsub(/\e\[(2J|\d*(;\d+)*(m|f|H))/, '').length | ||
end | ||
|
||
# Returns the number of rows and columns of the terminal as an array of two | ||
# integers [rows, cols]. We could use io/console here but shelling out is also | ||
# fine. | ||
def winsize | ||
`stty size`.split.map(&:to_i) | ||
end | ||
|
||
|
||
# | ||
# --- Slide Rendering ------------------------------------------------- | ||
# | ||
|
||
# Returns a string that the caller has to print as is to get the slide | ||
# properly rendered. The caller is responsible for clearing the screen. | ||
def render(slide) | ||
send("render_#{slide[1]}", slide[0]) if slide[0] =~ /\S/ | ||
end | ||
|
||
# Renders the content by centering each individual line. | ||
def render_center(content) | ||
nrows, ncols = winsize | ||
|
||
''.tap do |str| | ||
nlines = content.count("\n") | ||
row = [1, 1 + (nrows - nlines)/2].max | ||
content.each_line.with_index do |line, i| | ||
col = [1, 1 + (ncols - ansi_length(line.chomp))/2].max | ||
str << cursor_at(row + i, col) + line | ||
end | ||
end | ||
end | ||
|
||
# Renders a section banner. | ||
def render_section(content) | ||
nrows, ncols = winsize | ||
width = width(content) | ||
|
||
rfil = [1, width - 5].max/2 | ||
lfil = [1, width - 5 - rfil].max | ||
fleuron = '─' * lfil + ' ❧❦☙ ' + '─' * rfil | ||
|
||
render_center("#{fleuron}\n\n#{content}\n\n#{fleuron}\n") | ||
end | ||
|
||
# Renders Ruby source code. | ||
def render_code(code) | ||
render_block(Pygments.highlight(code, formatter: 'terminal256', lexer: 'ruby', options: {style: 'bw'})) | ||
end | ||
|
||
# Centers the whole content as a block. That is, the format within the content | ||
# is preserved, but the whole thing looks centered in the terminal. I think | ||
# this looks nicer than an ordinary flush against the left margin. | ||
def render_block(content) | ||
nrows, ncols = winsize | ||
|
||
nlines = content.count("\n") | ||
row ||= [1, 1 + (nrows - nlines)/2].max | ||
|
||
width = width(content) | ||
col ||= [1, 1 + (ncols - width)/2].max | ||
|
||
content.gsub(/^/) do | ||
cursor_at(row, col).tap { row += 1 } | ||
end | ||
end | ||
|
||
|
||
# | ||
# --- Main Loop ------------------------------------------------------- | ||
# | ||
|
||
# Reads either one single character or PageDown or PageUp. You need to | ||
# configure Terminal.app so that PageDown and PageUp get passed down the | ||
# script. Echoing is turned off while doing this. | ||
def read_command | ||
begin | ||
system 'stty raw -echo' | ||
command = STDIN.getc | ||
# Consume PageUp or PageDown if present. No other ANSI escape sequence is | ||
# supported so a blind 3.times getc is enough. | ||
3.times { command << STDIN.getc } if command == "\e" | ||
command | ||
ensure | ||
system "stty -raw echo" | ||
end | ||
end | ||
|
||
n = 0 | ||
loop do | ||
print clear_screen | ||
|
||
# We load the presentation in every iteration to ease editing and reload. | ||
# This is fast enough, so who cares about caching. | ||
$slides = [] | ||
load ARGV[0] | ||
|
||
n = [[0, n].max, $slides.length - 1].min | ||
render($slides[n]).each_char do |c| | ||
print c | ||
sleep 0.002 # old-school touch: running cursor | ||
end | ||
|
||
case read_command | ||
when ' ', 'n', 'l', 'k', "\e[5~" | ||
n += 1 | ||
when 'b', 'p', 'h', 'j', "\e[6~" | ||
n -= 1 | ||
when '^' | ||
n = 0 | ||
when '$' | ||
n = $slides.length - 1 | ||
when 'q' | ||
print clear_screen | ||
exit | ||
end | ||
end |
Oops, something went wrong.