unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Karl Chen <quarl@NOSPAM.quarl.org>
Subject: Re: css-mode
Date: Tue, 17 Jan 2006 03:39:38 -0800	[thread overview]
Message-ID: <quack.20060117T0339.lthwtgz3wxx@roar.cs.berkeley.edu> (raw)
In-Reply-To: m2bqydvcm8.fsf@alpinobombus.local


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 <foof@synthcode.com>
;; Version:    0.3
;; Keywords:   html, style sheets, languages

;; Copyright (C) 2005 Karl Chen <quarl@quarl.org>

;; 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 "^.*@[a-zA-Z0-9-]*$" 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	other threads:[~2006-01-17 11:39 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2006-01-13 17:27 css-mode Stefan Monnier
2006-01-13 20:33 ` css-mode Edward O'Connor
2006-01-13 22:16   ` css-mode Stefan Monnier
2006-01-14 16:14 ` css-mode Richard M. Stallman
2006-01-15  1:09   ` css-mode Kenichi Handa
2006-01-15 13:34     ` css-mode Alex Schroeder
2006-01-17 11:39       ` Karl Chen [this message]
2006-01-18  9:20         ` including javascript/ecmascript mode (was: css-mode) Mathias Dahl
2006-01-18 12:35           ` including javascript/ecmascript mode Lennart Borgman
2006-01-18 15:43           ` Stefan Monnier
2006-01-18 17:05           ` Edward O'Connor
2006-01-25 23:52             ` Karl Chen
2006-01-22  0:44           ` Juri Linkov
2006-01-22  0:51           ` Desktop fails reading unknown mode (was: including javascript/ecmascript mode) Juri Linkov
2006-01-22 17:44             ` Richard M. Stallman
2006-01-21  2:55       ` css-mode Stefan Monnier

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=quack.20060117T0339.lthwtgz3wxx@roar.cs.berkeley.edu \
    --to=quarl@nospam.quarl.org \
    --cc=quarl+dated+1137929880.5e73d2@nospam.quarl.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).