summaryrefslogtreecommitdiff
path: root/lisp/textmodes
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/textmodes')
-rw-r--r--lisp/textmodes/css-mode.el156
-rw-r--r--lisp/textmodes/reftex-vars.el2
2 files changed, 155 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&macros"
+ "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
diff --git a/lisp/textmodes/reftex-vars.el b/lisp/textmodes/reftex-vars.el
index 63abd048e9d..03da584e96f 100644
--- a/lisp/textmodes/reftex-vars.el
+++ b/lisp/textmodes/reftex-vars.el
@@ -164,6 +164,8 @@ distribution. Mixed-case symbols are convenience aliases.")
(?U . "\\autocite*[][]{%l}")
(?a . "\\citeauthor{%l}")
(?A . "\\citeauthor*{%l}")
+ (?i . "\\citetitle{%l}")
+ (?I . "\\citetitle*{%l}")
(?y . "\\citeyear{%l}")
(?Y . "\\citeyear*{%l}")
(?n . "\\nocite{%l}")))