Find file
Fetching contributors…
Cannot retrieve contributors at this time
467 lines (390 sloc) 14.6 KB
;;; ts-mode.el --- An Emacs major mode for editing TypoScript files
;; Copyright (C) 2009 Joachim Mathes
;; Author: Joachim Mathes <joachim <underscore> mathes <at> web <dot> de>
;; Created: July 2009
;; Version: 0.1
;; Last-Updated: Thu Aug 13 00:18:18 CEST 2009
;; By: Joachim Mathes
;; Update #: 4
;; Keywords: files
;; URL:
;; EmacsWiki: TypoScriptMode
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; Installation:
;; To install just drop this file into a directory on your load-path and
;; byte-compile it. To set up Emacs to automatically edit files ending in ".ts"
;; using ts-mode add the following to your ~/.emacs file (GNU Emacs) or
;; ~/.xemacs/init.el file (XEmacs):
;; (setq auto-mode-alist (cons '("\\.ts$" . ts-mode) auto-mode-alist))
;; (autoload 'ts-mode "ts-mode" "TypoScript file editing mode." t)
;; Description:
;; This is a major mode for editing TypoScript input files. It is developed to
;; support syntax highlighting, indentation and folding of blocks.
;; This file is *NOT* part of GNU Emacs.
;;; History:
;;; Code:
(defconst ts-version "0.1"
"`ts-mode' version number.")
;; User definable variables
;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
(defgroup typoscript nil
"Major mode for editing TypoScript files."
:prefix "ts-"
:group 'languages)
(defcustom ts-newline-function 'newline-and-indent
"Function to be called upon pressing `RET'."
:type '(choice (const newline)
(const newline-and-indent)
(const reindent-then-newline-and-indent))
:group 'typoscript)
(defcustom ts-block-indentation 2
"The indentation relative to a predecessing line which begins a new block.
In TypoScript blocks start with the left parenthesis `(' or the left brace
:type 'integer
:group 'typoscript)
(defcustom ts-fold-foreground-color "white"
"The foreground color used to highlight the folded block.
The default value is `white'. For a list of all available colors use `M-x
:type 'color
:group 'typoscript)
(defcustom ts-fold-background-color "DodgerBlue1"
"The background color used to highlight the folded block.
The default value is `DodgerBlue1'. For a list of all available colors use
`M-x list-colors-display'"
:type 'color
:group 'typoscript)
;; Internal variables
;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
(defvar ts-mode-hook nil
"Hook called by `ts-mode'.")
(defvar ts-classes-face 'ts-classes-face
"Face for TypoScript classes.")
(make-face 'ts-classes-face)
(defvar ts-path-face 'ts-path-face
"Face for TypoScript paths.")
(make-face 'ts-path-face)
(defvar ts-block-face 'ts-block-face
"Face for TypoScript blocks.")
(make-face 'ts-block-face)
(defvar ts-conditional-face 'ts-conditional-face
"Face for TypoScript conditionals.")
(make-face 'ts-conditional-face)
(defvar ts-html-face 'ts-html-face
"Face for TypoScript HTML tags.")
(make-face 'ts-html-face)
(defun ts-font-lock-mode-hook ()
"Defines a TypoScript font lock mode hook."
(or (face-differs-from-default-p 'ts-classes-face)
(copy-face 'font-lock-keyword-face 'ts-classes-face))
(copy-face 'font-lock-builtin-face 'ts-path-face)
(set-face-foreground 'ts-path-face
"DarkTurquoise" nil)
(copy-face 'font-lock-builtin-face 'ts-block-face)
(set-face-foreground 'ts-block-face
"DodgerBlue1" nil)
(copy-face 'font-lock-builtin-face 'ts-conditional-face)
(set-face-foreground 'ts-conditional-face
"maroon" nil)
(copy-face 'font-lock-builtin-face 'ts-html-face)
(set-face-foreground 'ts-html-face
"ForestGreen" nil))
(defvar ts-font-lock-keywords
(let ((kw1 (mapconcat 'identity
;; Basic TypoScript classes
;; Paths
'("^[ \t]*\\([[:alnum:]-_\\.]+\\)[ \t]*[=<>]" 1 'ts-path-face)
;; Blocks
'("^[ \t]*\\([[:alnum:]-_\\.]+\\)[ \t]*[{(]" 1 'ts-block-face)
;; Periods
;;'("^[ \t]*" "\\(\\.\\)" nil nil (1 'default t))
;; Classes (keywords)
(list (concat "\\<\\(" kw1 "\\)\\>") 1 'ts-classes-face t)
;; Conditional expressions `[...]'
'("^[ \t]*\\(\\[.+?\\]\\)[ \t]*$" 1 'ts-conditional-face)
;; Comment lines beginning with hash symbol `#'
'("^[ \t]*\\(#.*\\)$" 1 'font-lock-comment-face)
;; HTML special character encodings on the right side of the operator
'("\\(=\\|=<\\|>\\|:=\\)" "\\(&[#[:alnum:]]+;\\)" nil nil (0 'ts-html-face))
;; HTML tags
'("=<?\\|>\\|:=\\|[ \t]*" "\\(<[^<]+?>\\)" nil nil (0 'ts-html-face))
;; HTML color definitions
'("#[[:xdigit:]]\\{6\\}[ \t\n]+" 0 'ts-html-face t)))
"Expressions to highlight in TypoScript mode.")
(defvar ts-mode-syntax-table nil
"Syntax table used in TypoScript Mode buffers.")
(defvar ts-mode-map ()
"Key map used in TypoScript Mode buffers.")
(defvar ts-highlight-overlays [nil nil]
"A vector of different overlay to do highlighting.
This vector concerns only highlighting of horizontal lines.")
;; Functions
;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
(defun ts-mode ()
"Major mode for editing TypoScript files.
Bug reports, suggestions for new features and critics should go to
This mode knows about syntax highlighting, indentation and folding of
ts-newline-function\t\tbehaviour after pressing `RET'
ts-block-indentation\t\tindentation value
ts-fold-foreground-color\t\tforeground color of folded measurement block
ts-fold-background-color\t\tbackground color of folded measurement block"
;; Set up local variables
(make-local-variable 'font-lock-defaults)
(make-local-variable 'comment-start)
(make-local-variable 'comment-end)
(make-local-variable 'comment-start-skip)
(make-local-variable 'indent-line-function)
(make-local-variable 'defun-prompt-regexp)
(when (not ts-mode-syntax-table)
(setq ts-mode-syntax-table (make-syntax-table))
;; Parenthesis, brackets and braces
(modify-syntax-entry ?\( "()" ts-mode-syntax-table)
(modify-syntax-entry ?\) ")(" ts-mode-syntax-table)
(modify-syntax-entry ?\[ "(]" ts-mode-syntax-table)
(modify-syntax-entry ?\] ")[" ts-mode-syntax-table)
(modify-syntax-entry ?\{ "(}" ts-mode-syntax-table)
(modify-syntax-entry ?\} "){" ts-mode-syntax-table)
;; Comment delimiters
(modify-syntax-entry ?/ ". 124b" ts-mode-syntax-table)
(modify-syntax-entry ?* ". 23" ts-mode-syntax-table)
(modify-syntax-entry ?\n "> b" ts-mode-syntax-table)
(modify-syntax-entry ?\" "." ts-mode-syntax-table)
(modify-syntax-entry ?. "." ts-mode-syntax-table))
(set-syntax-table ts-mode-syntax-table)
(add-hook 'font-lock-mode-hook 'ts-font-lock-mode-hook)
(setq defun-prompt-regexp "^[ \t]*\\([[:alnum:]-_\\.]+\\)[ \t]*")
(if ts-mode-map
(setq ts-mode-map (make-sparse-keymap))
(define-key ts-mode-map "\r" 'ts-newline)
(define-key ts-mode-map "\C-c\C-e" 'ts-fold-block)
(define-key ts-mode-map "\C-c\C-a" 'ts-unfold-block)
(define-key ts-mode-map "\C-c\C-u\C-r" 'ts-unfold-region)
(define-key ts-mode-map "\C-c\C-u\C-b" 'ts-unfold-buffer)
(define-key ts-mode-map "}" 'ts-electric-brace)
(define-key ts-mode-map ")" 'ts-electric-brace))
(use-local-map ts-mode-map)
(setq major-mode 'ts-mode
mode-name "TypoScript"
font-lock-defaults '(ts-font-lock-keywords)
comment-start "# "
comment-end ""
comment-start-skip "# "
indent-line-function 'ts-indent-line)
;; Run the mode hook.
(if ts-mode-hook
(run-hooks 'ts-mode-hook)))
(defun ts-newline ()
"Call the dedicated newline function.
The variable `ts-newline-function' decides which newline function to
(funcall ts-newline-function))
(defun ts-indent-line ()
"Indent current line for TypoScript mode."
(let ((cp (point)) ; current point
(cc (current-column)) ; current column
(ci (current-indentation)) ; current indentation
(cl (line-number-at-pos)) ; current line
(counter 0)
ps ; parser state
psp ; parser state position
;; Evaluate parser state
(setq ps (ts-parser-state))
;; Check if parser state position is:
;; -> Inside a comment
((nth 8 ps)
(setq psp (nth 8 ps))
(goto-char psp)
(setq save-indent-column (+ (current-column)
;; Check if parser state position is:
;; -> Inside a parenthetical grouping
((nth 1 ps)
(setq psp (nth 1 ps))
;; Check if point is looking at a string and a closing curly brace
((looking-at "[ \t[:alnum:]]*[)}]")
(goto-char psp)
(setq save-indent-column (current-column)))
(goto-char psp)
(setq save-indent-column (+ (current-column)
;; Check if parser state position is:
;; -> nil
;; Skip empty lines
(forward-line -1)
(while (and (looking-at "^[ \t]*\n")
(not (bobp)))
(forward-line -1))
(setq save-indent-column (current-column)))))
;; Set indentation value on current line
(backward-delete-char-untabify (current-column))
(indent-to save-indent-column)
(if (> cc ci)
(forward-char (- cc ci)))))
(defun ts-parser-state ()
"Return the parser state at point."
(let ((here (point))
;; For correct indentation the character position of the start of the
;; innermost parenthetical grouping has to be found.
(goto-char (point-min))
;; Now get the parser state, i.e. the depth in parentheses.
(setq sps (parse-partial-sexp (point) here)))
(defun ts-block-start ()
"Return buffer position of the last unclosed enclosing block.
If nesting level is zero, return nil."
(let ((status (ts-parser-state)))
(if (<= (car status) 0)
(car (cdr status)))))
;; Electric characters
(defun ts-electric-brace (arg)
"Insert closing brace.
Argument ARG prefix."
(interactive "*P")
;; Insert closing brace.
(self-insert-command (prefix-numeric-value arg))
(when (and (looking-at "[ \t]*$")
(looking-back "^[ \t]*[})]"))
;; Folding
(defun ts-fold-block ()
"Hide the block on which point currently is located."
(let ((current-point (point))
(block-start (ts-block-start)))
(if (not block-start)
(message "Point is not within a block.")
;; Look for block start
(goto-char (ts-block-start))
(setq block-start (point)))
(when block-start
(let ((block-name
;; Save block name
(goto-char block-start)
"^[ \t]*\\(.*?\\)[ \t]*{")
(match-string 1)))
;; Look for block end
(goto-char block-start)
;; Variable for overlay
;; ------------------------------------------------------------------
;; The following local variables are defined up to here:
;; [1] block-start: point of block start, at the beginning
;; of the line; nil otherwise
;; [2] block-name : name of block, i.e. the object path
;; [3] block-end : point of block end, at the end of the
;; line which contains the closing curly brace `}
;; ------------------------------------------------------------------
;; Check if end of measurement block is beyond point;
;; call fold function otherwise
(if (>= block-end current-point)
(ts-fold block-start block-end block-name)
(message "Error: No valid block found."))
;; Indent overlay
(goto-char block-start)
(defun ts-fold (block-start block-end block-name)
"Fold block.
The block starts at BLOCK-START and ends at BLOCK-END. Its
BLOCK-NAME is the TypoScript object path."
(let (ts-overlay)
;; Check if block-start and block-end are valid values, i.e. not nil
(if (or (eq block-start nil)
(eq block-end nil))
(message "Error: No valid block found.")
;; Make an overlay and hide block
(setq ts-overlay (make-overlay block-start block-end
(current-buffer) t nil))
(overlay-put ts-overlay 'category 'ts-fold)
(overlay-put ts-overlay 'evaporate t)
(overlay-put ts-overlay 'mouse-face 'highlight)
(overlay-put ts-overlay 'display (concat "["
(propertize block-name
(overlay-put ts-overlay 'font-lock-face `(:foreground ,ts-fold-foreground-color
:background ,ts-fold-background-color))
(overlay-put ts-overlay 'help-echo (concat
"Folded block: "
(defun ts-unfold-buffer ()
"Unfold all blocks in the buffer."
(ts-unfold-region (point-min) (point-max)))
(defun ts-unfold-region (start end)
"Unfold all blocks in the region.
The region delimiters are START and END."
(interactive "r")
(let ((ts-overlays (overlays-in start end)))
(ts-unfold-overlays ts-overlays)))
(defun ts-unfold-block ()
"Unfold block at point."
(let ((ts-overlays (overlays-at (point))))
(ts-unfold-overlays ts-overlays)))
(defun ts-unfold-overlays (ts-overlays)
"Unfold all overlays set by ts-fold in TS-OVERLAYS.
Return non-nil if an unfold happened, nil otherwise."
(let (found)
(dolist (overlay ts-overlays)
(when (eq (overlay-get overlay 'category) 'ts-fold)
(delete-overlay overlay)
(setq found t)))
(provide 'ts-mode)
;;; ts-mode.el ends here