.

 

Emacs Notepad

Make emacs tell time, open files quickly, and talk to Perl.

In this piece I describe several simple utility functions I wrote for the text editor emacs, and which I include in my .emacs configuration file. I use each of these functions several times a day, and believe they may useful to others.

If you don't use emacs, these will be of limited interest. Sorry. Scott Rahin promises to write more soon.

For the totally uninitiated, emacs is a text editor popular with programmers. It runs on the Unix, Mac, and Windows operating systems, and has a built in "extension" language based on LISP. Emacs is like a skyscraper - it's huge and monolithic and hard to get into unless you know where you're supposed to be going. But I really like editing text in emacs, and I've tried everything, from MSWord to ancient folding editors last updated in 1985.

Throughout this piece, I refer to the .emacs ("dot ee-macks") file. This file lives in your home directory and contains LISP code that emacs runs every time you start the program. Your .emacs file can be used to contain all the stuff that's specific to you and how you work.

.  .  .  .  .  

Inserting the Time

While it provides many features for time-stamping documents, emacs lacks a flexible way to drop in a formatted date string at any point in a document. The answer is a very simple function:


(defun insert-time ()
  (interactive)
  (insert (format-time-string "%Y-%m-%d-%R")))

Now, when I hit M-x insert-time, emacs inserts a string like this: 2001-03-19-02:10. For the unitiated, M-x means "Meta-X" or "Escape-X"; it's a command sequence. Here's what's going on in the code:

(defun insert-time ()

Define a function called insert-time. It takes no arguments.

(interactive)

This tells emacs that the function can be called while editing.

(insert (format-time-string "%Y-%m-%d-%R")))

insert tells the program to insert what's next; format-time-string performs the standard Unix formatting on the time. You can learn more about time formatting by typing man date in the Unix shell.

.  .  .  .  .  

The Same Thing, Slightly Different

Let's look at another date formatting function.


(defun insert-ISO ()
  (interactive)
  (insert (format-time-string "%Y%m%d")))

As you can see, it's the same idea - except now we're putting out an ISO-formatted YYYYMMDD year, like so: 20010319.

If you felt extravagant, you could create something like this:


(defun insert-date-verbose ()
  (interactive)
  (insert (format-time-string "It is now second %S of minute %M of hour %H (%l %p) on day %d of %B in year %Y in time zone %Z. It is %A, and day %j, in week %U, of %Y. ")))

M-x insert-date-verbose produces: It is now second 41 of minute 44 of hour 02 ( 2 AM) on day 19 of March in year 2001 in time zone EST. It is Monday, and day 078, in week 11, of 2001.

.  .  .  .  .  

Aliasing Frequently Used Files to Functions

When writing, I find myself opening the same files over and over again, and I get sick of typing in the full path of the file and tab-completing all over the place, trying to match my current mental space to a unique file name. For the files I use most often, I create aliases like so:


(defun go-story () 
  (interactive) 
  (find-file "~/www/source/xml/ftrain_story.xml"))

(defun go-theory () 
  (interactive) 
  (find-file "~/www/source/xml/ftrain_theory.xml"))

Now, when I'm editing any document, I simply type M-x go-story and the file "~www/source/xml/ftrain_story.xml" is opened into my buffer and ready to go. Typing M-x go-theory takes me to a different file. And if I just type M-x go- and then hit tab, emacs will list all my "go-*" aliases in a new window, as a sort of reminder.

.  .  .  .  .  

Opening a File and Altering it Automatically

I keep a linear journal on Ftrain, to hold onto passing thoughts, and I want to be able to get in and out of it quickly - preferably without having to add any "metadata" like titles, date, filename, and so forth.

So, to keep things simple, I decided that my journal would be one big text file split up by timestamps, and timestamps would start with "*". That is, two journal entries might look like this:


*2001-03-19-02:00
This is the journal entry for the 19th of March, 2 am. My dog got fleas.

*2001-03-19-18:00
And here's another for the 19th, at 6 pm. Fleas getting better.

*2001-03-20-02:00
And this one is for the next day at 2 am. Fleas is gone.

That's it - plain text. I split up the days, convert paragraphs, line breaks, URLs, etc via a Perl script.

