Archive for October, 2008

How YOU can work with vim

Wednesday, October 22nd, 2008

I recently saw a great podcast on reddit titled  “How I work:  Rails with Vim” (http://blip.tv/file/1372096).  It showed how the author had made shortcuts to use vim more effectively.  Specifically, how to do a find from the current directory, get a list of files, and jump to any of those files.  TextMate fans will recognize this as a project file list with the ability to jump to that file.  Also the demo had a vim plugin that would do some replacements.

While it’s neat to see what he had done, the screen cast didn’t say _how_ he had done it.  It was almost like he had this cool funcitonality, which I was excitited to start using, but there were no instructions on how _I_ could use it.

So, I spent a little time figuring it out.  Here’s what I came up with:

VIM-

This is an alias to get a list of files in the current directory and below, and load that into vim.  Here’s my version

alias vim-=”find . -name .svn -prune -o -type f -print |vim -R -”
This finds all files (not directories “-type f”) under the current directory and prunes the .svn directories.  It then reads this list into vim from standard out.  It reads the file list as read-only “-R” so that you don’t have to worry about saving it or doing q! to get out of the editor

NOTE:  The find command may take some time with large code bases.

Next, change your vimrc file so that control T opens a new window with the file under the cursor
map <Ctrl-T> :new <cfile><Enter>
Apparently, <cfile> is the file name under the cursor.  You can use vnew instead of new to get a vertical window to the right.
Also, you can put this line in the vimrc to make the window open below the current window instead of above.
set splitbelow

Grill

The author also had a script that does a grep that is recursive, case insensitive, and lists the matching file names and puts that into a vim editor.  Here is my version.

I don’t know enough about aliases in bash to take arguments, so I just wrote a script.  My ~/bin directory is in my path, so I created a script called grill with this line
grep -ril $1 . |grep -v “.svn”  |vim -R -

Now “grill user” will open a vim window with a list of files that have the word user in them, and control-T will jump into that file.

Replacements

The last feature of this demo was the ability to input a shortcut and get several more keystrokes.  For this I use AutoHotKey in windows.  It is a great little utility that allows you to type “/ult/” and a tab, and it erases /utl/ and inserts /user/local/tools/tomcat*/apache*/, or whatever else you want.  It’s great for naviaging long paths or abbreviating frequent logins.  Now J@H maps to joe@hotmail.com<tab> password <enter> and it pops up on the screen as if you had done it yourself.  It’s very impressive in demos, and it works in any window, not just vim.

My Month-A-Page Calendar

Sunday, October 5th, 2008

Recently I needed to present data that relates to a month’s worth of appointments in Ruby.  In one scenario, the user might have 6 or 7 appointments on the same day for a dozen days in the month.  In another scenario they may only have 2 in a month.  Either way, I wanted to show the entire month and the title of each appointment on a calendar that took up the whole screen.

I looked for plugins, but nothing seemed to fit the bill.  Most of the plugins are for choosing a specific date (and time).  I downloaded and installed the calendar_helper plugin, but it didn’t seem to fit the bill, either.  Not until I dig in and played with it did I see that this was just what I was looking for.

Here’s how I got it to work:

First, install calendar helper

script/plugin install http://topfunky.net/svn/plugins/calendar_helper/

Then create the appointment model

script/generate model appointment name:string starts_at:datetime duration:integer
 exists  app/models/
 exists  test/unit/
 ... and so on

Update the database

rake db:migrate

Next, create a new controller, an index page, and a detail page

script/generate controller appointments index detail
 exists  app/controllers/
 exists  app/helpers/
 create  app/views/appointments
 exists  test/functional/
 create  app/controllers/appointments_controller.rb
 create  test/functional/appointments_controller_test.rb
 create  app/helpers/appointments_helper.rb
 create  app/views/appointments/index.html.erb
 create  app/views/appointments/detail.html.erb

And add it to the routes and bounce the app

map.resources :appointments

In the controller, we’ll want to set the default year and month to the current one.  We’ll also want to get the list of appointments for the needed month.  For simplicity’s sake, I’m just going to hard code some appointments here.  They will all be for the current day.

class AppointmentsController < ApplicationController
def index
@cal_time = Time.now
@appointments = []
@appointments << Appointment.new(:name=>"Lunch with the Boss", :starts_at=>Time.now.change(:hour=>12, :minute=>0) )
@appointments << Appointment.new(:name=>"Interview - Big Corporation", :starts_at=>Time.now.change(:hour=>8, :minute=>0) )
@appointments << Appointment.new(:name=>"Interview - Hot Startup", :starts_at=>Time.now.change(:hour=>3, :minute=>30) )
end
...

In the view we will need to do two things:

  1. We don’t want to loop through all the appointments for every day, so in order to handle the data intelligently, we’ll make an array with an entry for each day of the month.  All the appointments that fall on a particular day will be added to an array for that day.  This should make sense shortly.  If not, you can skip this part for now.
  2. We want to create a calendar with calendar_helper, and on each day we want a link to each appointment on that day.

To achieve number one here is the code

#Collect all the days into an array of arrays, so we can pull them out by day
appt_by_day = [];             # Holds arrays of appointments by day of month
@appointments.each{ |apt|
day = apt.starts_at.mday
if appt_by_day[day].nil?    # If this day does not have an array
appt_by_day[day] = []     # Then make one
end
appt_by_day[day].push(apt)  # Add this appointment to this day
}

To achieve number two, we use the calendar helper with the block that does processing for each day

calendar(:year => @cal_time.year, :month => @cal_time.month,  # 1
:previous_month_text=>"<a href='/make_this_smarter'><<</a>", #2
:next_month_text=>"<a href='/make_this_smarter'>>></a>"
) do |d|
display_string = ""                                #3
if !appt_by_day[d.mday].nil?                       #4
apt_for_day = appt_by_day[d.mday]
end
apt_for_day.each{ |a|                              #5
display_string += "<a href='/appointments/detail/#{a.id}'>#{a.name}</a><br>"
}
end
["<div class='dateBox'>#{d.mday}</div><br><div class='appt'>#{display_string}</div>" ] #6
end

This code does several things

  1. On this first line it sets the year and month for the calendar.  These should be integers.
  2. At number 2 and the next line it sets the URL for the next and previous month links.  These are left as an exercise to the user.
  3. This block runs for each day of the month, with the day as “d”.  The display_string is the string we want to display for this day.  It should contain a link to the detailed view for all the appointments for this day.
  4. The appt_by_day array for each day will either be nil (no appointments) or an array of appointments, since you can have several in the same day.
  5. The array of today’s appointments is in apt_for_day.  For each of these we want to add a link to the display_string for display.  Once we have the whole string we’ll send it to the helper.
  6. This line tells the helper what we want to display in the box.  I have put the day and the display_string in different divs, just to make formatting easier.

This works, but it’s pretty ugly.  The boxes for each day are too small, and there aren’t enough colors.

To make it nicer we need to install the css.

script/generate calendar_styles

This gives us some pretty css that the calendar can use.  Edit the views/layout/appointments.html.erb to look more like this

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>

<html xmlns=”http://www.w3.org/1999/xhtml” xml:lang=”en” lang=”en”><head>
<meta http-equiv=”content-type” content=”text/html;charset=UTF-8″ />
<title>Events: <%= controller.action_name %></title>
<%= stylesheet_link_tag ’scaffold’ %>
<%= stylesheet_link_tag ‘calendar/blue/style’ %>
</head>
<body>

<p style=”color: green”><%= flash[:notice] %></p>

<%= yield  %>

</body>
</html>

I added some css details for the divs we created and got this (picture was taken the next day):

Next up will be making it as wide as the screen, adjusting the colors, and making the users happy.

Taking full advantage of the flexibility of calendar_helper allowed me to deliver a full-screen view of a month with all the calendar information within easy reach of the user.