less-css-mode.el 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. ;;; less-css-mode.el --- Major mode for editing Less CSS files -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2011-2017 Free Software Foundation, Inc.
  3. ;; Author: Steve Purcell <steve@sanityinc.com>
  4. ;; Maintainer: Simen Heggestøyl <simenheg@gmail.com>
  5. ;; Keywords: hypermedia
  6. ;; This file is part of GNU Emacs.
  7. ;; GNU Emacs is free software: you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; GNU Emacs is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;; This mode provides syntax highlighting for Less CSS files
  19. ;; (http://lesscss.org/), plus optional support for compilation of
  20. ;; .less files to .css files at the time they are saved: use
  21. ;; `less-css-compile-at-save' to enable this.
  22. ;;
  23. ;; Command line utility "lessc" is required if setting
  24. ;; `less-css-compile-at-save' to t. To install "lessc" using the
  25. ;; Node.js package manager, run "npm install less".
  26. ;;
  27. ;; Also make sure the "lessc" executable is in Emacs' PATH, example:
  28. ;; (push (expand-file-name "~/.gem/ruby/1.8/bin") exec-path)
  29. ;; or customize `less-css-lessc-command' to point to your "lessc"
  30. ;; executable.
  31. ;;
  32. ;; We target lessc >= 1.4.0, and thus use the `--no-color' flag by
  33. ;; default. You may want to adjust `less-css-lessc-options' for
  34. ;; compatibility with older versions.
  35. ;;
  36. ;; `less-css-mode' is derived from `css-mode', and indentation of
  37. ;; nested blocks may not work correctly with versions of `css-mode'
  38. ;; other than that bundled with recent Emacs.
  39. ;;
  40. ;; You can specify per-file values for `less-css-compile-at-save',
  41. ;; `less-css-output-file-name' or `less-css-output-directory' using a
  42. ;; variables header at the top of your .less file, e.g.:
  43. ;;
  44. ;; // -*- less-css-compile-at-save: t; less-css-output-directory: "../css" -*-
  45. ;;
  46. ;; Alternatively, you can use directory local variables to set the
  47. ;; default value of `less-css-output-directory' for your project.
  48. ;;
  49. ;; In the case of files which are included in other .less files, you
  50. ;; may want to trigger the compilation of a "master" .less file on
  51. ;; save: you can accomplish this with `less-css-input-file-name',
  52. ;; which is probably best set using directory local variables.
  53. ;;
  54. ;; If you don't need CSS output but would like to be warned of any
  55. ;; syntax errors in your .less source, consider using `flymake-less':
  56. ;; https://github.com/purcell/flymake-less.
  57. ;;; Credits
  58. ;; The original code for this mode was, in large part, written using
  59. ;; Anton Johansson's scss-mode as a template -- thanks Anton!
  60. ;; https://github.com/antonj
  61. ;;; Code:
  62. (require 'compile)
  63. (require 'css-mode)
  64. (require 'derived)
  65. (eval-when-compile (require 'subr-x))
  66. (defgroup less-css nil
  67. "Less CSS mode."
  68. :prefix "less-css-"
  69. :group 'css)
  70. (defcustom less-css-lessc-command "lessc"
  71. "Command used to compile Less files.
  72. Should be \"lessc\" or the complete path to your lessc
  73. executable, e.g.: \"~/.gem/ruby/1.8/bin/lessc\"."
  74. :type 'file)
  75. (defcustom less-css-compile-at-save nil
  76. "If non-nil, Less buffers are compiled to CSS after each save."
  77. :type 'boolean)
  78. ;;;###autoload
  79. (put 'less-css-compile-at-save 'safe-local-variable 'booleanp)
  80. (defcustom less-css-lessc-options '("--no-color")
  81. "Command line options for Less executable.
  82. Use \"-x\" to minify output."
  83. :type '(repeat string))
  84. ;;;###autoload
  85. (put 'less-css-lessc-options 'safe-local-variable t)
  86. (defcustom less-css-output-directory nil
  87. "Directory in which to save CSS, or nil to use the Less file's directory.
  88. This path is expanded relative to the directory of the Less file
  89. using `expand-file-name', so both relative and absolute paths
  90. will work as expected."
  91. :type 'directory)
  92. ;;;###autoload
  93. (put 'less-css-output-directory 'safe-local-variable 'stringp)
  94. (defcustom less-css-output-file-name nil
  95. "File name in which to save CSS, or nil to use <name>.css for <name>.less.
  96. This can be also be set to a full path, or a relative path. If
  97. the path is relative, it will be relative to the value of
  98. `less-css-output-dir', if set, or the current directory by
  99. default."
  100. :type 'file)
  101. (make-variable-buffer-local 'less-css-output-file-name)
  102. (defcustom less-css-input-file-name nil
  103. "File name which will be compiled to CSS.
  104. When the current buffer is saved `less-css-input-file-name' file
  105. will be compiled to CSS instead of the current file.
  106. Set this in order to trigger compilation of a \"master\" .less
  107. file which includes the current file. The best way to set this
  108. variable in most cases is likely to be via directory local
  109. variables.
  110. This can be also be set to a full path, or a relative path. If
  111. the path is relative, it will be relative to the the current
  112. directory by default."
  113. :type 'file)
  114. ;;;###autoload
  115. (put 'less-css-input-file-name 'safe-local-variable 'stringp)
  116. (make-variable-buffer-local 'less-css-input-file-name)
  117. (defconst less-css-default-error-regex
  118. "^\\(?:\e\\[31m\\)?\\([^\e\n]*\\|FileError:.*\n\\)\\(?:\e\\[39m\e\\[31m\\)? in \\(?:\e\\[39m\\)?\\([^ \r\n\t\e]+\\)\\(?:\e\\[90m\\)?\\(?::\\| on line \\)\\([0-9]+\\)\\(?::\\|, column \\)\\([0-9]+\\):?\\(?:\e\\[39m\\)?")
  119. ;;; Compilation to CSS
  120. (add-to-list 'compilation-error-regexp-alist-alist
  121. (list 'less-css less-css-default-error-regex 2 3 4 nil 1))
  122. (add-to-list 'compilation-error-regexp-alist 'less-css)
  123. (defun less-css-compile-maybe ()
  124. "Run `less-css-compile' if `less-css-compile-at-save' is non-nil."
  125. (when less-css-compile-at-save
  126. (less-css-compile)))
  127. (defun less-css--output-path ()
  128. "Return the path to use for the compiled CSS file."
  129. (expand-file-name
  130. (or less-css-output-file-name
  131. (concat
  132. (file-name-nondirectory
  133. (file-name-sans-extension buffer-file-name))
  134. ".css"))
  135. (or less-css-output-directory default-directory)))
  136. (defun less-css-compile ()
  137. "Compile the current buffer to CSS using `less-css-lessc-command'."
  138. (interactive)
  139. (message "Compiling Less to CSS")
  140. (let ((compilation-buffer-name-function
  141. (lambda (_) "*less-css-compilation*")))
  142. (save-window-excursion
  143. (with-current-buffer
  144. (compile
  145. (string-join
  146. (append
  147. (list less-css-lessc-command)
  148. (mapcar #'shell-quote-argument less-css-lessc-options)
  149. (list (shell-quote-argument
  150. (or less-css-input-file-name buffer-file-name))
  151. (shell-quote-argument (less-css--output-path))))
  152. " "))
  153. (add-hook 'compilation-finish-functions
  154. (lambda (buf msg)
  155. (unless (string-match-p "^finished" msg)
  156. (display-buffer buf)))
  157. nil
  158. t)))))
  159. ;;; Major mode
  160. ;; TODO:
  161. ;; - interpolation ("@{val}")
  162. ;; - escaped values (~"...")
  163. ;; - JS eval (~`...`)
  164. ;; - custom faces.
  165. (defconst less-css-font-lock-keywords
  166. '(;; Variables
  167. ("@[a-z_-][a-z-_0-9]*" . font-lock-variable-name-face)
  168. ("&" . font-lock-preprocessor-face)
  169. ;; Mixins
  170. ("\\(?:[ \t{;]\\|^\\)\\(\\.[a-z_-][a-z-_0-9]*\\)[ \t]*;" .
  171. (1 font-lock-keyword-face))))
  172. (defvar less-css-mode-syntax-table
  173. (let ((st (make-syntax-table css-mode-syntax-table)))
  174. ;; C++-style comments.
  175. (modify-syntax-entry ?/ ". 124b" st)
  176. (modify-syntax-entry ?* ". 23" st)
  177. (modify-syntax-entry ?\n "> b" st)
  178. ;; Special chars that sometimes come at the beginning of words.
  179. (modify-syntax-entry ?. "'" st)
  180. st))
  181. (defvar less-css-mode-map
  182. (let ((map (make-sparse-keymap)))
  183. (define-key map "\C-c\C-c" 'less-css-compile)
  184. map))
  185. ;;;###autoload (add-to-list 'auto-mode-alist '("\\.less\\'" . less-css-mode))
  186. ;;;###autoload
  187. (define-derived-mode less-css-mode css-mode "Less"
  188. "Major mode for editing Less files (http://lesscss.org/).
  189. Special commands:
  190. \\{less-css-mode-map}"
  191. (font-lock-add-keywords nil less-css-font-lock-keywords)
  192. (setq-local comment-start "//")
  193. (setq-local comment-end "")
  194. (setq-local comment-continue " *")
  195. (setq-local comment-start-skip "/[*/]+[ \t]*")
  196. (setq-local comment-end-skip "[ \t]*\\(?:\n\\|\\*+/\\)")
  197. (add-hook 'after-save-hook 'less-css-compile-maybe nil t))
  198. (provide 'less-css-mode)
  199. ;;; less-css-mode.el ends here