When I began using this journal, I found that it was a real drag to open it up, scroll to the bottom of the file (the end-of-buffer), insert the time, and start typing. So I wrote another little function that:

  1. Opens the "journal" file;
  2. Goes to the bottom of the file and inserts a hard return;
  3. Inserts a time-stamp prefixed by an asterix;
  4. Inserts two hard-returns
Now, I just type M-x journal and I'm ready to go. Here's that function:


(defun journal () 
  (interactive) 
  (find-file "~/www/journal.txt")
  (end-of-buffer)
  (insert "\n\n")
  (insert "*")
  (insert-time)
  (insert "\n\n")
)

As long as you know that "\n" means "newline" in LISP, then you'll be able to figure out what's going on above. It's just a simple sequence of smaller functions, like insert and find-file adding up into a larger one.

As I wrote, when I'm in emacs, I call the journal with M-x journal. If I'm not in emacs, I can simply type emacs -f journal on the command line, and emacs will open and execute the journal function immediately. To speed things even more, I have the following line in my .bashrc file:

alias journal='emacs -nw -f journal'

With this, when I'm in the shell, I simply type journal and emacs opens up in journal mode without creating a new window. I type quickly, exit, and I'm done.

It seems a simple thing, but making the process of writing in my journal as free of resistance as possible has helped me catch hundreds of paragraphs before I moved onto something else. Also, since I write my email in emacs, when I write something that's worth saving, I simply copy it, type M-x journal, and paste into the journal, exit the journal file and go back to finish the email. Whenever I update Ftrain, the journal is sorted by day and auto-converted to XML, then integrated into the whole of the site.

I use a very similar function to open my private "messages" file, where I hold all my records of phone calls. I simply type "messages" at the shell prompt and begin taking notes; it's much faster than trying to open up a new file or clicking in a virtual "post-it" note.

.  .  .  .  .  

Inserting a Block of XML Code into a Buffer

Here's what was going on: I wanted to insert a block of XML code representing a section into the file I was editing, with the proper "date" information, and, since every block needs a unique indentifier, I wanted the editor to leave the cursor right at the spot where I could type the unique ID.

That is, I wanted the editor to insert a block like this, automatically entering the correct ISO date:


<section date="2001-03-19" id="">
  <title></title>
  <desc></desc>
  <key></key>

  <p></p>

</section>

After it inserted the code, I wanted emacs to move the cursor up between the quotes of id="", so I could insert an ID immediately. Here is the function I wrote:


(defun section () 
  (interactive) 
  (insert "\n<section date=\"")
  (insert-ISO)
  (insert "\" id=\"\">"
	  "\n  <title></title>"
	  "\n  <desc></desc>"
	  "\n  <key></key>"
	  "\n\n  <p></p>"
	  "\n\n</section>\n\n")
  (previous-line 9)
  (end-of-line)
  (backward-char 2)
)

If you read the previous explanations, it should all be fairly straightforward, but there's one thing worth noting: everything in emacs, whether entering text, moving around in text, and searching, or sending mail, is bound to a LISP function - which means you can use these functions in other functions. For instance, the LISP function previous-line is called every time you hit the "up" cursor key. In this case, we do the following:

(previous-line 9)

This is equivalent to hitting the "up" cursor key 9 times.

(end-of-line)

Then we go to the end of the current line...

(backward-char 2)

...and move 2 characters backward. This puts us right in between the quotes of the id="" attribute of the section tag, which is where we want to be.

With "insert" and a few functions like:

  1. previous-line
  2. next-line
  3. forward-char
  4. backward-char
  5. end-of-buffer
  6. beginning-of-buffer
  7. end-of-line
  8. beginning-of-line
you can quickly create some fairly useful functions. It's enough to make me want to learn LISP properly.

.  .  .  .  .  

Using Macro Mode to Quickly Change Several Hundred Files

Emacs has a simple Macro mode which records all of your keystrokes for later playback. I often use it when I'm editing hundreds of files at once, making simple changes in all the files.

  1. To start recording a keyboard macro, hit C-x (
  2. To stop recording a keyboard macro, hit C-x )

