2017-08-01 18:23:21 +00:00
;;; less-css-mode.el --- Major mode for editing Less CSS files -*- lexical-binding: t; -*-
2020-01-01 00:19:43 +00:00
;; Copyright (C) 2011-2020 Free Software Foundation, Inc.
2017-08-01 18:23:21 +00:00
2017-08-01 18:15:45 +00:00
;; Author: Steve Purcell <steve@sanityinc.com>
2017-08-01 18:23:21 +00:00
;; Maintainer: Simen Heggestøyl <simenheg@gmail.com>
;; Keywords: hypermedia
;; This file is part of GNU Emacs.
;; GNU Emacs 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 of the License, or
;; (at your option) any later version.
;; GNU Emacs 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
2017-09-13 22:52:52 +00:00
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
2017-08-01 18:23:21 +00:00
2017-08-01 18:15:45 +00:00
;;; Commentary:
2017-08-01 18:23:21 +00:00
;; This mode provides syntax highlighting for Less CSS files
;; (http://lesscss.org/), plus optional support for compilation of
;; .less files to .css files at the time they are saved: use
;; `less-css-compile-at-save' to enable this.
2017-08-01 18:15:45 +00:00
;;
;; Command line utility "lessc" is required if setting
;; `less-css-compile-at-save' to t. To install "lessc" using the
2017-08-01 18:23:21 +00:00
;; Node.js package manager, run "npm install less".
2017-08-01 18:15:45 +00:00
;;
;; Also make sure the "lessc" executable is in Emacs' PATH, example:
2017-08-01 18:23:21 +00:00
;; (push (expand-file-name "~/.gem/ruby/1.8/bin") exec-path)
;; or customize `less-css-lessc-command' to point to your "lessc"
;; executable.
2017-08-01 18:15:45 +00:00
;;
;; We target lessc >= 1.4.0, and thus use the `--no-color' flag by
;; default. You may want to adjust `less-css-lessc-options' for
;; compatibility with older versions.
;;
;; `less-css-mode' is derived from `css-mode', and indentation of
;; nested blocks may not work correctly with versions of `css-mode'
;; other than that bundled with recent Emacs.
;;
;; You can specify per-file values for `less-css-compile-at-save',
;; `less-css-output-file-name' or `less-css-output-directory' using a
;; variables header at the top of your .less file, e.g.:
;;
;; // -*- less-css-compile-at-save: t; less-css-output-directory: "../css" -*-
;;
;; Alternatively, you can use directory local variables to set the
;; default value of `less-css-output-directory' for your project.
;;
;; In the case of files which are included in other .less files, you
;; may want to trigger the compilation of a "master" .less file on
;; save: you can accomplish this with `less-css-input-file-name',
;; which is probably best set using directory local variables.
;;
;; If you don't need CSS output but would like to be warned of any
;; syntax errors in your .less source, consider using `flymake-less':
2017-08-01 18:23:21 +00:00
;; https://github.com/purcell/flymake-less.
2017-08-01 18:15:45 +00:00
;;; Credits
2017-08-01 18:23:21 +00:00
2017-08-01 18:15:45 +00:00
;; The original code for this mode was, in large part, written using
;; Anton Johansson's scss-mode as a template -- thanks Anton!
;; https://github.com/antonj
2017-08-01 18:23:21 +00:00
2017-08-01 18:15:45 +00:00
;;; Code:
( require 'compile )
( require 'css-mode )
2017-08-01 18:23:21 +00:00
( require 'derived )
( eval-when-compile ( require 'subr-x ) )
2017-08-01 18:15:45 +00:00
( defgroup less-css nil
2017-08-01 18:23:21 +00:00
" Less CSS mode. "
2017-12-13 07:21:24 +00:00
:version " 26.1 "
2017-08-01 18:15:45 +00:00
:prefix " less-css- "
:group 'css )
( defcustom less-css-lessc-command " lessc "
2017-08-01 18:23:21 +00:00
" Command used to compile Less files.
Should be \"lessc\" or the complete path to your lessc
executable, e.g.: \"~/.gem/ruby/1.8/bin/lessc\". "
:type 'file )
2017-08-01 18:15:45 +00:00
( defcustom less-css-compile-at-save nil
2017-08-01 18:23:21 +00:00
" If non-nil, Less buffers are compiled to CSS after each save. "
:type 'boolean )
2017-08-01 18:15:45 +00:00
;;;###autoload
2017-08-01 18:23:21 +00:00
( put 'less-css-compile-at-save 'safe-local-variable 'booleanp )
2017-08-01 18:15:45 +00:00
2017-08-01 18:23:21 +00:00
( defcustom less-css-lessc-options ' ( " --no-color " )
" Command line options for Less executable.
2017-08-01 18:15:45 +00:00
Use \"-x\" to minify output. "
2017-08-01 18:23:21 +00:00
:type ' ( repeat string ) )
2017-08-01 18:15:45 +00:00
;;;###autoload
2017-08-01 18:23:21 +00:00
( put 'less-css-lessc-options 'safe-local-variable t )
2017-08-01 18:15:45 +00:00
2017-08-01 18:23:21 +00:00
( defcustom less-css-output-directory nil
" Directory in which to save CSS, or nil to use the Less file's directory.
This path is expanded relative to the directory of the Less file
2017-08-01 18:15:45 +00:00
using ` expand-file-name ', so both relative and absolute paths
will work as expected. "
2017-12-13 20:29:24 +00:00
:type ' ( choice ( const :tag " Same as Less file " nil ) directory ) )
2017-08-01 18:15:45 +00:00
;;;###autoload
2017-08-01 18:23:21 +00:00
( put 'less-css-output-directory 'safe-local-variable 'stringp )
2017-08-01 18:15:45 +00:00
( defcustom less-css-output-file-name nil
" File name in which to save CSS, or nil to use <name>.css for <name>.less.
This can be also be set to a full path, or a relative path. If
the path is relative, it will be relative to the value of
` less-css-output-dir ', if set, or the current directory by
default. "
2017-12-13 20:29:24 +00:00
:type ' ( choice ( const :tag " Default " nil ) file ) )
2017-08-01 18:15:45 +00:00
( make-variable-buffer-local 'less-css-output-file-name )
( defcustom less-css-input-file-name nil
" File name which will be compiled to CSS.
When the current buffer is saved ` less-css-input-file-name ' file
2017-08-01 18:23:21 +00:00
will be compiled to CSS instead of the current file.
2017-08-01 18:15:45 +00:00
Set this in order to trigger compilation of a \"master\" . less
file which includes the current file. The best way to set this
variable in most cases is likely to be via directory local
variables.
2017-08-01 18:23:21 +00:00
This can be also be set to a full path, or a relative path. If
2017-10-10 05:53:19 +00:00
the path is relative, it will be relative to the current
2017-08-01 18:23:21 +00:00
directory by default. "
2017-12-13 20:29:24 +00:00
:type ' ( choice ( const nil ) file ) )
2017-08-01 18:23:21 +00:00
;;;###autoload
( put 'less-css-input-file-name 'safe-local-variable 'stringp )
2017-08-01 18:15:45 +00:00
( make-variable-buffer-local 'less-css-input-file-name )
( defconst less-css-default-error-regex
" ^ \\ (?: \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 \\ )? " )
2017-08-01 18:23:21 +00:00
;;; Compilation to CSS
2017-08-01 18:15:45 +00:00
( add-to-list 'compilation-error-regexp-alist-alist
( list 'less-css less-css-default-error-regex 2 3 4 nil 1 ) )
( add-to-list 'compilation-error-regexp-alist 'less-css )
( defun less-css-compile-maybe ( )
" Run `less-css-compile' if `less-css-compile-at-save' is non-nil. "
2017-08-01 18:23:21 +00:00
( when less-css-compile-at-save
( less-css-compile ) ) )
2017-08-01 18:15:45 +00:00
( defun less-css--output-path ( )
2017-08-01 18:23:21 +00:00
" Return the path to use for the compiled CSS file. "
( expand-file-name
( or less-css-output-file-name
( concat
( file-name-nondirectory
( file-name-sans-extension buffer-file-name ) )
" .css " ) )
( or less-css-output-directory default-directory ) ) )
2017-08-01 18:15:45 +00:00
( defun less-css-compile ( )
2017-08-01 18:23:21 +00:00
" Compile the current buffer to CSS using `less-css-lessc-command' . "
2017-08-01 18:15:45 +00:00
( interactive )
2017-08-01 18:23:21 +00:00
( message " Compiling Less to CSS " )
( let ( ( compilation-buffer-name-function
( lambda ( _ ) " *less-css-compilation* " ) ) )
2017-08-01 18:15:45 +00:00
( save-window-excursion
( with-current-buffer
( compile
2017-08-01 18:23:21 +00:00
( string-join
( append
( list less-css-lessc-command )
( mapcar #' shell-quote-argument less-css-lessc-options )
( list ( shell-quote-argument
( or less-css-input-file-name buffer-file-name ) )
( shell-quote-argument ( less-css--output-path ) ) ) )
" " ) )
2017-08-01 18:15:45 +00:00
( add-hook 'compilation-finish-functions
( lambda ( buf msg )
( unless ( string-match-p " ^finished " msg )
( display-buffer buf ) ) )
nil
t ) ) ) ) )
2017-08-01 18:23:21 +00:00
;;; Major mode
2017-08-01 18:15:45 +00:00
2017-08-01 18:23:21 +00:00
;; TODO:
;; - interpolation ("@{val}")
;; - escaped values (~"...")
;; - JS eval (~`...`)
;; - custom faces.
2017-08-01 18:15:45 +00:00
( defconst less-css-font-lock-keywords
' ( ;; Variables
2019-04-02 22:00:59 +00:00
( " @[a-z_-][a-z_0-9-]* " . font-lock-variable-name-face )
2017-08-01 18:15:45 +00:00
( " & " . font-lock-preprocessor-face )
;; Mixins
2019-04-02 22:00:59 +00:00
( " \\ (?:[ \t {;] \\ |^ \\ ) \\ ( \\ .[a-z_-][a-z_0-9-]* \\ )[ \t ]*; " .
2017-08-01 18:23:21 +00:00
( 1 font-lock-keyword-face ) ) ) )
( defvar less-css-mode-syntax-table
( let ( ( st ( make-syntax-table css-mode-syntax-table ) ) )
;; C++-style comments.
( modify-syntax-entry ?/ " . 124b " st )
( modify-syntax-entry ?* " . 23 " st )
( modify-syntax-entry ?\n " > b " st )
;; Special chars that sometimes come at the beginning of words.
( modify-syntax-entry ?. " ' " st )
st ) )
( defvar less-css-mode-map
( let ( ( map ( make-sparse-keymap ) ) )
( define-key map " \C -c \C -c " 'less-css-compile )
map ) )
;;;###autoload (add-to-list 'auto-mode-alist '("\\.less\\'" . less-css-mode))
2017-08-01 18:15:45 +00:00
;;;###autoload
2017-08-01 18:23:21 +00:00
( define-derived-mode less-css-mode css-mode " Less "
" Major mode for editing Less files (http://lesscss.org/).
2017-08-01 18:15:45 +00:00
Special commands:
\\{less-css-mode-map} "
( font-lock-add-keywords nil less-css-font-lock-keywords )
2017-08-01 18:23:21 +00:00
( setq-local comment-start " // " )
( setq-local comment-end " " )
( setq-local comment-continue " * " )
( setq-local comment-start-skip " /[*/]+[ \t ]* " )
( setq-local comment-end-skip " [ \t ]* \\ (?: \n \\ | \\ *+/ \\ ) " )
2017-08-01 18:15:45 +00:00
( add-hook 'after-save-hook 'less-css-compile-maybe nil t ) )
( provide 'less-css-mode )
;;; less-css-mode.el ends here