How to duplicate an iTerm tab using AppleScript

The terminal is my number one programming tool. And, as such, I need it to be as much ‘out of my way’ as possible, so that using the terminal doesn’t make me waste time. And wasting time is exactly what it forces me to do when I have to open a new tab in the same directory as an already existing tab. Whether you do this

  • by browsing the disk until finding the folder, then creating a new tab, writing cd and dragging the path of the directory; or
  • by browsing the file hierarchy with Quicksilver and then triggering a Quicksilver action to open the folder in iTerm;

or whatever other way you do this, it’s definitely a time waste.

Adding a new keyboard shortcut to iTerm 2

Many applications have a ⌘ D shortcut that allows you to duplicate whatever document the application works with. Why doesn’t a terminal emulator have that shortcut to duplicate a terminal tab? iTerm 2 certainly doesn’t have one, but it does have a preference panel where you can add new keyboard shortcuts:

To add a new global shortcut, just click on the + button. This will open the shortcut addition dialog, where you’ll enter the keyboard shortcut (⌘ D), the action to trigger (Send text), and the command to be sent to the terminal (duplicate-iterm-tab) when the shortcut is triggered:

Notice that the command duplicate-iterm-tab is to be executed when sent to the terminal, so a line feed must be entered at the end. Since pressing ↩ in the dialog will apply changes, to enter a new line you must press ⌥ ↩ instead.

Finally, click OK to create the new shortcut and then you’ll see the new shortcut in the Keys preference panel:

An AppleScript that duplicates the iTerm tab in use

The following AppleScript code should be written to the Duplicate iTerm tab.scpt file, and will get the working directory of the current iTerm tab and create a new tab, changing to that very same directory, and setting the same tab title (thus duplicating the tab you’ve selected before pressing ⌘ D):

-- Duplicate iTerm tab.scpt
tell application "iTerm"
	tell the current terminal
		tell the current session
			set the_name to get name
			set tty_name to do shell script "basename " & (get tty)
			set working_dir to do shell script "pwd-tty " & tty_name
		end tell

		launch session "Default Session"
		tell the last session
			set name to the_name
			write text "cd '" & working_dir & "'"
		end tell
	end tell
end tell

Necessary files for the shortcut to work

For the keyboard shortcut to work from iTerm 2, the Duplicate iTerm tab.scpt file must be placed in some directory of your choice (I’ve chosen ~/shell-config/osx-automation/Duplicate iTerm tab.scpt), and the following files are needed alongside:

  • duplicate-iterm-tab, the shell script that will be run by iTerm 2 when the keyboard shortcut is triggered, and that will execute the AppleScript
  • pwd-tty, a shell script used by the AppleScript to find out the current working directory of the TTY whose name is passed as the first command line argument

Both shell scripts must be in the PATH. I’ll keep them in ~/shell-config/bin, since files placed in there are by default in the PATH (according to how I’ve set up my Bash configuration). If you don’t want to add them to the PATH, you can just change the AppleScript code, writing the absolute path to each script instead.

The file duplicate-iterm-tab has the following content:

#!/bin/sh
#
# Runs the 'Duplicate iTerm tab' AppleScript, enabling iTerm 2 to run this AppleScript when a
# given keyboard shortcut is triggered.
osascript '~/shell-config/osx-automation/Duplicate iTerm tab.scpt'

Please note that you must change the path of the AppleScript to whatever directory you’ve placed it into.

The file pwd-tty has the following content:

#!/bin/sh
#
# Finds out the current working directory of the TTY whose name is passed as the first command
# line argument.
#
# Based on http://superuser.com/questions/220679/how-to-get-tty-shell-working-directory

TTY=$1
PID=`ps -f | grep $TTY | head -n 1 | awk '{ print $2; }'`
lsof -a -d cwd -F n -p $PID | egrep -v 'p[0-9]+' | awk '{ sub(/^n/, ""); print; }'