So, let's say your company was just purchased, and you have 200 html files in three different nested directories, and you need to change the title portion of each HTML file from one company's name to another. So


<title>Ftrain.com: "About Us"</title>

needs to become


<title>ConsolidatedUndulatingProng.com: "About Us"</title>

And so forth. You could always create a regular expression in a Perl script, and run that on every relevant file, replacing every occurrence of Ftrain.com with ConsolidatedUndulatingProng.com. Actually, for such a simple 1-to-1 transformation, that would be a better approach. You could just write a one-line perl script, called replacer.pl in your home directoryx like so:


#!/usr/bin/perl -pi.bak
s/Ftrain\.com/ConsolidatedUndulatingProng.com/gi;

and go into your top-level HTML directory and type:

$ perl ~/replacer.pl $(find -name "*.html")

That will, if memory serves, take the list of all HTML files in the current and all child directories, pipe them through replacer.pl, replace all the "Ftrain.com" references with "ConsolidatedUndulatingProng.com" references, and create .bak backup files for every file touched.

But enough digression - back to emacs. Let's just assume that you didn't want to deal with Perl for right now. Do the following:

Here's what's going on with all of that:

C-x (

We open the macro.

[Return]

We hit return. Because we're in Dired mode, we open the file that is listed at the cursor point

C-s <title>

We do a forward search for the <title> tag

C-s ftrain.com

Now we do a second forward search for the string "ftrain.com." This will find the first occurence of Ftrain.com after the <title> tag, regardless of spacing.

[Delete "Ftrain.com"]

Erase the string "Ftrain.com"; the macro will remember that you hit backspace 10 times, not what you erased.

[Type "ConsolidatedUndulatingProng.com"]

Enter then new string where Ftrain.com used to be.

C-x C-s

Save the file.

C-x k

Kill the buffer. This will bring you back to DIRED mode.

[Cursor down]

Go down to the next file.

C-x )

Close the macro.

Now, you can run the macro by typing C-x e. Do this a few times to test it out. You won't be able to see anything, but the cursor will continue to move down the file list. What happens is - the macro opens the currently selected file, makes the changes in the macro, saves the file, closes it, and moves to the next file in the list.

If you're confident the macro is doing what you want it to, simply type C-u 200 C-x e and the macro will be executed 200 times, moving down the list of files in your Dired buffer. If there aren't 200 files, it will break and beep, no worries.

I know it seems complicated, but play around. I found that emacs macros could save me dozens of hours of time, especially with regards to site-wide HTML editing. And it's often easier than dealing with a Perl regular expression because emacs allows you to jump over line breaks and do some very arbitrary stuff in a macro, whereas Perl makes you get things just right.

.  .  .  .  .  

Naming Macros for Re-Use

  1. Create the macro (see above)
  2. M-x name-last-kbd-macro
  3. Give the macro a name when prompted.
  4. Open your .emacs file and scroll to the end
  5. M-x insert-kbd-macro
  6. Enter the name you just gave your macro

.  .  .  .  .  

Using Perl to Interface with Emacs

There's a neat library called Emacs::Lisp written by a fellow named John Edwin Tobey that is very interesting; essentially, it lets you mix emacs and Perl functions together at will. There was a version of Emacs called Perlmacs, which had Perl built-in, but that was pure Unix craziness and not fit for mortals, while this is a simple Perl module, and everyone can play along.

You need to install the Perl module and associated LISP files in the approproate places, as defined in the Emacs::Lisp install docs. I'm not going to help you there, because if you know enough about Perl and Emacs to have read this far, you already know how to install modules. I found that the CPAN module choked on the install - you'll need to install by hand, and copy the LISP files by hand.

I tossed these two lines into my .emacs:


(load-library "perl")
(perl-load-file "~/.emacs.pl")

So, given those lines in my .emacs and a Perl script in my home directory, called .emacs.pl, I can do some very neat things. Here's a short version of .emacs.pl:


#!/usr/local/bin/perl

use Emacs::Lisp; 
use WWW::Search;

