Archive for the ‘emacs’ Category

No more source code indentation arguments

April 8, 2008

Guaranteed!1

We’ve all been there. Maybe you’ve joined a new team that uses a one space indent which is driving you crazy. Perhaps your team already compromised on a four space indent and a new hot-shot joins your team and tells you that Linus says that if you don’t use an eight space indent nature will screw you2

What is the solution? Simple – add a hook on check-in to your source repository that automatically reindents the code with the team’s standard. Then, anyone who wants to can configure their editor to reformat the code the way they like it when opening a file. When they check the code back in, their screwy indentation is fixed again. That way, everyone can develop using the style they like.3

You’re probably wondering where the emacs is in this post. Well, I don’t know about you, but I haven’t had much luck with getting indent(1) to format C++ in the way that I want. However, emacs knows how to indent my code just fine.

;; ---------------------------------------------------------------------- ;;

(defconst *my-cc-style*
  '((c-basic-offset . 4)
    (c-comment-only-line-offset . 0)
    (c-hanging-braces-alist . ((brace-list-open)
                               (brace-entry-open)
                               (substatement-open after)
                               (block-close . c-snug-do-while)))
    (c-cleanup-list . (brace-else-brace))
    (c-offsets-alist . ((statement-block-intro . +)
                        (knr-argdecl-intro     . 0)
                        (substatement-open     . 0)
                        (substatement-label    . 0)
                        (innamespace           . 0)
                        (case-label            . +)
                        (statement-cont        . +)))))

;; ---------------------------------------------------------------------- ;;