To finish setting all up, you should give both shell scripts execution permissions with chmod +x.

Useful links

I’ve switched to iTerm 2! (a “new” terminal app for the Mac)

Yesterday, I switched to iTerm 2!

Since the very first time I used my first Mac for work, I replaced OS X’s Terminal.app with iTerm, because I didn’t feel enough comfortable with Terminal.app.

At that time (in 2007, when we used to run Tiger), iTerm was a quite good piece of software for the Mac (and it still is), with tabs, Unicode support, full-screen mode, profiles, bookmarks, and some more stuff. However, it doesn’t seem to be updated that much. From time to time, Sparkle notifies you that there is a new iTerm version available, but that’s very seldom, and they are most of all minor releases.

But yesterday, while I was struggling to get AppleScript to do what I was commanding it to do, I found out iTerm 2. iTerm 2 is actually not the next major release of iTerm, but a fork which intends to be iTerm’s successor. However, it’s got a quite remarkable bunch of new —and neat— features that iTerm lacks, and that’s what persuaded me to switch.

PS: Don’t forget to check out these iTerm Shell Customizations for bash, zsh and tcsh.

Open Quicksilver selection in new iTerm window / tab

I’m not very happy with the iTerm plugin for Quicksilver. It does provide an action for opening Quicksilver’s current selection (i.e. an alias to a directory) in iTerm, but it isn’t very fine tuned, actually.

What it does is just open current selection into a new tab in the first opened iTerm window, which is an unwanted behavior in the 99.999999% of the occasions. The most handy behavior would be that the Quicksilver action opened current selection either in a new window (if there are no iTerm windows in your current Space) or in a new tab in the frontmost iTerm window of the current Space (if there are iTerm windows in your current Space).

Writing the scripts

Given that this has bothered me since the very first time I used this Quicksilver action and that I’m surprisingly keen on hacking a bit, I’ve taken courage and started researching AppleScript in the net. After taking a quick look at some introductory material and taking some inspiration, and making some mistakes, I finally came up to this code with help of this “new tab in iTerm” example:

For opening Quicksilver’s selection (must be a directory, not a file) in a new iTerm window in current Space:

-- File: Open in new iTerm window.scpt

tell application "Quicksilver"
	set folder_path to POSIX path of (selection as text)
end tell

tell application "iTerm"
	activate
	set t to (make new terminal)
	tell t -- this creates a new window, unlike tell current terminal
		launch session "Default Session"
		tell last session
			write text "cd '" & folder_path & "'"
			set name to do shell script "basename '" & folder_path & "'"
		end tell
	end tell
end tell

For opening Quicksilver’s selection (must be a directory, not a file) in a new tab in the last iTerm window in current Space:

-- File: Open in new iTerm tab.scpt

tell application "Quicksilver"
	set folder_path to POSIX path of (selection as text)
end tell

tell application "iTerm"
	activate
	tell the current terminal
		activate current session
		launch session "Default Session"
		tell last session
			write text "cd '" & folder_path & "'"
			set name to do shell script "basename '" & folder_path & "'"
		end tell
	end tell
end tell

Adapting the scripts for making Quicksilver actions

The two previous AppleScript’s work perfectly outside of Quicksilver (e.g. when running them within AppleScript Editor). However, if you place these scripts in the Quikcsilver Actions’ directory and try to run them, they won’t work. To make them work, you must make some slight modifications (take a look at Quicksilver’s AppleScript documentation; my inspiration was the “Paste file path” action).

For opening Quicksilver’s selection (must be a directory, not a file) in a new iTerm window in current Space:

-- File: Open in new iTerm window.scpt

using terms from application "Quicksilver"
	on open folder
		set folder_path to POSIX path of folder

		tell application "iTerm"
			activate
			set t to (make new terminal)
			tell t -- this creates a new window, unlike tell current terminal
				launch session "Default Session"
				tell last session
					write text "cd '" & folder_path & "'"
					set name to do shell script "basename '" & folder_path & "'"
				end tell
			end tell
		end tell
	end open
