emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: css-mode


From: Karl Chen
Subject: Re: css-mode
Date: Tue, 17 Jan 2006 03:39:38 -0800
User-agent: Gnus/5.110004 (No Gnus v0.4) Emacs/22.0.50 (gnu/linux)

Here is the one I've been using.

I don't care whose version is initially used; I'm sure each has
features to be merged.  It just goes to show that emacs
desperately needs to distribute a CSS mode since everyone has
their own.




;;; css-mode.el

;; css-mode.el             -*- Emacs-Lisp -*-

;; Mode for editing Cascading Style Sheets

;; Created:    <Sat Feb 12 13:51:49 EST 2000>
;; Time-stamp: <2002-11-25 10:21:39 foof>
;; Author:     Alex Shinn <address@hidden>
;; Version:    0.3
;; Keywords:   html, style sheets, languages

;; Copyright (C) 2005 Karl Chen <address@hidden>

;; Copyright (C) 2000-2002 Alex Shinn

;; This program 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 2 of
;; the License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public
;; License along with this program; if not, write to the Free
;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
;; MA 02111-1307 USA

;;; Commentary:
;;
;; This file provides a major mode for editing level 1 and 2 Cascading
;; Style Sheets.  It offers syntax highlighting, indentation, and
;; auto-completion of various CSS elements.
;;
;; To use it, put the following in your .emacs:
;;
;; (autoload 'css-mode "css-mode" "Mode for editing CSS files" t)
;;
;; You may also want something like:
;;
;; (setq auto-mode-alist
;;       (append '(("\\.css$" . css-mode))
;;               auto-mode-alist))
;;

;;; ChangeLog:
;;
;; 2002/11/25 (version 0.3):
;;   * changed to use indent-to to obey tab preference (Vasily Korytov)

;; 2005-11-21 Karl Chen
;;   * Major cleanup


(defgroup css nil
  "Customizations for editing Cascading Style Sheets"
  :group 'languages)

(defcustom css-electric-semi-behavior nil
  "If non-nil semicolons are electric in css mode"
  :group 'css
  :type  'boolean)

(defcustom css-electric-brace-behavior nil
  "If non-nil braces are electric in css mode"
  :group 'css
  :type  'boolean)

(defcustom css-indent-offset 4
  "Number of spaces to indent lines in CSS mode"
  :group 'css
  :type  'integer)

(defcustom css-tab-mode 'auto
  "Behavior of tab in CSS mode"
  :group 'css
  :type  '(choice (const :tag "Always insert" insert)
                  (const :tag "Always indent" indent)
                  (const :tag "Always complete" complete)
                  (const :tag "Auto" auto) ))

(defun css--list-to-table (input)
  (let ((table (make-vector 5 0)))
    (mapcar (lambda (x) (intern x table))
            input)
    table))

(defvar css-at-rule-keywords
  '("import" "media" "page" "font-face" "charset")
  "Keywords for CSS at rules" )

(defvar css-at-rule-table (css--list-to-table css-at-rule-keywords)
  "Table for CSS at rules")

(defvar css-element-keywords
  '("A" "ADDRESS" "B" "BLOCKQUOTE" "BODY" "BR" "CITE"
    "CODE" "DIR" "DIV" "DD" "DL" "DT" "EM" "FORM" "H1"
    "H2" "H3" "H4" "H5" "H6" "HR" "I" "IMG" "KBD" "LI"
    "MENU" "OL" "P" "PRE" "SAMP" "SPAN" "STRONG" "TABLE"
    "TR" "TH" "TD" "TT" "UL" "VAR" )
  "Common CSS elements" )

(defvar css-element-table (css--list-to-table css-element-keywords)
  "Table for CSS elements")


(defvar css-property-keywords
  '("azimuth" "background" "background-attachment" "background-color"
    "background-image" "background-position" "background-repeat" "border"
    "border-collapse" "border-color" "border-spacing" "border-style"
    "border-top" "border-right" "border-bottom" "border-left"
    "border-top-color" "border-right-color" "border-bottom-color"
    "border-left-color" "border-top-style" "border-right-style"
    "border-bottom-style" "border-left-style" "border-top-width"
    "border-right-width" "border-bottom-width" "border-left-width"
    "border-width" "bottom" "caption-side" "clear" "clip" "color"
    "content" "counter-increment" "counter-reset" "cue" "cue-after"
    "cue-before" "cursor" "direction" "display" "elevation" "empty-cells"
    "float" "font" "font-family" "font-size" "font-size-adjust"
    "font-stretch" "font-style" "font-variant" "font-weight" "height"
    "left" "letter-spacing" "line-height" "list-style" "list-style-image"
    "list-style-position" "list-style-type" "margin" "margin-top"
    "margin-right" "margin-bottom" "margin-left" "marker-offset" "marks"
    "max-height" "max-width" "min-height" "min-width" "orphans" "outline"
    "outline-color" "outline-style" "outline-width" "overflow" "padding"
    "padding-top" "padding-right" "padding-bottom" "padding-left" "page"
    "page-break-after" "page-break-before" "page-break-inside" "pause"
    "pause-after" "pause-before" "pitch" "pitch-range" "play-during"
    "position" "quotes" "richness" "right" "size" "speak" "speak-header"
    "speak-numeral" "speak-punctuation" "speech-rate" "stress"
    "table-layout" "text-align" "text-decoration" "text-indent"
    "text-shadow" "text-transform" "top" "unicode-bidi" "vertical-align"
    "visibility" "voice-family" "volume" "white-space" "widows" "width"
    "word-spacing" "z-index")
  "CSS properties")

(defvar css-property-table (css--list-to-table css-property-keywords)
  "Table for CSS properties")

(defconst css-font-lock-keywords
  (purecopy
   (let* ((css-keywords  "\\(url\\|![ \t]*important\\)")
          (css-ident     "[a-zA-Z][a-zA-Z0-9-]*")
          (css-at-rule   (concat "\\(@"
                                 (regexp-opt css-at-rule-keywords t)
                                 "\\)"))
          (css-element-s (concat "^\\(" css-ident "\\)"))
          (css-element (concat "\\(?:[,+>][ \t]*\\)\\(" css-ident "\\)"))
          (css-class  (concat css-element "?\\.\\(" css-ident "\\)"))
          (css-pseudo (concat ":\\(" css-ident "\\)"))
          (css-attr (concat "\\[\\(" css-ident "\\)\\]"))
          (css-id (concat "#\\(" css-ident "\\)"))
          (css-declaration (concat "[ \t][ \t]*\\(\\<" css-ident "\\>\\):")) )
     (list
      (list css-keywords    1 'font-lock-keyword-face)
      (list css-at-rule     1 'font-lock-keyword-face)
      (list css-element-s   1 'font-lock-function-name-face)
      (list css-element     1 'font-lock-function-name-face)
      (list css-class       2 'font-lock-type-face)
      (list css-pseudo      1 'font-lock-constant-face)
      (list css-attr        1 'font-lock-variable-name-face)
      (list css-id          1 'font-lock-string-face)
      (list css-declaration 1 'font-lock-variable-name-face) )))
  "Expressions for highlighting in css-mode.")

(defvar css-mode-syntax-table
  (let ((table (make-syntax-table)))
    (modify-syntax-entry ?+  "."    table)
    (modify-syntax-entry ?=  "."    table)
    (modify-syntax-entry ?<  "."    table)
    (modify-syntax-entry ?>  "."    table)
    (modify-syntax-entry ?-  "w"    table)
    (modify-syntax-entry ?/  "w"    table)
    (modify-syntax-entry ?.  "w"    table)
    (modify-syntax-entry ?\' "\""   table)
    ;; (modify-syntax-entry ?/  ". 1456" table) ; XEmacs(?)
    (modify-syntax-entry ?/  ". 124b" table) ; GNU Emacs
    (modify-syntax-entry ?*  ". 23"   table)
    (modify-syntax-entry ?\n "> b"  table)
    ;; Give CR the same syntax as newline, for selective-display
    (modify-syntax-entry ?\^m "> b" table)
    table)

  "Syntax table used in `css-mode' buffers.")

(defvar css-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map ";"        'css-electric-semicolon)
    (define-key map "{"        'css-electric-brace)
    (define-key map "}"        'css-electric-brace)
    (define-key map "\t"       'css-tab-function)
    (define-key map "\C-c\C-c" 'css-comment-region)
    (define-key map "\C-c\C-a" 'css-complete-at-keyword)
    (define-key map "\C-c\C-e" 'css-complete-element)
    (define-key map "\C-c\C-p" 'css-complete-property)
    map)
  "Keymap used in `css-mode' buffers.")

;;; Utility functions

(defun css-in-comment-p ()
  "Returns non-nil if point is within a comment."
  (save-excursion
    (let ((here (point)))
      (and (search-backward "/*" nil t)
           (not (search-forward "*/" here t))))))

(defun css-complete-symbol (&optional table predicate prettify)
  (let* ((end (point))
         (beg (save-excursion
                (skip-syntax-backward "w")
                (point)))
         (pattern (buffer-substring beg end))
         (table (or table obarray))
         (completion (try-completion pattern table predicate)))
    (cond ((eq completion t))
          ((null completion)
           (error "Can't find completion for \"%s\"" pattern))
          ((not (string-equal pattern completion))
           (delete-region beg end)
           (insert completion))
          (t
           (message "Making completion list...")
           (let ((list (all-completions pattern table predicate)))
             (if prettify
                 (setq list (funcall prettify list)))
             (with-output-to-temp-buffer "*Help*"
               (display-completion-list list)))
           (message "Making completion list...%s" "done")))))

(defsubst css--backup-to-nonempty-line ()
  (while (and (= 0 (forward-line -1))
              (or (looking-at "^[ \t]*$")
                  (css-in-comment-p) ))
    ;; Jump to a non comment/white-space line
    ))

(defun css--indent-depth ()
  (let ((depth 0))
    (save-excursion
      (css--backup-to-nonempty-line)
      (cond ((looking-at "\\([ \t]*\\)\\([^ \t].*\\)?{[ \t]*$")
             (setq depth (+ (- (match-end 1) (match-beginning 1))
                            css-indent-offset )))
            ((looking-at "\\([ \t]*\\)[^ \t]")
             (setq depth (- (match-end 1) (match-beginning 1))) )
            (t (setq depth 0))))

    (save-excursion
      (beginning-of-line)
      (if (looking-at "[ \t]*}")
          (setq depth (- depth css-indent-offset))))

    depth))

(defun indent-line-to-excursion (column)
  (setq column (max column 0))
  (if (> (current-column) (current-indentation))
      (save-excursion (indent-line-to column))
    (indent-line-to column)))

(defun css-indent-line ()
  "Indent the current line"
  (unless (or (css-in-comment-p)
              (looking-at "[ \t]*/\\*"))
    (indent-line-to-excursion (css--indent-depth))))

;;; Commands

(defun css-electric-semicolon (arg)
  "Insert a semi-colon, and possibly indent line.
If numeric argument is not given, or is 1, auto-indent according to
`css-electric-semi-behavior'.  If arg is 0, do not auto-indent, if
arg is 2 always auto-indent, and if arg is anything else invert the
usual behavior."
  (interactive "P")
  ;; insert a semicolon
  (self-insert-command 1)
  ;; maybe do electric behavior
  (or (css-in-comment-p)
      (and (eq arg 1)
           css-electric-semi-behavior
           (css-indent-line) )
      (and (eq arg 2)
           (css-indent-line) )
      (eq arg 0)
      (or (not css-electric-semi-behavior)
          (css-indent-line) )))


(defun css-electric-brace (arg)
  "Insert a brace, and possibly indent line.
If numeric argument is not given, or is 1, auto-indent according to
`css-electric-brace-behavior'.  If arg is 0, do not auto-indent, if
arg is 2 always auto-indent, and if arg is anything else invert the
usual behavior."
  (interactive "P")
  ;; insert a brace
  (self-insert-command 1)
  ;; maybe do electric behavior
  (or (css-in-comment-p)
      (and (eq arg 1)
           css-electric-brace-behavior
           (css-indent-line) )
      (and (eq arg 2)
           (css-indent-line) )
      (eq arg 0)
      (or (not css-electric-brace-behavior)
          (css-indent-line) )))

(defun css-complete-at-keyword ()
  "Complete the standard element at point"
  (interactive)
  (let ((completion-ignore-case t))
    (css-complete-symbol css-at-rule-table) ))

(defun css-complete-element ()
  "Complete the standard element at point"
  (interactive)
  (let ((completion-ignore-case t))
    (css-complete-symbol css-element-table) ))

(defun css-complete-property ()
  "Complete the standard element at point"
  (interactive)
  (let ((completion-ignore-case t))
    (css-complete-symbol css-property-table) ))


(defun css-tab-function (&optional arg)
  "Function to call when tab is pressed in CSS mode.

With a prefix arg, insert a literal tab.  Otherwise behavior depends
on the value of `css-tab-mode'.  If it's 'insert, insert a literal
tab.  If it's 'indent, indent the current line, and if it's 'complete,
try to complete the expression before point.  A value of 'auto means
to inspect the current line, and indent if point is at the beginning
or end of the line, but complete if it's at a word.

There are three possible completions to perform:
`css-complete-at-keyword' if the point is after an '@',
`css-complete-property' if point is inside a block, and
`css-complete-element' otherwise."
  (interactive "P")
  (let* ((end (point))
         (start (prog2
                    (beginning-of-line)
                    (point)
                  (goto-char end) ))
         (prefix (buffer-substring start end)) )
    (cond ((or arg (eq css-tab-mode 'insert))
           (insert "\t"))
          ((eq css-tab-mode 'indent)
           (css-indent-line))
          ((and (not (eq css-tab-mode 'complete))
                (or (string-match "^[ \t]*[{}]?[ \t]*$" prefix)
                    (string-match "^.*;[ \t]*" prefix) ))
           ;; indent at the beginning or end of a line
           (css-indent-line))
          ((string-match "address@hidden" prefix)
           (css-complete-at-keyword))
          ((string-match "^\\([ \t]+.*\\|.*\{[ \t]*[a-zA-Z]+\\)$" prefix)
           ;; complete properties on non-starting lines
           (css-complete-property))
          ;; otherwise try an element
          (t (css-complete-element)) )))


;;;###autoload
(define-derived-mode css-mode fundamental-mode "CSS"
  "Major mode for editing CSS files"

  ;; local variables
  (make-local-variable 'parse-sexp-ignore-comments)
  (make-local-variable 'comment-start-skip)
  (make-local-variable 'comment-start)
  (make-local-variable 'comment-end)
  (make-local-variable 'block-comment-start)
  (make-local-variable 'block-comment-end)
  (make-local-variable 'block-comment-left)
  ;; (make-local-variable 'block-comment-right)
  ;; (make-local-variable 'block-comment-top-right)
  ;; (make-local-variable 'block-comment-bot-left)
  ;; (make-local-variable 'block-comment-char)
  (make-local-variable 'paragraph-start)
  (make-local-variable 'paragraph-separate)
  (make-local-variable 'paragraph-ignore-fill-prefix)
  (make-local-variable 'font-lock-defaults)
  (setq font-lock-defaults '(css-font-lock-keywords))
  ;; now set their values
  (setq parse-sexp-ignore-comments t
        comment-start-skip "/\\*+ *\\|// *"
        comment-start "/\\*"
        comment-end   "\\*/")
  (setq block-comment-start     "/*"
        block-comment-end       "*/"
        ;; block-comment-left      " * "
        ;; block-comment-right     " *"
        ;; block-comment-top-right ""
        ;; block-comment-bot-left  " "
        ;; block-comment-char      ?*
        )
  (setq indent-line-function   'css-indent-line
        indent-region-function 'css-indent-region
        paragraph-ignore-fill-prefix t
        paragraph-start (concat "\\|$" page-delimiter)
        paragraph-separate paragraph-start)
  ;; (run-hooks 'css-mode-hook)
  )


(provide 'css-mode)

;;; css-mode.el ends here.


-- 
Karl 2006-01-17 03:38





reply via email to

[Prev in Thread] Current Thread [Next in Thread]