haml--haml/extra/haml-mode.el

188 lines
5.9 KiB
EmacsLisp

;;; haml-mode.el -- Major mode for editing Haml files
;;; Version 0.0.1
;;; Written by Nathan Weizenbaum
;;; Because Haml's indentation schema is similar
;;; to that of YAML and Python, many indentation-related
;;; functions are similar to those in yaml-mode and python-mode.
;;; To install, save this somewhere and add the following to your .emacs file:
;;;
;;; (add-to-list 'load-path "/path/to/haml-mode.el")
;;; (require 'haml-mode nil 't)
;;;
;;; Code:
;; User definable variables
(defgroup haml nil
"Support for the Haml template language."
:group 'languages
:prefix "haml-")
(defcustom haml-mode-hook nil
"*Hook run by `haml-mode'."
:type 'hook
:group 'haml)
(defcustom haml-indent-offset 2
"*Amount of offset per level of indentation."
:type 'integer
:group 'haml)
(defcustom haml-backspace-function 'backward-delete-char-untabify
"*Function called by `haml-electric-backspace' when deleting backwards."
:type 'function
:group 'haml)
(defface haml-tab-face
'((((class color)) (:background "red" :foreground "red" :bold t))
(t (:reverse-video t)))
"Face to use for highlighting tabs in Haml files."
:group 'faces
:group 'haml)
;; Helper Functions
(defun string-* (str n)
"Concatenates a string with itself n times."
(if (= n 0) ""
(concat str (string-* str (- n 1)))))
(defun hre (str)
"Prepends a Haml-tab-matching regexp to str."
(concat "^\\(" (string-* " " haml-indent-offset) "\\)*" str))
;; Constants
(defconst haml-mode-version "0.0.1" "Version of `haml-mode.'")
(defconst haml-blank-line-re "^[ \t]*$"
"Regexp matching a line containing only whitespace.")
; Base for Regexen matching a Haml tag.
(setq haml-tag-re-base (hre "\\([%\\.#][^ \t]*\\)\\({.*}\\)?\\(\\[.*\\]\\)?"))
(defconst haml-tag-nest-re (concat haml-tag-re-base "[ \t]*$")
"Regexp matching a Haml tag that can have nested elements.")
(defconst haml-tag-re (concat haml-tag-re-base "\\(.?\\)")
"Regexp matching a Haml tag.")
(defconst haml-block-re (hre "[-=].*do[ \t]*\\(|.*|[ \t]*\\)?$")
"Regexp matching a Ruby block in Haml.")
(defconst haml-block-cont-re (hre (concat "-[ \t]*"
(regexp-opt '("else" "elsif"
"rescue" "ensure"
"when"))))
"Regexp matching a continued Ruby block in Haml.")
(defconst haml-html-comment-re (hre "/\\(\\[.*\\]\\)?[ \t]*$")
"Regexp matching a Haml HTML comment command.")
(defconst haml-comment-re (hre "-#[ \t]$")
"Regexp matching a Haml comment command.")
(defconst haml-filter-re (hre ":")
"Regexp matching a Haml filter command.")
;; Mode setup
(defvar haml-mode-map ()
"Keymap used in `haml-mode' buffers.")
(if haml-mode-map
nil
(setq haml-mode-map (make-sparse-keymap))
(define-key haml-mode-map [backspace] 'haml-electric-backspace)
(define-key haml-mode-map "\C-?" 'haml-electric-backspace)
(define-key haml-mode-map "\C-j" 'newline-and-indent))
(defvar sample-font-lock-keywords
'(("function \\(\\sw+\\)" (1 font-lock-function-name-face)))
"Keyword highlighting specification for `sample-mode'.")
(define-derived-mode haml-mode fundamental-mode "Haml"
"Simple mode to edit Haml.
\\{haml-mode-map}"
(set (make-local-variable 'indent-line-function) 'haml-indent-line)
(set (make-local-variable 'font-lock-defaults) '(sample-font-lock-keywords)))
;; Font-lock support
(defvar haml-font-lock-keywords
(list
(cons haml-tag-re '(1 font-lock-function-name-face))
'("function \\(\\sw+\\)" (1 font-lock-function-name-face))
'("^[\t]+" 0 'haml-tab-face t))
"Expressions to highlight in Haml mode.")
;; Indentation and electric keys
(defun haml-compute-indentation ()
"Calculate the maximum sensible indentation for the current line."
(save-excursion
(beginning-of-line)
(if (bobp) 10
(forward-line -1)
(while (and (looking-at haml-blank-line-re)
(> (point) (point-min)))
(forward-line -1))
(+ (current-indentation)
(if (or (looking-at haml-filter-re)
(looking-at haml-comment-re)
(looking-at haml-html-comment-re)
(looking-at haml-block-cont-re)
(looking-at haml-tag-nest-re)
(looking-at haml-block-re))
haml-indent-offset 0)))))
(defun haml-indent-line ()
"Indent the current line.
The first time this command is used, the line will be indented to the
maximum sensible indentation. Each immediately subsequent usage will
back-dent the line by `haml-indent-offset' spaces. On reaching column
0, it will cycle back to the maximum sensible indentation."
(interactive "*")
(let ((ci (current-indentation))
(cc (current-column))
(need (haml-compute-indentation)))
(save-excursion
(beginning-of-line)
(delete-horizontal-space)
(if (and (equal last-command this-command) (/= ci 0))
(indent-to (* (/ (- ci 1) haml-indent-offset) haml-indent-offset))
(indent-to need)))
(if (< (current-column) (current-indentation))
(forward-to-indentation 0))))
(defun haml-electric-backspace (arg)
"Delete characters or back-dent the current line.
If invoked following only whitespace on a line, will back-dent to the
immediately previous multiple of `haml-indent-offset' spaces."
(interactive "*p")
(if (or (/= (current-indentation) (current-column)) (bolp))
(funcall haml-backspace-function arg)
(let ((ci (current-column)))
(beginning-of-line)
(delete-horizontal-space)
(indent-to (* (/ (- ci (* arg haml-indent-offset))
haml-indent-offset)
haml-indent-offset)))))
;; Setup/Activation
(defun haml-mode-version ()
"Diplay version of `haml-mode'."
(interactive)
(message "haml-mode %s" haml-mode-version)
haml-mode-version)
(provide 'haml-mode)
(add-to-list 'auto-mode-alist '("\\.haml\\'" . haml-mode))
;;; haml-mode.el ends here