end using terms from

For opening Quicksilver’s selection (must be a directory, not a file) in a new tab in the last iTerm window in current Space:

-- File: Open in new iTerm tab.scpt

using terms from application "Quicksilver"
	on open folder
		set folder_path to POSIX path of folder

		tell application "iTerm"
			activate
			tell the current terminal
				activate current session
				launch session "Default Session"
				tell last session
					write text "cd '" & folder_path & "'"
					set name to do shell script "basename '" & folder_path & "'"
				end tell
			end tell
		end tell
	end open
end using terms from

For Quicksilver to recognize these actions, you must place both AppleScript files into ~/Library/Application Support/Quicksilver/Actions/ and restart Quicksilver. After doing that, you can just fire up Quicksilver, select an alias to a directory in the first tab, and then select the wanted action in the action tab (second tab).

やった! 🙂

Useful links

SQLite Manager for OS X

I’m currently working on some web projects using Django, which is a pretty nice Python framework for web development, and I run all my tests with a SQLite database. During development, the models are very dynamic and its fields are always changing, so I need the database tables to reflect those changes, but the utilities bundled with Django only create tables, but don’t alter them when models change.

So, the way to overcome this workaround is quite simple: just drop the tables and run Django’s table creation utility again. However, that’s a bit of a hassle to do through SQL queries when you’ve got plenty of tables to drop.

For managing SQLite databases, I’ve always used sqliteman, which is multiplatform and free. There are binaries for Windows and Linux, and I compiled it on OS X and even packaged the binary into an OS X .app, so that I could just run it like any other OS X app. But sqliteman has still got an issue which makes it uncomfortable for droping a bunch of tables: in the table tree view, you cannot select more than one table to drop them at once, and, besides, there is no shortcut to drop tables (you must ctrl-click and delete tables from the context menu!). And I definitely do not want to drop tables one-by-one and by-hand, nor do I want to have to write a drop query for each table!

For managing MySQL databases from OS X, I’ve always used CocoaMySQL, which evolved to Sequel Pro. This has a pretty, native interface for OS X and… It allows you to select multiple tables and delete them like you’ve always wanted: with shift-selection! It doesn’t provide any shortcuts for deleting though, but I can live with that, and that gives you more safety, too (it prevents you from deleting by mistake).

So, the quest for the Holy Manager arises: I want a GUI manager for SQLite databases that runs on OS X, allows batch dropping of tables, and —of course— is free! A simple search for “sqlite manager interface mac” yields some results (though not all of them are free):

MesaSQLite looks promising, but it’s paid software and I didn’t get to know the limitations of the trial. Lita looks very simple, so at first sight I’m not sure if it will allow batch dropping, and also the look & feel is not suited for the OS X posh kids. Base is also quite promising and very good-looking, and the trial limits sessions to 15 minutes and custom SQL queries will only return 5 rows. Well, I don’t think it’ll take me 15 minutes to drop some tables, and dropping doesn’t yield any rows… But Base does not allow to drop many tables at once 😦 However, MesaSQLite does! Anyway, I’ll keep both, since Base might be useful some day, too.

PS: Yeah, I know Safari is a memory monster. I might switch some day 🙂

Reading URLs from OS X clipboard with PyObjC

I’m definitely lazy. I don’t like making efforts bigger than needed, under any circumstances. And, when it comes to downloading a (quite big) bunch of files, I don’t like doing it manually. That’s why I’m currently using jDownloader on my iMac. However, it’s still got an issue: when I copy URLs from Safari, it instantly recognizes that URLs have been copied to the clipboard and adds them for download; but, when I copy HTML links from Safari whose text is just text (not an URL), this does not work anymore, and I then have to copy the links one by one, which really bothers me, because I’m lazy (and because I have to waste a lot of time for that).

So, the problem is that everything works fine when I copy something like

http://example.com/

or something like