(provide 'cc-style)

;; ---------------------------------------------------------------------- ;;

A simple shell script wrapper will let you call indent.sh <filename>. Warning: this script has not been tested and may break your code. Use at your own risk.

#!/bin/sh

CC_STYLE=$HOME/emacs-files/cc-style.el

emacs --batch -eval "
  (progn
    (load \"$CC_STYLE\")
    (c-add-style \"PERSONAL\" *my-cc-style*)
    (find-file \"$1\")
    (c++-mode)
    (c-set-style \"PERSONAL\")
    (setq tab-width 4)
    ;; (setq make-backup-files nil)
    (ident-region (point-min) (point-max))
    (untabify (point-min) (point-max))
    (basic-save-buffer))"

1. (or your money back)

2. Yep, horribly misquoted and probably misattributed – sorry

3. They might not like the output from the diff tool though. Tell them about diff -b

Advertisements

Wrapping FTP in emacs – part 2

April 2, 2008

Now that we have a nice wrapper around copying a file that handles connecting to the remote server if we are not already connected, implementing the single key press upload function is trivial.

My website related directories are all under c:/home/juang/website. However, sometimes I like to upload some of my elisp files which live elsewhere. I should probably have an external environment variable which points to my home directory (e.g. %HOME%) but for the moment I have constants within my elisp source.

(defconst *ftp-HOME* "c:/home/juang/")
(defconst *ftp-WEB* (concat *ftp-HOME* "website/"))

In elisp we normally use a list of lists where we might use a hash in Perl. This is not because elisp doesn’t have hashes (it does), but because the syntax for lists is cleaner. A simple mapping might look like this:

(defconst *map*
  '(("c:/www" "/www")
    ("c:/emacs-files" "/www/emacs-files")))

However, quote suppresses variable evaluation so we need to be a little bit more verbose.

(defconst *ftp-location-map*
  (list
   (list (concat *ftp-WEB* "wordpress") "/www/blog")
   (list *ftp-WEB* "/www/")
   (list (concat *ftp-HOME* "emacs-files") "/www/files")))

I like emacs to ask for confirmation before it uploads a file but as others might want to skip that step we add a variable to allow the alternative behaviour.

(defcustom *ftp-upload-skip-prompt* nil
  "Skip the ftp prompt")

A couple of comments about (ftp-upload-file …):

  • (do-list (var list) body) iterates through a list, setting var to each item in turn.
  • (y-or-n-p &prompt) displays the prompt in the mini-buffer and waits for a y or n input.

The rest of the function should be fairly self-explanatory.

(defun ftp-upload-file (from)
  (let ((to from))
    (dolist (tuple *ftp-location-map*)
      (let ((regex (car tuple))
            (replace (cadr tuple)))
        (when (string-match (concat "^" regex) to)
          (setq to (replace-match replace t t to)))))
    (if (or *ftp-upload-skip-prompt*
            (y-or-n-p (format "Query: Upload %s to %s ? " from to)))
        (ftp-copy-file from to)
      (message "Info: upload %s to %s aborted" from to))))

(defun ftp-upload-current-file ()
  (interactive)
  (ftp-upload-file (expand-file-name (buffer-file-name))))

(global-set-key [f9] 'ftp-upload-current-file)

We also provide a convenience function to upload the current file and bind it to a single key. The final result is linked from the original version of this page.

JG

Wrapping FTP in Emacs

March 29, 2008

Emacs provides some very nice abstractions for working with remote files. With emacs package ange-ftp find-file can access remote files transparently. You simply enter (for example) /ftp:<user>@<host>:/www/blog/index.php and behind the scenes emacs handles setting up an ftp connection to the remote server and downloading the file. When you save emacs uploads the file to the server. If the ftp connection has timed out then it reconnects automatically. All this is done transparently and it is almost like working with a local file. It is yet another reason to use emacs. However, sometimes that is not exactly what you want.

If you are working with an external website usually you have a development version running locally. After tweaking a page and seeing how it looks then you upload it to your remote server. Wouldn’t it be nice if you could upload a file to the correct location with a single key rather than resorting to an external program? Of course, you could do all the development remotely without too much pain using ange-ftp as described above, but working locally is faster and saves a lot of bandwidth.

ange-ftp provides some nice functions for connecting to an ftp server and assigning a password to particular user/host combination.

For a reminder on how to find useful emacs functions go here

(require 'ange-ftp)

We set up some variables for the username, hostname and password and also a variable to reference the ftp process.

(defvar *ftp-process* nil)
(defvar *ftp-user* "some-user")
(defvar *ftp-host* "ftp.some-host.com")
(defvar *ftp-password* "...")

I tried using the ftp client supplied with windows without much success so eventually I downloaded the ftp client mentioned at the ange-ftp wikipage
ftp://ftp.gnu.org/old-gnu/emacs/windows/contrib/ftp-for-win32.zip

(setq ange-ftp-ftp-program-name "c:/bin/ftp.exe")

(ange-ftp-set-passwd *ftp-host* *ftp-user* *ftp-password*)

The first function we need is one that connects to the remote server. This is a simple wrapper around ange-ftp-get-process. We check if we are already connected before trying to reconnect. How do we know if we are already connected? We could check the status of the process – if it is ‘run then the process is running. The problem with this strategy is that with many ftp-clients, when a connection is timed out, the ftp process is not disconnected until it tries to send another command. This means that we need to track timeouts ourselves.

Add a constant for how long it takes the remote server to disconnect us if there is no activity and a variable to store when we last did something.

(defconst *ftp-max-timeout* 100)

(defvar *ftp-last-action* 0)

Then we need some functions to check if we have timed out and set when we last sent a command to the server.

(defun ftp-has-timed-out-p ()
(> (- (time-to-seconds (current-time)) *ftp-last-action*)
*ftp-max-timeout*))

(defun ftp-set-last-action ()
(setq *ftp-last-action* (time-to-seconds (current-time))))

Add a predicate to check if we are connected and then we can easily implement the ftp-connect function.

(defun ftp-connected-p (proc)
(and proc
(equal (process-status proc) 'run)
(ftp-has-timed-out-p)))

(defun ftp-connect (host user)
(when (ftp-has-timed-out-p)
(ftp-kill-process host user *ftp-process*)
(sit-for 0)
(sleep-for 1))
(when (not (ftp-connected-p *ftp-process*))
(setq *ftp-process*
(ange-ftp-get-process host user))
(ange-ftp-set-binary-mode host user))
(ftp-set-last-action)
*ftp-process*)

If we have timed out then we kill the existing process, wait for the display to update with (sit-for 0) and wait for a second before trying to reconnect. You might notice we used an ftp-kill-process function that hasn’t yet been defined.

(defun ftp-kill-process (host user proc)
(when (ftp-connected-p proc)
(kill-process proc)
(setq proc nil)
(set-buffer (get-buffer (ange-ftp-ftp-process-buffer host user)))
(insert "\nTerminated"))
nil)

Finally we provide a wrapper around ange-ftp-raw-send-cmd that connects us if we are disconnected and then sets the last-action to the current time and add commands to make directories and copy files.

(defun ftp-raw-send-cmd-wrapper (cmd)
(let ((proc (ftp-connect *ftp-host* *ftp-user*)))
(ange-ftp-raw-send-cmd proc cmd)
(ftp-set-last-action))
t)

(defun ftp-copy-file (from to)
(ftp-raw-send-cmd-wrapper
(format "put %s %s" (expand-file-name from) to)))

(defun ftp-mkdir (dir)
(ftp-raw-send-cmd-wrapper (format "mkdir %s" dir)))

Hopefully you get the impression from the above code that writing elisp is similar to writing any other code – you decide on the abstractions you want and then you implement them. So why bother learning it? Firstly, there is a huge body of code that implements a lot of functionality already written in elisp. For example, if I had to implement everything that ange-ftp provides, that would have been a lot of code. Secondly, if I wrote the ftp-wrapper in perl then I would have to execute the script manually (or perhaps write an elisp binding anyway) which be more inconvenient than simply pressing a key while in my editor.

In the second part, we will see how to add some very simple functions to upload files to remote locations based on where the local file is.

The final version of the ftp-wrapper is linked from the
original version of this page.

describe-key and apropos

March 28, 2008

Emacs is often described as a self-documenting text editor. Part of that is because of the superb manuals that are built-in but another part is that you can ask the editor to describe itself using C-h k (describe key) and M-x apropos.

describe-key tells you which function is invoked when you press a certain key chord. For example, I wanted to know what C-h k invoked so I pressed C-h k C-h k. Whilst writing a script I needed to know which function positions the cursor at the beginning of the line. C-h k C-a gives the answer – (move-beginning-of-line arg). Okay, so that is useful, but what if you are looking for a command that isn’t bound to a key combination. That is where apropos comes in.

apropos enables you to list all the functions in emacs that match a regular expression. For example, say you are not sure which function returns the current time. You might try M-x apropos time$<RET> and search through the list of functions that are returned or perhaps M-x apropos current.*time which quickly points you at (current-time). However, current-time returns a list of three integer values which isn’t very useful. Maybe you can convert it into an integer?

M-x apropos integer<RET>
C-s (isearch-forward) time - no matches

Maybe not… How about a float?

M-x apropos float<RET>
C-s time

Both of the following look to be suitable for my purposes. Of course, your purposes may be different!

(float-time)
(float-time (current-time))

Right, that is quite enough about time. How about a search for all the hooks?

M-x apropos hook$

This returns 155 hooks in my emacs. One interesting hook is post-command-hook which runs after every command. I will be using that in a later post.

Right, that should have given you an idea of how to search for the functions you need.

JG