defun (\*google_search, 
       interactive("r"), 
       sub {
	 my $query = &read_string("Enter Google Search Term: ");
  	 my $search = new WWW::Search('Google');
	 $search->maximum_to_retrieve(15);
	 $search->native_query(WWW::Search::escape_query($query));
	 while (my $result = $search->next_result()) {
	   &insert($result->url, "\n");
	 }
       });

defun (\*google_search_href, 
       interactive("r"), 
       sub {
	 my $accum;
	 my $query = &read_string("Enter Google Search Term: ");
  	 my $search = new WWW::Search('Google');
	 $search->maximum_to_retrieve(15);
	 $search->native_query(WWW::Search::escape_query($query));
	 $accum = "\n<ol>\n";
	 while (my $result = $search->next_result()) {
	   my $url = xml_cleanup($result->url);
	   my $title = xml_cleanup($result->title);
	   $query = xml_cleanup($query);
	   $accum .= qq{\n\t<li>\n\t\t<a href="$url" title="Found via Google.com search for: $query">$title</a>\n\t\t<br/>\n\t<a href="$url" title="Found via Google.com search for: $query">$url</a>\n\t</li>\n\n};
       }
	 $accum .= "\n</ol>\n";
	 &insert("$accum");

       });


sub xml_cleanup {
    ($in) = (@_);
    $in =~ s/>/\&gt;/g;
    $in =~ s/</\&lt;/;
    $in =~ s/\&amp;/\&/;
    $in =~ s/\&/\&amp;/;
    $in =~ s/\"/\&quot;/;
    $in;
}

What you see are two functions, one called google-search and the other called google-search-href. If things are set up properly, you can use them like any function, by typing M-x google-search or M-x google-search-href. Both query the Google search engine and spit out a list of 15 simple URLs; the first just prints the URLs and the latter prints them as fully qualified HTML HREFs in an ordered list. For instance, let's type in M-x google-search and when it asks Enter Google Search Term I'll type in "Paul Ford Ftrain":


http://www.ftrain.com/
http://www.ftrain.com/about_pef.html
http://www.consolidatedundulatingprong.com/
http://www.metafilter.com/comments.mefi/689
http://www.metafilter.com/comments.mefi/688
http://www.clickey.com/search.cgi?keyword=Subway
http://www.villagevoice.com/vls/170/dibbell.shtml
http://www.pigsandfishes.org/links/weblog/archives.html?year=2000&month=02
http://glassdog.com/lance/wom/
http://www.ruth.co.uk/homes/roo/2000_07_01_archive.html
http://www.diarist.net/cgi-bin/registry.cgi?author=P
http://www.diarist.net/registry/byname/1800.shtml
http://members.home.net/gwym/links.html
http://www.cl.cam.ac.uk/~mn200/weblog/2000_04.html
http://abakusz.matav.hu/erre/links/

Now the same, but with M-x google-search-href


  1. Ftrain.com
    http://www.ftrain.com/

  2. Ftrain.com: "Paul.Ford"
    http://www.ftrain.com/about_pef.html

  3. Ftrain.com: "Ftrain.com"
    http://www.consolidatedundulatingprong.com/

  4. Metafilter | Comments on 689
    http://www.metafilter.com/comments.mefi/689

  5. Metafilter | Comments on 688
    http://www.metafilter.com/comments.mefi/688

  6. Clickey Search Results
    http://www.clickey.com/search.cgi?keyword=Subway

  7. Voice Literary Supplement: My Modem, Myself
    http://www.villagevoice.com/vls/170/dibbell.shtml

  8. P&F: Weblog Archives [02/2000]
    http://www.pigsandfishes.org/links/weblog/archives.html?year=2000&month=02

  9. glassdog.WORD OF MOUTH
    http://glassdog.com/lance/wom/

  10. roo.i.am
    http://www.ruth.co.uk/homes/roo/2000_07_01_archive.html

  11. Diary Registry: Search
    http://www.diarist.net/cgi-bin/registry.cgi?author=P

  12. Diary Registry: Browse by Author
    http://www.diarist.net/registry/byname/1800.shtml

  13. Go West, Young Man: List of Links
    http://members.home.net/gwym/links.html

  14. Michael Norrish's web-log - April 2000
    http://www.cl.cam.ac.uk/~mn200/weblog/2000_04.html

  15. Links
    http://abakusz.matav.hu/erre/links/

