diff options
Diffstat (limited to 'lisp/textmodes/css-mode.el')
-rw-r--r-- | lisp/textmodes/css-mode.el | 156 |
1 files changed, 153 insertions, 3 deletions
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el index c81c3f62e16..0c7d76f7924 100644 --- a/lisp/textmodes/css-mode.el +++ b/lisp/textmodes/css-mode.el @@ -32,9 +32,11 @@ ;;; Code: +(require 'eww) (require 'seq) (require 'sgml-mode) (require 'smie) +(require 'subr-x) (defgroup css nil "Cascading Style Sheets (CSS) editing mode." @@ -621,6 +623,12 @@ cannot be completed sensibly: `custom-ident', (modify-syntax-entry ?- "_" st) st)) +(defvar css-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [remap info-lookup-symbol] 'css-lookup-symbol) + map) + "Keymap used in `css-mode'.") + (eval-and-compile (defconst css--uri-re (concat @@ -734,7 +742,30 @@ cannot be completed sensibly: `custom-ident', (defconst css-smie-grammar (smie-prec2->grammar - (smie-precs->prec2 '((assoc ";") (assoc ",") (left ":"))))) + (smie-precs->prec2 + '((assoc ";") + ;; Colons that belong to a CSS property. These get a higher + ;; precedence than other colons, such as colons in selectors, + ;; which are represented by a plain ":" token. + (left ":-property") + (assoc ",") + (assoc ":"))))) + +(defun css--colon-inside-selector-p () + "Return t if point looks to be inside a CSS selector. +This function is intended to be good enough to help SMIE during +tokenization, but should not be regarded as a reliable function +for determining whether point is within a selector." + (save-excursion + (re-search-forward "[{};)]" nil t) + (eq (char-before) ?\{))) + +(defun css--colon-inside-funcall () + "Return t if point is inside a function call." + (when-let (opening-paren-pos (nth 1 (syntax-ppss))) + (save-excursion + (goto-char opening-paren-pos) + (eq (char-after) ?\()))) (defun css-smie--forward-token () (cond @@ -748,7 +779,13 @@ cannot be completed sensibly: `custom-ident', ";") ((progn (forward-comment (point-max)) (looking-at "[;,:]")) - (forward-char 1) (match-string 0)) + (forward-char 1) + (if (equal (match-string 0) ":") + (if (or (css--colon-inside-selector-p) + (css--colon-inside-funcall)) + ":" + ":-property") + (match-string 0))) (t (smie-default-forward-token)))) (defun css-smie--backward-token () @@ -759,7 +796,13 @@ cannot be completed sensibly: `custom-ident', ((and (eq (char-before) ?\}) (scss-smie--not-interpolation-p) (> pos (point))) ";") ((memq (char-before) '(?\; ?\, ?\:)) - (forward-char -1) (string (char-after))) + (forward-char -1) + (if (eq (char-after) ?\:) + (if (or (css--colon-inside-selector-p) + (css--colon-inside-funcall)) + ":" + ":-property") + (string (char-after)))) (t (smie-default-backward-token))))) (defun css-smie-rules (kind token) @@ -1087,5 +1130,112 @@ pseudo-elements, pseudo-classes, at-rules, and bang-rules." (setq-local font-lock-defaults (list (scss-font-lock-keywords) nil t))) + + +(defvar css--mdn-lookup-history nil) + +(defcustom css-lookup-url-format + "https://developer.mozilla.org/en-US/docs/Web/CSS/%s?raw¯os" + "Format for a URL where CSS documentation can be found. +The format should include a single \"%s\" substitution. +The name of the CSS property, @-id, pseudo-class, or pseudo-element +to look up will be substituted there." + :version "26.1" + :type 'string + :group 'css) + +(defun css--mdn-after-render () + (setf header-line-format nil) + (goto-char (point-min)) + (let ((window (get-buffer-window (current-buffer) 'visible))) + (when window + (when (re-search-forward "^Summary" nil 'move) + (beginning-of-line) + (set-window-start window (point)))))) + +(defconst css--mdn-symbol-regexp + (concat "\\(" + ;; @-ids. + "\\(@" (regexp-opt css-at-ids) "\\)" + "\\|" + ;; ;; Known properties. + (regexp-opt css-property-ids t) + "\\|" + ;; Pseudo-classes. + "\\(:" (regexp-opt css-pseudo-class-ids) "\\)" + "\\|" + ;; Pseudo-elements with either one or two ":"s. + "\\(::?" (regexp-opt css-pseudo-element-ids) "\\)" + "\\)") + "Regular expression to match the CSS symbol at point.") + +(defconst css--mdn-property-regexp + (concat "\\_<" (regexp-opt css-property-ids t) "\\s-*\\(?:\\=\\|:\\)") + "Regular expression to match a CSS property.") + +(defconst css--mdn-completion-list + (nconc + ;; @-ids. + (mapcar (lambda (atrule) (concat "@" atrule)) css-at-ids) + ;; Pseudo-classes. + (mapcar (lambda (class) (concat ":" class)) css-pseudo-class-ids) + ;; Pseudo-elements with either one or two ":"s. + (mapcar (lambda (elt) (concat ":" elt)) css-pseudo-element-ids) + (mapcar (lambda (elt) (concat "::" elt)) css-pseudo-element-ids) + ;; Properties. + css-property-ids) + "List of all symbols available for lookup via MDN.") + +(defun css--mdn-find-symbol () + "A helper for `css-lookup-symbol' that finds the symbol at point. +Returns the symbol, a string, or nil if none found." + (save-excursion + ;; Skip backward over a word first. + (skip-chars-backward "-[:alnum:] \t") + ;; Now skip ":" or "@" to see if it's a pseudo-element or at-id. + (skip-chars-backward "@:") + (if (looking-at css--mdn-symbol-regexp) + (match-string-no-properties 0) + (let ((bound (save-excursion + (beginning-of-line) + (point)))) + (when (re-search-backward css--mdn-property-regexp bound t) + (match-string-no-properties 1)))))) + +;;;###autoload +(defun css-lookup-symbol (symbol) + "Display the CSS documentation for SYMBOL, as found on MDN. +When this command is used interactively, it picks a default +symbol based on the CSS text before point -- either an @-keyword, +a property name, a pseudo-class, or a pseudo-element, depending +on what is seen near point." + (interactive + (list + (let* ((sym (css--mdn-find-symbol)) + (enable-recursive-minibuffers t) + (value (completing-read + (if sym + (format "Describe CSS symbol (default %s): " sym) + "Describe CSS symbol: ") + css--mdn-completion-list nil nil nil + 'css--mdn-lookup-history sym))) + (if (equal value "") sym value)))) + (when symbol + ;; If we see a single-colon pseudo-element like ":after", turn it + ;; into "::after". + (when (and (eq (aref symbol 0) ?:) + (member (substring symbol 1) css-pseudo-element-ids)) + (setq symbol (concat ":" symbol))) + (let ((url (format css-lookup-url-format symbol)) + (buffer (get-buffer-create "*MDN CSS*"))) + (save-selected-window + ;; Make sure to display the buffer before calling `eww', as + ;; that calls `pop-to-buffer-same-window'. + (switch-to-buffer-other-window buffer) + (with-current-buffer buffer + (eww-mode) + (add-hook 'eww-after-render-hook #'css--mdn-after-render nil t) + (eww url)))))) + (provide 'css-mode) ;;; css-mode.el ends here |