http://example.com/

but it doesn’t work anymore when the link is like

this is a link to an example domain

And, since I’m lazy, I decided I had to write a program to copy a bunch of links at once, without all that hassle. The first idea that came to my mind was to write a Python script that used mechanize to copy to the clipboard all URLs in a web page, but that didn’t seem like the prettiest solution at that moment.

But then a second approach occurred to me: I could just copy the web page’s text to the clipboard and let a program read the richt text, HTML or whatever from the clipboard and find the URLs. Besides, that seemed to be an interesting program to write. I started considering RubyCocoa for that, but a deep knowledge of sorcery is needed to be able to use Xcode, and I’m not such a good sorcerer. After that, my tests with Qt weren’t successful, so I decided to go search on the internet for some recipes to access the OS X clipboard with Python.

After digging for a while, I found and article on Python and the Mac Clipboard which pointed me in the right direction: use PyObjC to access the NSPasteboard class. After taking a look at the documentation and testing a bit, supported by Python’s built-in dir() and help() functions, I found that

  1. all of the items that are currently in the pasteboard are returned as an NSArray by NSPasteboard.pasteboardItems method,
  2. the list of all types to which each pasteboard item can be converted is returned as an NSArray by the NSPasteboardItem.types method, and
  3. all text copied from Safari can be accessed both as com.apple.webarchive with the NSPasteboardItem.dataForType_ method, and as public.rtf with the NSPasteboardItem.stringForType_ method.
Given that we’re interested in getting the URLs from the richt text representation, the best approach was to use regular expressions to extract all hyperlinks from the RTF document representation of the web page clip copied, resulting in the following Python code:
from AppKit import NSPasteboard

import re

pb = NSPasteboard.generalPasteboard()
pattern = re.compile(r'HYPERLINK "(.+)"')
for item in pb.pasteboardItems():
	if 'public.rtf' in item.types():
		matches = pattern.findall(item.stringForType_('public.rtf'))
		for url in matches:
			print url

You can then copy the output URLs manually to the clipboard, which would make jDownloader to recognize them, or you could also improve the script to add copying capabilities, so that the script would copy the output to the clipboard again.

Ruby eating up all my CPU resources

I’ve recently been programming in Ruby on both of my Macs, a MacBook Pro with an Intel Core 2 Duo and 3 GB RAM, and an iMac with an Intel Core i3 and 4 GB RAMs. Well, I’ve been mostly using irb, the Ruby interpreter, for loads and loads of tests while writing the actual code. I usually test fragments of code interactively to see if things are working, and, thus, I open a few terminal tabs and fire up irb in them.

The thing is that I’ve noticed in the iStat Pro widget that the CPU is 90%–100% active… And both have at least two cores… with HyperThreading! Even when I quit the terminal app (iTerm in my case), the CPU is still working a lot! Then and idea came to me: launch Activity Monitor and see what application is consuming all those resources. What a surprise when I see 4 ruby processes eating up 90-ish% of the CPU!

After forcing quit those processes, everything becomes normal again! Notice the CPU usage graph before and after:

And I guess it wasn’t only eating up the CPU resources, but also the memory. And that’s happened to me with both computers. Unbelievable!

Android Tutorials, by Android Research Blog

I’ve recently discovered Android Tutorials, a collection of tutorials and recipes by Android Research Blog regarding Android programming.

I’ve been interested in Android programming for a while, but never had the time to start learning. I’ve read a few magazine articles on Android programming, but I haven’t found a good introductory tutorial either, and I think some good recipes are also necessary for doing some things (like, e.g., a good preferences panel).

Anyway, I guess this is gonna be a good resource when I start learning, and, after taking a look at some of the articles, it looks promising!

Generating timetables with Python

I hope I’ll be going next semester to Germany as an Erasmus student, and I’m now struggling with the Learning Agreement, courses, and timetables. In particular, there are a lot of interesting courses being offered by my guest university, but there are many factors to take into account when deciding what course you’re going to take, and one of them is timetable compatibility.