Not too awful. In any case, I just installed this thing tonight, but I can definitely think of many nifty functions, especially as Perl has a host of useful linguistic analysis tools, better regular expressions than LISP, and modules for everything from mail-checking to Web-page-downloading. I'm thinking that I can write emacs/Perl routines to enter random poetry, define the current word via the Perl::Wordnet, the semantic word database, and automatically offer synonyms. Emacs = thesaurus! Emacs = dictionary! Emacs = all manner of stuff. I can spit my sentences out to link grammars, and call Figlet on my titles, and on, and on, and on until I explode into a huge ball of emacs-loving plasma.

.  .  .  .  .  

So, that's it. Let me know if you find this sort of thing useful, and what I can add to it; it's fun to write it out, and forces me to ask questions of the mechanistic processes I'm using to create text, both inside the computer, and inside my head.


[Top]

Ftrain.com

PEEK

Ftrain.com is the website of Paul Ford and his pseudonyms. It is showing its age. I'm rewriting the code but it's taking some time.

FACEBOOK

There is a Facebook group.

TWITTER

You will regret following me on Twitter here.

EMAIL

Enter your email address:

A TinyLetter Email Newsletter

About the author: I've been running this website from 1997. For a living I write stories and essays, program computers, edit things, and help people launch online publications. (LinkedIn). I wrote a novel. I was an editor at Harper's Magazine for five years; then I was a Contributing Editor; now I am a free agent. I was also on NPR's All Things Considered for a while. I still write for The Morning News, and some other places.

If you have any questions for me, I am very accessible by email. You can email me at ford@ftrain.com and ask me things and I will try to answer. Especially if you want to clarify something or write something critical. I am glad to clarify things so that you can disagree more effectively.

POKE


Syndicate: RSS1.0, RSS2.0
Links: RSS1.0, RSS2.0

Contact

© 1974-2011 Paul Ford

Recent

@20, by Paul Ford. Not any kind of eulogy, thanks. And no header image, either. (October 15)

Recent Offsite Work: Code and Prose. As a hobby I write. (January 14)

Rotary Dial. (August 21)

10 Timeframes. (June 20)

Facebook and Instagram: When Your Favorite App Sells Out. (April 10)

Why I Am Leaving the People of the Red Valley. (April 7)

Welcome to the Company. (September 21)

“Facebook and the Epiphanator: An End to Endings?”. Forgot to tell you about this. (July 20)

“The Age of Mechanical Reproduction”. An essay for TheMorningNews.org. (July 11)

Woods+. People call me a lot and say: What is this new thing? You're a nerd. Explain it immediately. (July 10)

Reading Tonight. Reading! (May 25)

Recorded Entertainment #2, by Paul Ford. (May 18)

Recorded Entertainment #1, by Paul Ford. (May 17)

Nanolaw with Daughter. Why privacy mattered. (May 16)

0h30m w/Photoshop, by Paul Ford. It's immediately clear to me now that I'm writing again that I need to come up with some new forms in order to have fun here—so that I can get a rhythm and know what I'm doing. One thing that works for me are time limits; pencils up, pencils down. So: Fridays, write for 30 minutes; edit for 20 minutes max; and go whip up some images if necessary, like the big crappy hand below that's all meaningful and evocative because it's retro and zoomed-in. Post it, and leave it alone. Can I do that every Friday? Yes! Will I? Maybe! But I crave that simple continuity. For today, for absolutely no reason other than that it came unbidden into my brain, the subject will be Photoshop. (Do we have a process? We have a process. It is 11:39 and...) (May 13)

That Shaggy Feeling. Soon, orphans. (May 12)

Antilunchism, by Paul Ford. Snack trams. (May 11)

Tickler File Forever, by Paul Ford. I'll have no one to blame but future me. (May 10)

Time's Inverted Index, by Paul Ford. (1) When robots write history we can get in trouble with our past selves. (2) Search-generated, "false" chrestomathies and the historical fallacy. (May 9)

Bantha Tracks. (May 5)

More...
Tables of Contents