;;; emmet-mode.el --- Unofficial Emmet's support for emacs ;; Copyright (C) 2014- Dmitry Mukhutdinov (@flyingleafe https://github.com/flyingleafe) ;; Copyright (C) 2014- William David Mayo (@pbocks https://github.com/pobocks) ;; Copyright (C) 2013- Shin Aoyama (@smihica https://github.com/smihica) ;; Copyright (C) 2009-2012 Chris Done ;; Version: 1.0.10 ;; Package-Version: 20180613.341 ;; Package-Commit: 1acb821e0142136344ccf40c1e5fb664d7db2e70 ;; Author: Shin Aoyama ;; URL: https://github.com/smihica/emmet-mode ;; Last-Updated: 2014-08-11 Mon ;; Keywords: convenience ;; 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 ;; 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 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: ;; ;; Unfold CSS-selector-like expressions to markup. Intended to be used ;; with sgml-like languages; xml, html, xhtml, xsl, etc. ;; ;; See `emmet-mode' for more information. ;; ;; Copy emmet-mode.el to your load-path and add to your .emacs: ;; ;; (require 'emmet-mode) ;; ;; Example setup: ;; ;; (add-to-list 'load-path "~/Emacs/emmet/") ;; (require 'emmet-mode) ;; (add-hook 'sgml-mode-hook 'emmet-mode) ;; Auto-start on any markup modes ;; (add-hook 'html-mode-hook 'emmet-mode) ;; (add-hook 'css-mode-hook 'emmet-mode) ;; ;; Enable the minor mode with M-x emmet-mode. ;; ;; See ``Test cases'' section for a complete set of expression types. ;; ;; If you are hacking on this project, eval (emmet-test-cases) to ;; ensure that your changes have not broken anything. Feel free to add ;; new test cases if you add new features. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; History: ;; ;; This is a fork of zencoding-mode to support Emmet's feature. ;; zencoding-mode (https://github.com/rooney/zencoding) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Code: (defconst emmet-mode:version "1.0.10") (with-no-warnings (require 'cl)) ;; for portability with < 24.3 EMACS (unless (fboundp 'cl-labels) (fset 'cl-labels 'labels)) (unless (fboundp 'cl-flet) (fset 'cl-flet 'flet)) ;; < 22.1 (unless (fboundp 'string-to-number) (fset 'string-to-number 'string-to-int)) (defmacro emmet-defparameter (symbol &optional initvalue docstring) `(progn (defvar ,symbol nil ,docstring) (setq ,symbol ,initvalue))) (defun emmet-join-string (lis joiner) (mapconcat 'identity lis joiner)) (defun emmet-get-keys-of-hash (hash) (let ((ks nil)) (maphash #'(lambda (k v) (setq ks (cons k ks))) hash) ks)) (defun emmet-get-vals-of-hash (hash) (let ((vs nil)) (maphash #'(lambda (k v) (setq vs (cons v vs))) hash) vs)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Generic parsing macros and utilities (defmacro emmet-aif (test-form then-form &rest else-forms) "Anaphoric if. Temporary variable `it' is the result of test-form." `(let ((it ,test-form)) (if it ,then-form ,@(or else-forms '(it))))) (defmacro emmet-pif (test-form then-form &rest else-forms) "Parser anaphoric if. Temporary variable `it' is the result of test-form." `(let ((it ,test-form)) (if (not (eq 'error (car it))) ,then-form ,@(or else-forms '(it))))) (defmacro emmet-parse (regex nums label &rest body) "Parse according to a regex and update the `input' variable." `(emmet-aif (emmet-regex ,regex input ',(number-sequence 0 nums)) (let ((input (elt it ,nums))) ,@body) `,`(error ,(concat "expected " ,label)))) (defmacro emmet-run (parser then-form &rest else-forms) "Run a parser and update the input properly, extract the parsed expression." `(emmet-pif (,parser input) (let ((input (cdr it)) (expr (car it))) ,then-form) ,@(or else-forms '(it)))) (defmacro emmet-por (parser1 parser2 then-form &rest else-forms) "OR two parsers. Try one parser, if it fails try the next." `(emmet-pif (,parser1 input) (let ((input (cdr it)) (expr (car it))) ,then-form) (emmet-pif (,parser2 input) (let ((input (cdr it)) (expr (car it))) ,then-form) ,@else-forms))) (defmacro emmet-find (direction regexp &optional limit-of-search repeat-count) "Regexp-search in given direction, returning the position (or nil) and leaving the point in place." `(save-excursion (if (,(intern (concat "re-search-" direction)) ,regexp ,limit-of-search t ,repeat-count) (match-beginning 0)))) (defun emmet-regex (regexp string refs) "Return a list of (`ref') matches for a `regex' on a `string' or nil." (if (string-match (concat "^" regexp "\\([^\n]*\\)$") string) (mapcar (lambda (ref) (match-string ref string)) (if (sequencep refs) refs (list refs))) nil)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Emmet minor mode (defgroup emmet nil "Customization group for emmet-mode." :group 'convenience) (defun emmet-expr-on-line () "Extract a emmet expression and the corresponding bounds for the current line." (let* ((end (point)) (start (emmet-find-left-bound)) (line (buffer-substring-no-properties start end)) (expr (emmet-regex "\\([ \t]*\\)\\([^\n]+\\)" line 2))) (if (first expr) (list (first expr) start end)))) (defun emmet-find-left-bound () "Find the left bound of an emmet expr" (save-excursion (save-match-data (let ((char (char-before)) (in-style-attr (looking-back "style=[\"'][^\"']*" nil)) (syn-tab (make-syntax-table))) (modify-syntax-entry ?\\ "\\") (while char (cond ((and in-style-attr (member char '(?\" ?\'))) (setq char nil)) ((member char '(?\} ?\] ?\))) (with-syntax-table syn-tab (backward-sexp) (setq char (char-before)))) ((eq char ?\>) (if (looking-back "<[^>]+>" (line-beginning-position)) (setq char nil) (progn (backward-char) (setq char (char-before))))) ((not (string-match-p "[[:space:]\n;]" (string char))) (backward-char) (setq char (char-before))) (t (setq char nil)))) (point))))) (defcustom emmet-indentation 4 "Number of spaces used for indentation." :type '(number :tag "Spaces") :group 'emmet) (defcustom emmet-indent-after-insert t "Indent region after insert?" :type 'boolean :group 'emmet) (defcustom emmet-use-style-tag-and-attr-detection t "When true, enables detection of style tags and attributes in HTML to provide proper CSS abbreviations completion." :type 'boolean :group 'emmet) (defcustom emmet-self-closing-tag-style "/" "Self-closing tags style. This determines how Emmet expands self-closing tags. E.g., FOO is a self-closing tag. When expanding \"FOO\": When \" /\", the expansion is \"\". When \"/\", the expansion is \"\". When \"\", the expansion is \"\". Default value is \"/\". NOTE: only \" /\", \"/\" and \"\" are valid." :type '(choice (const :tag " />" " /") (const :tag "/>" "/") (const :tag ">" "")) :group 'emmet) (defvar emmet-use-css-transform nil "When true, transform Emmet snippets into CSS, instead of the usual HTML.") (make-variable-buffer-local 'emmet-use-css-transform) (defvar emmet-use-sass-syntax nil "When true, uses Sass syntax for CSS abbreviations expanding, e. g. without semicolons") (make-variable-buffer-local 'emmet-use-sass-syntax) (defvar emmet-css-major-modes '(css-mode scss-mode sass-mode less-mode less-css-mode) "Major modes that use emmet for CSS, rather than HTML.") (defvar emmet-fallback-filter '("html") "Fallback filter for `emmet-default-filter', if none is found.") (defvar emmet-file-filter nil "File local filter used by `emmet-default-filter'.") (make-variable-buffer-local 'emmet-file-filter) (defun emmet-transform (input) (if (or (emmet-detect-style-tag-and-attr) emmet-use-css-transform) (emmet-css-transform input) (emmet-html-transform input))) (defun emmet-detect-style-tag-and-attr () (let* ((style-attr-end "[^=][\"']") (style-attr-begin "style=[\"']") (style-tag-end "") (style-tag-begin "