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

202 lines
7.4 KiB
EmacsLisp

;;; haml-mode.el -- Major mode for editing Haml files
;;; 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)
;;; (add-to-list 'auto-mode-alist '("\\.sass$" . sass-mode))
;;;
;;; Code:
(eval-when-compile (require 'cl))
;; User definable variables
(defgroup haml nil
"Support for the Haml template language."
:group 'languages
:prefix "haml-")
(defcustom haml-mode-hook nil
"Hook run when entering Haml mode."
:type 'hook
:group 'haml)
(defcustom haml-indent-offset 2
"Amount of offset per level of indentation."
:type 'integer
:group 'haml)
(defface haml-tab-face
'((((class color)) (:background "hotpink"))
(t (:reverse-video t)))
"Face to use for highlighting tabs in Haml files."
:group 'faces
:group 'haml)
(defvar haml-indent-function 'haml-indent-p
"This function should look at the current line and return true
if the next line could be nested within this line.")
(defvar haml-block-openers
`("^ *\\([%\\.#][^ \t]*\\)\\(\\[.*\\]\\)?\\({.*}\\)?\\(\\[.*\\]\\)?[ \t]*$"
"^ *[-=].*do[ \t]*\\(|.*|[ \t]*\\)?$"
,(concat "^ *-[ \t]*"
(regexp-opt '("else" "elsif" "rescue" "ensure" "when")))
"^ */\\(\\[.*\\]\\)?[ \t]*$"
"^ *-#"
"^ *:")
"A list of regexps that match lines of Haml that could have
text nested beneath them.")
;; Font lock
(defconst haml-font-lock-keywords
'(("^ *\\(\t\\)" 1 'haml-tab-face)
("^!!!.*" 0 font-lock-constant-face)
("\\('[^']*'\\)" 1 font-lock-string-face append)
("\\(\"[^\"]*\"\\)" 1 font-lock-string-face append)
("&?:\\w+" 0 font-lock-constant-face append)
("@[a-z0-9_]+" 0 font-lock-variable-name-face append)
("| *$" 0 font-lock-string-face)
("^[ \t]*\\(/.*\\)$" 1 font-lock-comment-face append)
("^ *\\(#[a-z0-9_]+\/?\\)" 1 font-lock-keyword-face)
("^ *\\(\\.[a-z0-9_]+\/?\\)" 1 font-lock-type-face)
("^ *\\(%[a-z0-9_]+\/?\\)" 1 font-lock-function-name-face)
("^ *\\(#[a-z0-9_]+\/?\\)" (1 font-lock-keyword-face)
("\\.[a-z0-9_]+" nil nil (0 font-lock-type-face)))
("^ *\\(\\.[a-z0-9_]+\/?\\)" (1 font-lock-type-face)
("\\.[a-z0-9_]+" nil nil (0 font-lock-type-face)))
("^ *\\(\\.[a-z0-9_]+\/?\\)" (1 font-lock-type-face)
("\\#[a-z0-9_]+" nil nil (0 font-lock-keyword-face)))
("^ *\\(%[a-z0-9_]+\/?\\)" (1 font-lock-function-name-face)
("\\.[a-z0-9_]+" nil nil (0 font-lock-type-face)))
("^ *\\(%[a-z0-9_]+\/?\\)" (1 font-lock-function-name-face)
("\\#[a-z0-9_]+" nil nil (0 font-lock-keyword-face)))
("^ *\\([~=-] .*\\)" 1 font-lock-preprocessor-face prepend)
("^ *[\\.#%a-z0-9_]+\\([~=-] .*\\)" 1 font-lock-preprocessor-face prepend)
("^ *[\\.#%a-z0-9_]+\\({[^}]+}\\)" 1 font-lock-preprocessor-face prepend)
("^ *[\\.#%a-z0-9_]+\\(\\[[^]]+\\]\\)" 1 font-lock-preprocessor-face prepend)))
;; Mode setup
(defvar haml-mode-syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?: "." table)
(modify-syntax-entry ?_ "w" table)
table)
"Syntax table in use in haml-mode buffers.")
(defvar haml-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [backspace] 'haml-electric-backspace)
(define-key map "\C-?" 'haml-electric-backspace)
map))
(define-derived-mode haml-mode fundamental-mode "Haml"
"Major mode for editing Haml files.
\\{haml-mode-map}"
(set-syntax-table haml-mode-syntax-table)
(set (make-local-variable 'indent-line-function) 'haml-indent-line)
(set (make-local-variable 'indent-region-function) 'haml-indent-region)
(setq font-lock-defaults '((haml-font-lock-keywords) nil t)))
;; Indentation and electric keys
(defun haml-indent-p ()
"Returns true if the current line can have lines nested beneath it."
(loop for opener in haml-block-openers
if (looking-at opener) return t
return nil))
(defun haml-compute-indentation ()
"Calculate the maximum sensible indentation for the current line."
(save-excursion
(beginning-of-line)
(if (bobp) 0
(loop do (forward-line -1)
while (and (looking-at "^[ \t]$")
(> (point) (point-min))))
(+ (current-indentation)
(if (funcall haml-indent-function) haml-indent-offset
0)))))
(defun haml-indent-region (start end)
"Indent each nonblank line in the region.
This is done by indenting the first line based on
`haml-compute-indentation' and preserving the relative
indentation of the rest of the region.
If this command is used multiple times in a row, it will cycle
between possible indentations."
(save-excursion
(goto-char end)
(setq end (point-marker))
(goto-char start)
;; Don't start in the middle of a line
(unless (bolp) (forward-line 1))
(let (this-line-column current-column
(next-line-column
(if (and (equal last-command this-command) (/= (current-indentation) 0))
(* (/ (- (current-indentation) 1) haml-indent-offset) haml-indent-offset)
(haml-compute-indentation))))
(while (< (point) end)
(setq this-line-column next-line-column
current-column (current-indentation))
;; Delete whitespace chars at beginning of line
(delete-horizontal-space)
(unless (eolp)
(setq next-line-column (save-excursion
(loop do (forward-line 1)
while (and (not (eobp)) (looking-at "^[ \t]*$")))
(+ this-line-column
(- (current-indentation) current-column))))
;; Don't indent an empty line
(unless (eolp) (indent-to this-line-column)))
(forward-line 1)))
(move-marker end nil)))
(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))
(backward-delete-char 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
(provide 'haml-mode)