diff options
| author | Tom Tromey | 2017-01-19 21:40:38 -0700 |
|---|---|---|
| committer | Tom Tromey | 2017-01-30 15:53:10 -0700 |
| commit | 68e8f4bb4aab3076f6b543864a9116d0a206c8f7 (patch) | |
| tree | 5efc784d12ec1b735fcd720bd488f8325fa5d849 | |
| parent | 77888c88503861197f5e855d18813eb1f6cb4c80 (diff) | |
| download | emacs-68e8f4bb4aab3076f6b543864a9116d0a206c8f7.tar.gz emacs-68e8f4bb4aab3076f6b543864a9116d0a206c8f7.zip | |
css-mode documentation lookup feature
* etc/NEWS: Mention new feature.
* lisp/textmodes/css-mode.el (css-mode-map): New defvar.
(css--mdn-lookup-history): New defvar.
(css-lookup-url-format): New defcustom.
(css--mdn-property-regexp, css--mdn-completion-list): New defconsts.
(css--mdn-after-render, css--mdn-find-symbol, css-lookup-symbol): New
defuns.
* test/lisp/textmodes/css-mode-tests.el (css-mdn-symbol-guessing): New
test.
| -rw-r--r-- | etc/NEWS | 7 | ||||
| -rw-r--r-- | lisp/textmodes/css-mode.el | 114 | ||||
| -rw-r--r-- | test/lisp/textmodes/css-mode-tests.el | 15 |
3 files changed, 136 insertions, 0 deletions
| @@ -613,6 +613,13 @@ HTML tags, classes and IDs using the 'completion-at-point' command. | |||
| 613 | Completion candidates for HTML classes and IDs are retrieved from open | 613 | Completion candidates for HTML classes and IDs are retrieved from open |
| 614 | HTML mode buffers. | 614 | HTML mode buffers. |
| 615 | 615 | ||
| 616 | --- | ||
| 617 | *** CSS mode now binds 'C-h s' to a function that will show | ||
| 618 | information about a CSS construct (an at-rule, property, pseudo-class, | ||
| 619 | pseudo-element, with the default being guessed from context). By | ||
| 620 | default the information is looked up on the Mozilla Developer Network, | ||
| 621 | but this can be customized using 'css-lookup-url-format'. | ||
| 622 | |||
| 616 | +++ | 623 | +++ |
| 617 | ** Emacs now supports character name escape sequences in character and | 624 | ** Emacs now supports character name escape sequences in character and |
| 618 | string literals. The syntax variants \N{character name} and | 625 | string literals. The syntax variants \N{character name} and |
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el index c81c3f62e16..19f74daec63 100644 --- a/lisp/textmodes/css-mode.el +++ b/lisp/textmodes/css-mode.el | |||
| @@ -35,6 +35,7 @@ | |||
| 35 | (require 'seq) | 35 | (require 'seq) |
| 36 | (require 'sgml-mode) | 36 | (require 'sgml-mode) |
| 37 | (require 'smie) | 37 | (require 'smie) |
| 38 | (require 'eww) | ||
| 38 | 39 | ||
| 39 | (defgroup css nil | 40 | (defgroup css nil |
| 40 | "Cascading Style Sheets (CSS) editing mode." | 41 | "Cascading Style Sheets (CSS) editing mode." |
| @@ -621,6 +622,12 @@ cannot be completed sensibly: `custom-ident', | |||
| 621 | (modify-syntax-entry ?- "_" st) | 622 | (modify-syntax-entry ?- "_" st) |
| 622 | st)) | 623 | st)) |
| 623 | 624 | ||
| 625 | (defvar css-mode-map | ||
| 626 | (let ((map (make-sparse-keymap))) | ||
| 627 | (define-key map [remap info-lookup-symbol] 'css-lookup-symbol) | ||
| 628 | map) | ||
| 629 | "Keymap used in `css-mode'.") | ||
| 630 | |||
| 624 | (eval-and-compile | 631 | (eval-and-compile |
| 625 | (defconst css--uri-re | 632 | (defconst css--uri-re |
| 626 | (concat | 633 | (concat |
| @@ -1087,5 +1094,112 @@ pseudo-elements, pseudo-classes, at-rules, and bang-rules." | |||
| 1087 | (setq-local font-lock-defaults | 1094 | (setq-local font-lock-defaults |
| 1088 | (list (scss-font-lock-keywords) nil t))) | 1095 | (list (scss-font-lock-keywords) nil t))) |
| 1089 | 1096 | ||
| 1097 | |||
| 1098 | |||
| 1099 | (defvar css--mdn-lookup-history nil) | ||
| 1100 | |||
| 1101 | (defcustom css-lookup-url-format | ||
| 1102 | "https://developer.mozilla.org/en-US/docs/Web/CSS/%s?raw¯os" | ||
| 1103 | "Format for a URL where CSS documentation can be found. | ||
| 1104 | The format should include a single \"%s\" substitution. | ||
| 1105 | The name of the CSS property, @-id, pseudo-class, or pseudo-element | ||
| 1106 | to look up will be substituted there." | ||
| 1107 | :version "26.1" | ||
| 1108 | :type 'string | ||
| 1109 | :group 'css) | ||
| 1110 | |||
| 1111 | (defun css--mdn-after-render () | ||
| 1112 | (setf header-line-format nil) | ||
| 1113 | (goto-char (point-min)) | ||
| 1114 | (let ((window (get-buffer-window (current-buffer) 'visible))) | ||
| 1115 | (when window | ||
| 1116 | (when (re-search-forward "^Summary" nil 'move) | ||
| 1117 | (beginning-of-line) | ||
| 1118 | (set-window-start window (point)))))) | ||
| 1119 | |||
| 1120 | (defconst css--mdn-symbol-regexp | ||
| 1121 | (concat "\\(" | ||
| 1122 | ;; @-ids. | ||
| 1123 | "\\(@" (regexp-opt css-at-ids) "\\)" | ||
| 1124 | "\\|" | ||
| 1125 | ;; ;; Known properties. | ||
| 1126 | (regexp-opt css-property-ids t) | ||
| 1127 | "\\|" | ||
| 1128 | ;; Pseudo-classes. | ||
| 1129 | "\\(:" (regexp-opt css-pseudo-class-ids) "\\)" | ||
| 1130 | "\\|" | ||
| 1131 | ;; Pseudo-elements with either one or two ":"s. | ||
| 1132 | "\\(::?" (regexp-opt css-pseudo-element-ids) "\\)" | ||
| 1133 | "\\)") | ||
| 1134 | "Regular expression to match the CSS symbol at point.") | ||
| 1135 | |||
| 1136 | (defconst css--mdn-property-regexp | ||
| 1137 | (concat "\\_<" (regexp-opt css-property-ids t) "\\s-*\\(?:\\=\\|:\\)") | ||
| 1138 | "Regular expression to match a CSS property.") | ||
| 1139 | |||
| 1140 | (defconst css--mdn-completion-list | ||
| 1141 | (nconc | ||
| 1142 | ;; @-ids. | ||
| 1143 | (mapcar (lambda (atrule) (concat "@" atrule)) css-at-ids) | ||
| 1144 | ;; Pseudo-classes. | ||
| 1145 | (mapcar (lambda (class) (concat ":" class)) css-pseudo-class-ids) | ||
| 1146 | ;; Pseudo-elements with either one or two ":"s. | ||
| 1147 | (mapcar (lambda (elt) (concat ":" elt)) css-pseudo-element-ids) | ||
| 1148 | (mapcar (lambda (elt) (concat "::" elt)) css-pseudo-element-ids) | ||
| 1149 | ;; Properties. | ||
| 1150 | css-property-ids) | ||
| 1151 | "List of all symbols available for lookup via MDN.") | ||
| 1152 | |||
| 1153 | (defun css--mdn-find-symbol () | ||
| 1154 | "A helper for `css-lookup-symbol' that finds the symbol at point. | ||
| 1155 | Returns the symbol, a string, or nil if none found." | ||
| 1156 | (save-excursion | ||
| 1157 | ;; Skip backward over a word first. | ||
| 1158 | (skip-chars-backward "-[:alnum:] \t") | ||
| 1159 | ;; Now skip ":" or "@" to see if it's a pseudo-element or at-id. | ||
| 1160 | (skip-chars-backward "@:") | ||
| 1161 | (if (looking-at css--mdn-symbol-regexp) | ||
| 1162 | (match-string-no-properties 0) | ||
| 1163 | (let ((bound (save-excursion | ||
| 1164 | (beginning-of-line) | ||
| 1165 | (point)))) | ||
| 1166 | (when (re-search-backward css--mdn-property-regexp bound t) | ||
| 1167 | (match-string-no-properties 1)))))) | ||
| 1168 | |||
| 1169 | ;;;###autoload | ||
| 1170 | (defun css-lookup-symbol (symbol) | ||
| 1171 | "Display the CSS documentation for SYMBOL, as found on MDN. | ||
| 1172 | When this command is used interactively, it picks a default | ||
| 1173 | symbol based on the CSS text before point -- either an @-keyword, | ||
| 1174 | a property name, a pseudo-class, or a pseudo-element, depending | ||
| 1175 | on what is seen near point." | ||
| 1176 | (interactive | ||
| 1177 | (list | ||
| 1178 | (let* ((sym (css--mdn-find-symbol)) | ||
| 1179 | (enable-recursive-minibuffers t) | ||
| 1180 | (value (completing-read | ||
| 1181 | (if sym | ||
| 1182 | (format "Describe CSS symbol (default %s): " sym) | ||
| 1183 | "Describe CSS symbol: ") | ||
| 1184 | css--mdn-completion-list nil nil nil | ||
| 1185 | 'css--mdn-lookup-history sym))) | ||
| 1186 | (if (equal value "") sym value)))) | ||
| 1187 | (when symbol | ||
| 1188 | ;; If we see a single-colon pseudo-element like ":after", turn it | ||
| 1189 | ;; into "::after". | ||
| 1190 | (when (and (eq (aref symbol 0) ?:) | ||
| 1191 | (member (substring symbol 1) css-pseudo-element-ids)) | ||
| 1192 | (setq symbol (concat ":" symbol))) | ||
| 1193 | (let ((url (format css-lookup-url-format symbol)) | ||
| 1194 | (buffer (get-buffer-create "*MDN CSS*"))) | ||
| 1195 | (save-selected-window | ||
| 1196 | ;; Make sure to display the buffer before calling `eww', as | ||
| 1197 | ;; that calls `pop-to-buffer-same-window'. | ||
| 1198 | (switch-to-buffer-other-window buffer) | ||
| 1199 | (with-current-buffer buffer | ||
| 1200 | (eww-mode) | ||
| 1201 | (add-hook 'eww-after-render-hook #'css--mdn-after-render nil t) | ||
| 1202 | (eww url)))))) | ||
| 1203 | |||
| 1090 | (provide 'css-mode) | 1204 | (provide 'css-mode) |
| 1091 | ;;; css-mode.el ends here | 1205 | ;;; css-mode.el ends here |
diff --git a/test/lisp/textmodes/css-mode-tests.el b/test/lisp/textmodes/css-mode-tests.el index 6eb32ea7fc4..5372c37a179 100644 --- a/test/lisp/textmodes/css-mode-tests.el +++ b/test/lisp/textmodes/css-mode-tests.el | |||
| @@ -218,5 +218,20 @@ | |||
| 218 | (should (member "body" completions)) | 218 | (should (member "body" completions)) |
| 219 | (should-not (member "article" completions))))) | 219 | (should-not (member "article" completions))))) |
| 220 | 220 | ||
| 221 | (ert-deftest css-mdn-symbol-guessing () | ||
| 222 | (dolist (item '(("@med" "ia" "@media") | ||
| 223 | ("@keyframes " "{" "@keyframes") | ||
| 224 | ("p::after" "" "::after") | ||
| 225 | ("p:before" "" ":before") | ||
| 226 | ("a:v" "isited" ":visited") | ||
| 227 | ("border-" "color: red" "border-color") | ||
| 228 | ("border-color: red" ";" "border-color") | ||
| 229 | ("border-color: red; color: green" ";" "color"))) | ||
| 230 | (with-temp-buffer | ||
| 231 | (css-mode) | ||
| 232 | (insert (nth 0 item)) | ||
| 233 | (save-excursion (insert (nth 1 item))) | ||
| 234 | (should (equal (nth 2 item) (css--mdn-find-symbol)))))) | ||
| 235 | |||
| 221 | (provide 'css-mode-tests) | 236 | (provide 'css-mode-tests) |
| 222 | ;;; css-mode-tests.el ends here | 237 | ;;; css-mode-tests.el ends here |