Tips, snippets and smug satisfaction for Launchpad's Emacs and XEmacs users. If you are using the right editor though, you should visit UltimateVimPythonSetup.
Contents
Sorting the TAGS tables
make TAGS creates a tags table with the canonical package sorted first.
Find things
Add bzr-tools.el to your load-path and then use bzr-tools-grep to search all of the versioned files. This can help you catch code in txt or zcml files that an rgrep might not find.
Cheap TODO lists
lp:difftodo is a Bazaar plugin that looks at a Python diff and extracts new or modified XXX comments. e.g.
$ bzr todo Using submit branch /home/jml/repos/launchpad/devel lib/lp/buildmaster/interfaces/builder.py:161: XXX: This needs to change to become a method that returns a Deferred. lib/lp/buildmaster/manager.py:389: XXX: Now returns a Deferred. lib/lp/buildmaster/manager.py:398: XXX: Now returns a Deferred. lib/lp/buildmaster/manager.py:401: XXX Julian 2010-07-29 bug=611258 We're not using the RecordingSlave until dispatching, which means that this part blocks until we've received a response from the builder. updateBuild() needs to be made asyncronous. lib/lp/buildmaster/model/builder.py:189: XXX: Nothing external calls this. Make it private. lib/lp/buildmaster/model/builder.py:196: XXX: Change this to do non-blocking IO. Things to do: 6
If you have bzr-tools.el then you can add the following to your .emacs:
(defun bzr-tools-branch-todo () (interactive) (bzr-tools-at-branch-root "." (compile "bzr todo" t)))
Then M-x bzr-todo RET will give you a list of things you need to do in your branch. You can even click on the links in Emacs to open up to the line that the XXX comment is on.
Never have lint again!
This snippet, pinched from http://www.plope.com/Members/chrism/flymake-mode/view, runs pyflakes through flymake. That means unused imports and unrecognized names get highlighted, without you having to explicitly run anything.
Tested with GNU Emacs 23.0.60.1.
;; Run pyflakes with flymake. (when (load "flymake" t) (defun flymake-pyflakes-init () (let* ((temp-file (flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace)) (local-file (file-relative-name temp-file (file-name-directory buffer-file-name)))) (list "pyflakes" (list local-file)))) (add-to-list 'flymake-allowed-file-name-masks '("\\.py\\'" flymake-pyflakes-init))) (add-hook 'find-file-hook 'flymake-find-file-hook) ;; Work around bug in flymake that causes Emacs to hang when you open a ;; docstring. (delete '(" *\\(\\[javac\\]\\)? *\\(\\([a-zA-Z]:\\)?[^:(\t\n]+\\)\:\\([0-9]+\\)\:[ \t\n]*\\(.+\\)" 2 4 nil 5) flymake-err-line-patterns) ;; And the same for the emacs-snapshot in Hardy ... spot the difference. (delete '(" *\\(\\[javac\\] *\\)?\\(\\([a-zA-Z]:\\)?[^:( \n]+\\):\\([0-9]+\\):[ \n]*\\(.+\\)" 2 4 nil 5) flymake-err-line-patterns) (delete '(" *\\(\\[javac\\] *\\)?\\(\\([a-zA-Z]:\\)?[^:( \n]+\\):\\([0-9]+\\):[ \n]*\\(.+\\)" 2 4 nil 5) flymake-err-line-patterns)
BarryWarsaw 07-Oct-2010: are these workarounds still necessary for Emacs 23.1 in Maverick?
Sorting import statements
Chuck this in your .emacs, restart (or eval-region it), put the cursor between the ()s of a big nasty import statement and M-x sort-imports should straighten everything out (MichaelHudson).
BarryWarsaw 22-Oct-2007 -- made this portable between XEmacs and FSFmacs and renamed the command sort-imports
BarryWarsaw 07-Oct-2010 -- Current versions of python-mode.el include this for free as py-sort-imports or C-c C-f
BradCrittenden 22-Oct-2007 -- sort-imports does not work with emacs21. Upgrade to emacs22 and it works just fine.
GavinPanella 19-Aug-2010 -- Updated to new import formatting policy.
(defun sort-imports () (interactive) (save-excursion (let ((open-paren (save-excursion (progn (up-list -1) (point)))) (close-paren (save-excursion (progn (up-list 1) (point)))) (string-lessp-case-insensitive (lambda (a b) (string-lessp (downcase a) (downcase b)))) sorted-imports) (goto-char (1+ open-paren)) (skip-chars-forward " \n\t") (setq sorted-imports (sort (delete-dups (split-string (buffer-substring (point) (save-excursion (goto-char (1- close-paren)) (skip-chars-backward " \n\t") (point))) ", *\\(\n *\\)?")) string-lessp-case-insensitive)) (delete-region open-paren close-paren) (goto-char open-paren) (insert "(\n") (insert (mapconcat (lambda (import) (concat " " import ",\n")) (remove "" sorted-imports) "")) (insert " )") )))
Quoting diffs for reviews
Emacs users: here's a little defun that makes the quoting part of the above workflow a little easier. Just mark the region of text you want to quote and do M-x baw-quote-region (or bind to a key of course). (BarryWarsaw)
(defun baw-quote-region (start end) (interactive "_r") (let ((fill-prefix "> ")) (indent-region start end nil)))
Note that this snippet only works with XEmacs. Testing in emacs-snapshot on gutsy gave this error: execute-extended-command: Invalid control letter '_' (137) in interactive calling string.
In XEmacs the underscore will not cause the region to be deactivated when the command completes. The equivalent would be to set zmacs-region-stays at the end of the function (but only if it's bound so that it wouldn't error on FSFmacs). I don't know if FSFmacs has the equivalent functionality. -- BarryWarsaw
And here's a version that works with GNU Emacs:
(defun equote-region () (interactive) (save-excursion (replace-regexp "^" "> " nil (region-beginning) (region-end))))
Spotting style issues while you edit
You can set up Emacs to highlight some common style issues - long lines, trailing whitespace, annoying silent TABs and such.
I used whitespace-mode.el from the GNU Emacs tree. The mode customization is difficult, so I just copied my custom variables here. This gives you long line highlighting, trailing whitespace, extra newlines at the end of the file, and bright red evil TABs:
(custom-set-variables ... '(global-whitespace-mode t) '(whitespace-style (quote (tabs trailing lines empty tab-mark))) ... (custom-set-faces ... '(whitespace-tab ((((class color) (background dark)) (:background "red1" :foreground "yellow")))) ...
For something lighter, you may want to set the show-trailing-whitespace variable - it will show trailing whitespace as bright red.
Get Emacs to clean up your whitespace
Note: Emacs 21 and later has delete-trailing-whitespace.
(defun baw-whitespace-normalization () "Like untabify, but also cleans up lines with trailing whitespace." (interactive) (save-excursion (save-restriction (untabify (point-min) (point-max)) (goto-char (point-min)) (while (re-search-forward "[ \t]+$" nil t) (let ((bol (save-excursion (beginning-of-line) (point))) (eol (save-excursion (end-of-line) (point)))) (goto-char (match-beginning 0)) (if (and (bolp) (eq (char-after) ?\)) (forward-char 1)) (skip-chars-backward " \t" bol) (delete-region (point) eol) )) ;; Now clean up any trailing blank lines (goto-char (point-max)) (skip-chars-backward " \t\n") (if (not (bolp)) (forward-char 1)) (delete-region (point) (point-max)) ;; But make sure the files ends in a newline (if (not (bolp)) (newline)) )))
Bugify and mail standup notes
Joey has asked that standup notes include the bug number and description. Kiko has contributed a script (lp-bug-ifier.py) to fetch the bug description for all bugs found in STDIN and spit it out on STDOUT.
The following script will copy the current buffer into a compose-mail buffer and bugify it. The launchpad list address is filled in as is most of the subject. It'll need to be customized for other teams than Foundations.
(defun bugify () (interactive) (mark-whole-buffer) (kill-ring-save (region-beginning) (region-end)) (beginning-of-buffer) (set-mark-command nil) (compose-mail) (end-of-buffer) (yank) (mark-whole-buffer) (shell-command-on-region (region-beginning) (region-end) "lp-bug-ifier.py" t) (beginning-of-buffer) (end-of-line) (insert "launchpad@lists.canonical.com") (next-line) (end-of-line) (insert "Foundations standup: 2008-") (delete-other-windows) )
Work with large files
Sometimes you may need to work with large files in Launchpad. To avoid Emacs prompting you about whether you really mean to bring that large file into a buffer, use this:
(setq large-file-warning-threshold nil)
Editing wiki pages
The moin wiki has a mode that you can use to make editing wiki text pretty -- see http://moinmo.in/EmacsForMoinMoin.
If you are using Emacs 23, then the following code will give you nice WYSIWYG editing, almost.
;; Moinmoin mode (require 'moinmoin-mode) (add-hook 'moinmoin-mode-hook (lambda () (variable-pitch-mode t)))
Writing log messages
Below is some code (taken from http://svn.red-bean.com/repos/kfogel/trunk/.emacs) that automates a lot of the pain of writing good log messages.
;;;; Partial automation for writing log messages in Emacs. ;;; ;;; 1) Put this code in your .emacs. ;;; ;;; 2) Reload your .emacs (by "M-x load-file", or just by restarting). ;;; ;;; 3) Bind the entry point to a key, for example to "C-c h": ;;; ;;; (global-set-key "\C-ch" 'kf-log-message) ;;; ;;; Now whenever you're working on Launchpad code, Emacs will help you ;;; write the log message for the change you're working on. Just type ;;; C-c h while inside, say, lib/lp/bugs/interfaces/bugtarget.py, in ;;; the class IHasBugs, in the method getBugCounts(). Emacs will ;;; bring up a file in which to accumulate a log message (by default, ;;; this is the file "msg" at the top of your Bazaar working tree). ;;; ;;; If neither the source file path and class/method information are ;;; currently in the log message file, Emacs will insert them, leaving ;;; point at the end so you can write something about the change. If ;;; some of that information is already in the log message (because ;;; you're doing more work in the same class or method), Emacs will ;;; put point at what it thinks is the most appropriate place in the ;;; log message, and the kill ring (that is, the clipboard) should ;;; have anything else you need -- type C-y to paste in the method ;;; name, and if that's not quite right, type M-y immediately to paste ;;; it in surrounded by parentheses and followed by a colon, which is ;;; a traditional format for starting a new subsection for a given ;;; method in a log message. ;;; ;;; The result is log messages that look like this: ;;; ;;; Working with Abel on bug #506018: ;;; ;;; Use the view instead of the model to prepare data for display. ;;; ;;; * lib/lp/bugs/browser/bugtarget.py: Import datetime, timezone, ;;; BugTaskSearchParams, and BugAttachmentType. ;;; (BugsPatchesView.patch_tasks, ;;; BugsPatchesView.context_can_have_different_bugtargets, ;;; BugsPatchesView.youngest_patch, ;;; BugsPatchesView.patch_age): New properties and methods. ;;; ;;; * lib/lp/bugs/templates/bugtarget-patches.pt: Rewrite. ;;; ;;; * lib/lp/bugs/model/bugtarget.py ;;; (HasBugsBase.fish_patches): Remove this now-unused property. ;;; ;;; * lib/lp/bugs/interfaces/bugtarget.py ;;; (IHasBugs.patches): Likewise remove. ;;; ;;; This format more or less adheres to the guidelines given at ;;; http://subversion.apache.org/docs/community-guide/#log-messages, ;;; which I think are pretty good, though of course every project may ;;; have their own guidelines, "your mileage may vary", "void where ;;; prohibited by law", etc. (defun kf-log-path-derive (path &optional root) "If ROOT is a prefix of PATH, return the remainder; else return PATH." (save-match-data (if (and root (string-prefix-p root path)) (substring path (length root)) path))) (defcustom kf-log-message-file-basename "msg" "*The basename of the file in which to accumulate a log message. See `kf-log-message' for more.") (defun kf-log-message-file (path) "Return the name of the log message accumulation file for PATH: the file `kf-log-message-file-basename' in PATH's directory or in some parent upwards from PATH." (interactive) (let* ((d (directory-file-name path)) ;; If there's a .bzr directory here, that indicates the top ;; of a working tree, which is a good place for a log message. (b (concat d "/.bzr")) ;; Or if there's already a "msg" file here, then go with that. (m (concat d "/" kf-log-message-file-basename))) (save-match-data (while (and d (not (file-exists-p m)) (not (file-exists-p b))) (string-match "\\(.*\\)/[^/]+$" d) (setq d (match-string 1 d) m (concat d "/" kf-log-message-file-basename) b (concat d "/.bzr"))) m))) (defun kf-add-log-current-defun () "Try to determine the current defun using `add-log-current-defun' first, falling back to various custom heuristics if that fails." (let* ((flavor (kf-markup-flavor)) (default-defun (add-log-current-defun))) ;; Work around a bug in add-log-current-defun w.r.t. Subversion's code. (if (string-match "\\.h$" (buffer-file-name)) (setq default-defun nil)) (save-excursion (save-match-data (cond ((and (not default-defun) (eq major-mode 'c-mode)) ;; Handle .h files as well as .c files. (progn (c-beginning-of-statement-1) (or (= (char-after (1- (point))) ?\( ) (search-forward "(" nil t)) (forward-char -1) (forward-sexp -1) (buffer-substring-no-properties (point) (progn (forward-sexp 1) (point))))) ((or (eq flavor 'xml) (eq flavor 'html)) (let* ((section-open-re "\\(<sect[0-9]\\|<div\\)") (section-close-re "</\\(sect[0-9]\\|div\\)>") (title-open-re "<\\(title\\|h[0-9]\\)>") (title-close-re "</\\(title\\|h[0-9]\\)>") (nearest-title-spot (or (save-excursion (re-search-backward title-open-re nil t)) (point-min))) (nearest-section-spot (or (save-excursion (re-search-backward section-open-re nil t)) (point-min))) (title-grabber (lambda () (when (re-search-backward title-open-re nil t) (search-forward ">") (buffer-substring-no-properties (point) (progn (re-search-forward title-close-re) (search-backward "</") (point))))))) (if (> nearest-title-spot nearest-section-spot) (funcall title-grabber) ;; Else we have a section or div with no title, so use ;; one of the usual attributes instead. (goto-char nearest-section-spot) (let ((opoint (point)) (bound (progn (re-search-forward section-close-re) (point)))) (goto-char opoint) (if (re-search-forward "\\(id=\"\\|name=\"\\|label=\"\\|title=\"\\)" nil t) (buffer-substring-no-properties (point) (progn (search-forward "\"") (1- (point)))) (funcall title-grabber)))))) (t (add-log-current-defun))))))) (defun kf-current-defun-to-kill-ring () "Put the name of the current defun into the kill-ring." (interactive) (kill-new (kf-add-log-current-defun))) (defun kf-log-message (short-file-names) "Add to an in-progress log message, based on context around point. If prefix arg SHORT-FILE-NAMES is non-nil, then use basenames only in log messages, otherwise use full paths. The current defun name is always used. If the log message already contains material about this defun, then put point there, so adding to that material is easy. Else if the log message already contains material about this file, put point there, and push onto the kill ring the defun name with log message dressing around it, plus the raw defun name, so yank and yank-next are both useful. Else if there is no material about this defun nor file anywhere in the log message, then put point at the end of the message and insert a new entry for file with defun. See also the function `kf-log-message-file'." (interactive "P") (let* ((this-defun (kf-add-log-current-defun)) (log-file (kf-log-message-file buffer-file-name)) (log-file-dir (file-name-directory log-file)) (this-file (if short-file-names (file-name-nondirectory buffer-file-name) (kf-log-path-derive buffer-file-name log-file-dir)))) (find-file log-file) (goto-char (point-min)) ;; Strip text properties from strings (set-text-properties 0 (length this-file) nil this-file) (set-text-properties 0 (length this-defun) nil this-defun) ;; If log message for defun already in progress, add to it (if (and this-defun ;; we have a defun to work with (search-forward this-defun nil t) ;; it's in the log msg already (save-excursion ;; and it's about the same file (save-match-data (if (re-search-backward ; Ick, I want a real filename regexp! "^\\*\\s-+\\([a-zA-Z0-9-_.@=+^$/%!?(){}<>]+\\)" nil t) (string-equal (match-string 1) this-file) t)))) (if (re-search-forward ":" nil t) (if (looking-at " ") (forward-char 1))) ;; Else no log message for this defun in progress... (goto-char (point-min)) ;; But if log message for file already in progress, add to it. (if (search-forward this-file nil t) (progn (if this-defun (progn (kill-new (format "(%s): " this-defun)) (kill-new this-defun))) (search-forward ")" nil t) (if (looking-at " ") (forward-char 1))) ;; Found neither defun nor its file, so create new entry. (goto-char (point-max)) (if (not (bolp)) (insert "\n")) (insert (format "\n* %s (%s): " this-file (or this-defun ""))) ;; Finally, if no derived defun, put point where the user can ;; type it themselves. (if (not this-defun) (forward-char -3)))))) ;;;; End kf-log-message stuff. ;;;;
Pyflakes for doctests
Get it pyflakes-doctest
Quick XXX
You'll want to change this to have your nick, not jml's.
(defun xxx () "Insert an XXX comment." (interactive) (insert (format "# XXX: jml %s: " (format-time-string "%Y-%m-%d"))))