So, after a lot of struggle with the university’s website and trying to find some timetables, I’ve thought of making a timetable where I’ll put all the courses I’m interested in, so that I can see which ones are compatible with each other, i.e., which ones don’t overlap.

But the timetable has time slots of 15 minutes, from 8:00 to 18:15, and, of course, I’m naturally lazy. I don’t want to create a spreadsheet with as many rows as the 15-minutes-slots there are in the timetable!

And here’s when Python comes to the rescue!

I’ve coded a Python class that represents time, and which has methods for addition and logical comparison:

class Time:
    def __init__(self, hour, minutes):
        self.hour = hour
        self.minutes = minutes
    
    def normalize(self):
        hour = self.hour
        minutes = self.minutes
        
        quotient = minutes / 60
        if quotient > 0:
            hour += quotient
            minutes = minutes % 60
        
        self.hour = hour
        self.minutes = minutes
        
        return self
    
    def __add__(self, t):
        """add two times (sum)"""
        hour = self.hour + t.hour
        minutes = self.minutes + t.minutes
        res = Time(hour, minutes)
        res.normalize()
        return res
    
    def __mul__(self, k):
        """multiply a time and an integer constant k (product)"""
        hour = self.hour * k
        minutes = self.minutes * k
        res = Time(hour, minutes)
        res.normalize()
        return res
    
    def __lt__(self, t):
        """less than"""
        if self.hour < t.hour or (self.hour == t.hour and self.minutes < t.minutes):
            return True
        else:
            return False
    
    def __eq__(self, t):
        """equal"""
        if self.hour == t.hour and self.minutes == t.minutes:
            return True
        else:
            return False
    
    def __le__(self, t):
        """less or equal"""
        return self < t or self == t
    
    def __gt__(self, t):
        """greater than"""
        return not self <= t
    
    def __ge__(self, t):
        """greater or equal"""
        return self > t or self == t
    
    def __ne__(self, t):
        """not equal"""
        return not self == t
    
    def __str__(self):
        hour = fill(str(self.hour), 2, '0')
        minutes = fill(str(self.minutes), 2, '0')
        return '%s:%s' % (hour, minutes)

I’ve implemented all the basic logical operators for comparison, because they’re very easy to implement, but we won’t be needing all of them.

In addition, I’ve also coded the __str__ method, which allows you to convert Time objects into Python strings by just using the str() function. This method needs a function called fill, which is intended to pad or fill a string with some character, so that the string reaches a given maximum size. That is, for example, when you have a string '8' that you want to be 2 characters long; you would fill it with zeroes until you reach that length: '08'. The code for this function is pretty simple:

def fill(s, size, c=' ', position='before'):
    """s: string; c: char"""
    if position == 'before':
        s = c * (size - len(s)) + s
    elif position == 'after':
        s += c * (size - len(s))
    return s

And, finally, this is the function to generate the time slots from a start time up to a given end time by some time increment:

def generate_timetable(start_time, interval=Time(0, 15), times=5, end_time=None):
    timetable = []
    
    if end_time is None:
        end_time = start_time + interval*times
    
    time = start_time
    while time < end_time:
        timetable.append(tuple([time, time + interval]))
        time += interval
    
    return timetable

In my case, I’ll just execute the following in the Python interpreter:

start_time = Time(8, 0)
end_time = Time(18, 15)
for start, end in generate_timetable(start_time, end_time=end_time):
    print '%s-%s' % (start, end)

This prints the time slots that should go into each row of the spreadsheet, so I’ll just have to copy that, and I’ll be able to start filling my timetable!

Evan Miller: Rank Hotness With Newton’s Law of Cooling

Evan Miller: Rank Hotness With Newton’s Law of Cooling

Evan Miller: How Not To Sort By Average Rating

Evan Miller: How Not To Sort By Average Rating

Via Jeff Croft’s blog.