diff options
| author | Anders Lindgren | 2017-10-26 21:31:13 +0200 |
|---|---|---|
| committer | Anders Lindgren | 2017-10-26 21:31:28 +0200 |
| commit | a0e5a02125a62d3c4f09abea3a0085111ddffa77 (patch) | |
| tree | 10b95465cb986c63eafe3ce7ee86a4dded42bc39 | |
| parent | bc9300ac5ed3bdf52a2f8b9e217236e1ee76cd02 (diff) | |
| download | emacs-a0e5a02125a62d3c4f09abea3a0085111ddffa77.tar.gz emacs-a0e5a02125a62d3c4f09abea3a0085111ddffa77.zip | |
New package, `faceup'
`faceup' is a framework for regression testing of font-lock
keywords in ert. It is based on a human-readable markup
language. (Bug#16063 and bug#28311).
* lisp/emacs-lisp/faceup.el:
* test/lisp/emacs-lisp/faceup-tests/faceup-test-basics.el:
* test/lisp/emacs-lisp/faceup-tests/faceup-test-files.el:
* test/lisp/emacs-lisp/faceup-resources/faceup-test-mode.el:
* test/lisp/emacs-lisp/faceup-resources/faceup-test-this-file-directory.el:
* test/lisp/emacs-lisp/faceup-resources/files/test1.txt:
* test/lisp/emacs-lisp/faceup-resources/files/test1.txt.faceup:
New files.
7 files changed, 1671 insertions, 0 deletions
diff --git a/lisp/emacs-lisp/faceup.el b/lisp/emacs-lisp/faceup.el new file mode 100644 index 00000000000..3a0f7e5c7a5 --- /dev/null +++ b/lisp/emacs-lisp/faceup.el | |||
| @@ -0,0 +1,1183 @@ | |||
| 1 | ;;; faceup.el --- Markup language for faces and font-lock regression testing -*- lexical-binding: t -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2013-2017 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Anders Lindgren | ||
| 6 | ;; Version: 0.0.6 | ||
| 7 | ;; Created: 2013-01-21 | ||
| 8 | ;; Keywords: faces languages | ||
| 9 | ;; URL: https://github.com/Lindydancer/faceup | ||
| 10 | |||
| 11 | ;; This file is part of GNU Emacs. | ||
| 12 | |||
| 13 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 14 | ;; it under the terms of the GNU General Public License as published by | ||
| 15 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 16 | ;; (at your option) any later version. | ||
| 17 | |||
| 18 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 21 | ;; GNU General Public License for more details. | ||
| 22 | |||
| 23 | ;; You should have received a copy of the GNU General Public License | ||
| 24 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 25 | |||
| 26 | ;;; Commentary: | ||
| 27 | |||
| 28 | ;; Emacs is capable of highlighting buffers based on language-specific | ||
| 29 | ;; `font-lock' rules. This package makes it possible to perform | ||
| 30 | ;; regression test for packages that provide font-lock rules. | ||
| 31 | ;; | ||
| 32 | ;; The underlying idea is to convert text with highlights ("faces") | ||
| 33 | ;; into a plain text representation using the Faceup markup | ||
| 34 | ;; language. This language is semi-human readable, for example: | ||
| 35 | ;; | ||
| 36 | ;; «k:this» is a keyword | ||
| 37 | ;; | ||
| 38 | ;; By comparing the current highlight with a highlight performed with | ||
| 39 | ;; stable versions of a package, it's possible to automatically find | ||
| 40 | ;; problems that otherwise would have been hard to spot. | ||
| 41 | ;; | ||
| 42 | ;; This package is designed to be used in conjunction with Ert, the | ||
| 43 | ;; standard Emacs regression test system. | ||
| 44 | ;; | ||
| 45 | ;; The Faceup markup language is a generic markup language, regression | ||
| 46 | ;; testing is merely one way to use it. | ||
| 47 | |||
| 48 | ;; Regression test examples: | ||
| 49 | ;; | ||
| 50 | ;; This section describes the two typical ways regression testing with | ||
| 51 | ;; this package is performed. | ||
| 52 | ;; | ||
| 53 | ;; | ||
| 54 | ;; Full source file highlighting: | ||
| 55 | ;; | ||
| 56 | ;; The most straight-forward way to perform regression testing is to | ||
| 57 | ;; collect a number of representative source files. From each source | ||
| 58 | ;; file, say `alpha.mylang', you can use `M-x faceup-write-file RET' | ||
| 59 | ;; to generate a Faceup file named `alpha.mylang.faceup', this file | ||
| 60 | ;; use the Faceup markup language to represent the text with | ||
| 61 | ;; highlights and is used as a reference in future tests. | ||
| 62 | ;; | ||
| 63 | ;; An Ert test case can be defined as follows: | ||
| 64 | ;; | ||
| 65 | ;; (require 'faceup) | ||
| 66 | ;; | ||
| 67 | ;; (defvar mylang-font-lock-test-dir (faceup-this-file-directory)) | ||
| 68 | ;; | ||
| 69 | ;; (defun mylang-font-lock-test-apps (file) | ||
| 70 | ;; "Test that the mylang FILE is fontifies as the .faceup file describes." | ||
| 71 | ;; (faceup-test-font-lock-file 'mylang-mode | ||
| 72 | ;; (concat mylang-font-lock-test-dir file))) | ||
| 73 | ;; (faceup-defexplainer mylang-font-lock-test-apps) | ||
| 74 | ;; | ||
| 75 | ;; (ert-deftest mylang-font-lock-file-test () | ||
| 76 | ;; (should (mylang-font-lock-test-apps "apps/FirstApp/alpha.mylang")) | ||
| 77 | ;; ;; ... Add more test files here ... | ||
| 78 | ;; ) | ||
| 79 | ;; | ||
| 80 | ;; To execute the tests, run something like `M-x ert RET t RET'. | ||
| 81 | ;; | ||
| 82 | ;; | ||
| 83 | ;; Source snippets: | ||
| 84 | ;; | ||
| 85 | ;; To test smaller snippets of code, you can use the | ||
| 86 | ;; `faceup-test-font-lock-string'. It takes a major mode and a string | ||
| 87 | ;; written using the Faceup markup language. The functions strips away | ||
| 88 | ;; the Faceup markup, inserts the plain text into a temporary buffer, | ||
| 89 | ;; highlights it, converts the result back into the Faceup markup | ||
| 90 | ;; language, and finally compares the result with the original Faceup | ||
| 91 | ;; string. | ||
| 92 | ;; | ||
| 93 | ;; For example: | ||
| 94 | ;; | ||
| 95 | ;; (defun mylang-font-lock-test (faceup) | ||
| 96 | ;; (faceup-test-font-lock-string 'mylang-mode faceup)) | ||
| 97 | ;; (faceup-defexplainer mylang-font-lock-test) | ||
| 98 | ;; | ||
| 99 | ;; (ert-deftest mylang-font-lock-test-simple () | ||
| 100 | ;; "Simple MyLang font-lock tests." | ||
| 101 | ;; (should (mylang-font-lock-test "«k:this» is a keyword")) | ||
| 102 | ;; (should (mylang-font-lock-test "«k:function» «f:myfunc» («v:var»)"))) | ||
| 103 | ;; | ||
| 104 | |||
| 105 | ;; Executing the tests: | ||
| 106 | ;; | ||
| 107 | ;; Once the tests have been defined, you can use `M-x ert RET t RET' | ||
| 108 | ;; to execute them. Hopefully, you will be given the "all clear". | ||
| 109 | ;; However, if there is a problem, you will be presented with | ||
| 110 | ;; something like: | ||
| 111 | ;; | ||
| 112 | ;; F mylang-font-lock-file-test | ||
| 113 | ;; (ert-test-failed | ||
| 114 | ;; ((should | ||
| 115 | ;; (mylang-font-lock-test-apps "apps/FirstApp/alpha.mylang")) | ||
| 116 | ;; :form | ||
| 117 | ;; (mylang-font-lock-test-apps "apps/FirstApp/alpha.mylang") | ||
| 118 | ;; :value nil :explanation | ||
| 119 | ;; ((on-line 2 | ||
| 120 | ;; ("but_«k:this»_is_not_a_keyword") | ||
| 121 | ;; ("but_this_is_not_a_keyword"))))) | ||
| 122 | ;; | ||
| 123 | ;; You should read this that on line 2, the old font-lock rules | ||
| 124 | ;; highlighted `this' inside `but_this_is_not_a_keyword' (which is | ||
| 125 | ;; clearly wrong), whereas the new doesn't. Of course, if this is the | ||
| 126 | ;; desired result (for example, the result of a recent change) you can | ||
| 127 | ;; simply regenerate the .faceup file and store it as the reference | ||
| 128 | ;; file for the future. | ||
| 129 | |||
| 130 | ;; The Faceup markup language: | ||
| 131 | ;; | ||
| 132 | ;; The Faceup markup language is designed to be human-readable and | ||
| 133 | ;; minimalistic. | ||
| 134 | ;; | ||
| 135 | ;; The two special characters `«' and `»' marks the start and end of a | ||
| 136 | ;; range of a face. | ||
| 137 | ;; | ||
| 138 | ;; | ||
| 139 | ;; Compact format for special faces: | ||
| 140 | ;; | ||
| 141 | ;; The compact format `«<LETTER>:text»' is used for a number of common | ||
| 142 | ;; faces. For example, `«U:abc»' means that the text `abc' is | ||
| 143 | ;; underlined. | ||
| 144 | ;; | ||
| 145 | ;; See `faceup-face-short-alist' for the known faces and the | ||
| 146 | ;; corresponding letter. | ||
| 147 | ;; | ||
| 148 | ;; | ||
| 149 | ;; Full format: | ||
| 150 | ;; | ||
| 151 | ;; The format `«:<NAME OF FACE>:text»' is used use to encode other | ||
| 152 | ;; faces. | ||
| 153 | ;; | ||
| 154 | ;; For example `«:my-special-face:abc»' meanst that `abc' has the face | ||
| 155 | ;; `my-special-face'. | ||
| 156 | ;; | ||
| 157 | ;; | ||
| 158 | ;; Anonymous faces: | ||
| 159 | ;; | ||
| 160 | ;; An "anonymous face" is when the `face' property contains a property | ||
| 161 | ;; list (plist) on the form `(:key value)'. This is represented using | ||
| 162 | ;; a variant of the full format: `«:(:key value):text»'. | ||
| 163 | ;; | ||
| 164 | ;; For example, `«:(:background "red"):abc»' represent the text `abc' | ||
| 165 | ;; with a red background. | ||
| 166 | ;; | ||
| 167 | ;; | ||
| 168 | ;; Multiple properties: | ||
| 169 | ;; | ||
| 170 | ;; In case a text contains more than one face property, they are | ||
| 171 | ;; represented using nested sections. | ||
| 172 | ;; | ||
| 173 | ;; For example: | ||
| 174 | ;; | ||
| 175 | ;; * `«B:abc«U:def»»' represent the text `abcdef' that is both *bold* | ||
| 176 | ;; and *underlined*. | ||
| 177 | ;; | ||
| 178 | ;; * `«W:abc«U:def»ghi»' represent the text `abcdefghi' where the | ||
| 179 | ;; entire text is in *warning* face and `def' is *underlined*. | ||
| 180 | ;; | ||
| 181 | ;; In case two faces partially overlap, the ranges will be split when | ||
| 182 | ;; represented in Faceup. For example: | ||
| 183 | ;; | ||
| 184 | ;; * `«B:abc«U:def»»«U:ghi»' represent the text `abcdefghi' where | ||
| 185 | ;; `abcdef' is bold and `defghi' is underlined. | ||
| 186 | ;; | ||
| 187 | ;; | ||
| 188 | ;; Escaping start and end markers: | ||
| 189 | ;; | ||
| 190 | ;; Any occurrence of the start or end markers in the original text | ||
| 191 | ;; will be escaped using the start marker in the Faceup | ||
| 192 | ;; representation. In other words, the sequences `««' and `«»' | ||
| 193 | ;; represent a start and end marker, respectively. | ||
| 194 | ;; | ||
| 195 | ;; | ||
| 196 | ;; Other properties: | ||
| 197 | ;; | ||
| 198 | ;; In addition to representing the `face' property (or, more | ||
| 199 | ;; correctly, the value of `faceup-default-property') other properties | ||
| 200 | ;; can be encoded. The variable `faceup-properties' contains a list of | ||
| 201 | ;; properties to track. If a property behaves like the `face' | ||
| 202 | ;; property, it is encoded as described above, with the addition of | ||
| 203 | ;; the property name placed in parentheses, for example: | ||
| 204 | ;; `«(my-face)U:abd»'. | ||
| 205 | ;; | ||
| 206 | ;; The variable `faceup-face-like-properties' contains a list of | ||
| 207 | ;; properties considered face-like. | ||
| 208 | ;; | ||
| 209 | ;; Properties that are not considered face-like are always encoded | ||
| 210 | ;; using the full format and the don't nest. For example: | ||
| 211 | ;; `«(my-fibonacci-property):(1 1 2 3 5 8):abd»'. | ||
| 212 | ;; | ||
| 213 | ;; Examples of properties that could be tracked are: | ||
| 214 | ;; | ||
| 215 | ;; * `font-lock-face' -- an alias to `face' when `font-lock-mode' is | ||
| 216 | ;; enabled. | ||
| 217 | ;; | ||
| 218 | ;; * `syntax-table' -- used by a custom `syntax-propertize' to | ||
| 219 | ;; override the default syntax table. | ||
| 220 | ;; | ||
| 221 | ;; * `help-echo' -- provides tooltip text displayed when the mouse is | ||
| 222 | ;; held over a text. | ||
| 223 | |||
| 224 | ;; Reference section: | ||
| 225 | ;; | ||
| 226 | ;; Faceup commands and functions: | ||
| 227 | ;; | ||
| 228 | ;; `M-x faceup-write-file RET' - generate a Faceup file based on the | ||
| 229 | ;; current buffer. | ||
| 230 | ;; | ||
| 231 | ;; `M-x faceup-view-file RET' - view the current buffer converted to | ||
| 232 | ;; Faceup. | ||
| 233 | ;; | ||
| 234 | ;; `faceup-markup-{string,buffer}' - convert text with properties to | ||
| 235 | ;; the Faceup markup language. | ||
| 236 | ;; | ||
| 237 | ;; `faceup-render-view-buffer' - convert buffer with Faceup markup to | ||
| 238 | ;; a buffer with real text properties and display it. | ||
| 239 | ;; | ||
| 240 | ;; `faceup-render-string' - return string with real text properties | ||
| 241 | ;; from a string with Faceup markup. | ||
| 242 | ;; | ||
| 243 | ;; `faceup-render-to-{buffer,string}' - convert buffer with Faceup | ||
| 244 | ;; markup to a buffer/string with real text properties. | ||
| 245 | ;; | ||
| 246 | ;; `faceup-clean-{buffer,string}' - remove Faceup markup from buffer | ||
| 247 | ;; or string. | ||
| 248 | ;; | ||
| 249 | ;; | ||
| 250 | ;; Regression test support: | ||
| 251 | ;; | ||
| 252 | ;; The following functions can be used as Ert test functions, or can | ||
| 253 | ;; be used to implement new Ert test functions. | ||
| 254 | ;; | ||
| 255 | ;; `faceup-test-equal' - Test function, work like Ert:s `equal', but | ||
| 256 | ;; more ergonomically when reporting multi-line string errors. | ||
| 257 | ;; Concretely, it breaks down multi-line strings into lines and | ||
| 258 | ;; reports which line number the error occurred on and the content of | ||
| 259 | ;; that line. | ||
| 260 | ;; | ||
| 261 | ;; `faceup-test-font-lock-buffer' - Test that a buffer is highlighted | ||
| 262 | ;; according to a reference Faceup text, for a specific major mode. | ||
| 263 | ;; | ||
| 264 | ;; `faceup-test-font-lock-string' - Test that a text with Faceup | ||
| 265 | ;; markup is refontified to match the original Faceup markup. | ||
| 266 | ;; | ||
| 267 | ;; `faceup-test-font-lock-file' - Test that a file is highlighted | ||
| 268 | ;; according to a reference .faceup file. | ||
| 269 | ;; | ||
| 270 | ;; `faceup-defexplainer' - Macro, define an explainer function and set | ||
| 271 | ;; the `ert-explainer' property on the original function, for | ||
| 272 | ;; functions based on the above test functions. | ||
| 273 | ;; | ||
| 274 | ;; `faceup-this-file-directory' - Macro, the directory of the current | ||
| 275 | ;; file. | ||
| 276 | |||
| 277 | ;; Real-world examples: | ||
| 278 | ;; | ||
| 279 | ;; The following are examples of real-world package that use faceup to | ||
| 280 | ;; test their font-lock keywords. | ||
| 281 | ;; | ||
| 282 | ;; * [cmake-font-lock](https://github.com/Lindydancer/cmake-font-lock) | ||
| 283 | ;; an advanced set of font-lock keywords for the CMake language | ||
| 284 | ;; | ||
| 285 | ;; * [objc-font-lock](https://github.com/Lindydancer/objc-font-lock) | ||
| 286 | ;; highlight Objective-C function calls. | ||
| 287 | ;; | ||
| 288 | |||
| 289 | ;; Other Font Lock Tools: | ||
| 290 | ;; | ||
| 291 | ;; This package is part of a suite of font-lock tools. The other | ||
| 292 | ;; tools in the suite are: | ||
| 293 | ;; | ||
| 294 | ;; | ||
| 295 | ;; Font Lock Studio: | ||
| 296 | ;; | ||
| 297 | ;; Interactive debugger for font-lock keywords (Emacs syntax | ||
| 298 | ;; highlighting rules). | ||
| 299 | ;; | ||
| 300 | ;; Font Lock Studio lets you *single-step* Font Lock keywords -- | ||
| 301 | ;; matchers, highlights, and anchored rules, so that you can see what | ||
| 302 | ;; happens when a buffer is fontified. You can set *breakpoints* on | ||
| 303 | ;; or inside rules and *run* until one has been hit. When inside a | ||
| 304 | ;; rule, matches are *visualized* using a palette of background | ||
| 305 | ;; colors. The *explainer* can describe a rule in plain-text English. | ||
| 306 | ;; Tight integration with *Edebug* allows you to step into Lisp | ||
| 307 | ;; expressions that are part of the Font Lock keywords. | ||
| 308 | ;; | ||
| 309 | ;; | ||
| 310 | ;; Font Lock Profiler: | ||
| 311 | ;; | ||
| 312 | ;; A profiler for font-lock keywords. This package measures time and | ||
| 313 | ;; counts the number of times each part of a font-lock keyword is | ||
| 314 | ;; used. For matchers, it counts the total number and the number of | ||
| 315 | ;; successful matches. | ||
| 316 | ;; | ||
| 317 | ;; The result is presented in table that can be sorted by count or | ||
| 318 | ;; time. The table can be expanded to include each part of the | ||
| 319 | ;; font-lock keyword. | ||
| 320 | ;; | ||
| 321 | ;; In addition, this package can generate a log of all font-lock | ||
| 322 | ;; events. This can be used to verify font-lock implementations, | ||
| 323 | ;; concretely, this is used for back-to-back tests of the real | ||
| 324 | ;; font-lock engine and Font Lock Studio, an interactive debugger for | ||
| 325 | ;; font-lock keywords. | ||
| 326 | ;; | ||
| 327 | ;; | ||
| 328 | ;; Highlight Refontification: | ||
| 329 | ;; | ||
| 330 | ;; Minor mode that visualizes how font-lock refontifies a buffer. | ||
| 331 | ;; This is useful when developing or debugging font-lock keywords, | ||
| 332 | ;; especially for keywords that span multiple lines. | ||
| 333 | ;; | ||
| 334 | ;; The background of the buffer is painted in a rainbow of colors, | ||
| 335 | ;; where each band in the rainbow represent a region of the buffer | ||
| 336 | ;; that has been refontified. When the buffer is modified, the | ||
| 337 | ;; rainbow is updated. | ||
| 338 | ;; | ||
| 339 | ;; | ||
| 340 | ;; Face Explorer: | ||
| 341 | ;; | ||
| 342 | ;; Library and tools for faces and text properties. | ||
| 343 | ;; | ||
| 344 | ;; This library is useful for packages that convert syntax highlighted | ||
| 345 | ;; buffers to other formats. The functions can be used to determine | ||
| 346 | ;; how a face or a face text property looks, in terms of primitive | ||
| 347 | ;; face attributes (e.g. foreground and background colors). Two sets | ||
| 348 | ;; of functions are provided, one for existing frames and one for | ||
| 349 | ;; fictitious displays, like 8 color tty. | ||
| 350 | ;; | ||
| 351 | ;; In addition, the following tools are provided: | ||
| 352 | ;; | ||
| 353 | ;; - `face-explorer-list-faces' -- list all available faces. Like | ||
| 354 | ;; `list-faces-display' but with information on how a face is | ||
| 355 | ;; defined. In addition, a sample for the selected frame and for a | ||
| 356 | ;; fictitious display is shown. | ||
| 357 | ;; | ||
| 358 | ;; - `face-explorer-describe-face' -- Print detailed information on | ||
| 359 | ;; how a face is defined, and list all underlying definitions. | ||
| 360 | ;; | ||
| 361 | ;; - `face-explorer-describe-face-prop' -- Describe the `face' text | ||
| 362 | ;; property at the point in terms of primitive face attributes. | ||
| 363 | ;; Also show how it would look on a fictitious display. | ||
| 364 | ;; | ||
| 365 | ;; - `face-explorer-list-display-features' -- Show which features a | ||
| 366 | ;; display supports. Most graphical displays support all, or most, | ||
| 367 | ;; features. However, many tty:s don't support, for example, | ||
| 368 | ;; strike-through. Using specially constructed faces, the resulting | ||
| 369 | ;; buffer will render differently in different displays, e.g. a | ||
| 370 | ;; graphical frame and a tty connected using `emacsclient -nw'. | ||
| 371 | ;; | ||
| 372 | ;; - `face-explorer-list-face-prop-examples' -- Show a buffer with an | ||
| 373 | ;; assortment of `face' text properties. A sample text is shown in | ||
| 374 | ;; four variants: Native, a manually maintained reference vector, | ||
| 375 | ;; the result of `face-explorer-face-prop-attributes' and | ||
| 376 | ;; `face-explorer-face-prop-attributes-for-fictitious-display'. Any | ||
| 377 | ;; package that convert a buffer to another format (like HTML, ANSI, | ||
| 378 | ;; or LaTeX) could use this buffer to ensure that everything work as | ||
| 379 | ;; intended. | ||
| 380 | ;; | ||
| 381 | ;; - `face-explorer-list-overlay-examples' -- Show a buffer with a | ||
| 382 | ;; number of examples of overlays, some are mixed with `face' text | ||
| 383 | ;; properties. Any package that convert a buffer to another format | ||
| 384 | ;; (like HTML, ANSI, or LaTeX) could use this buffer to ensure that | ||
| 385 | ;; everything work as intended. | ||
| 386 | ;; | ||
| 387 | ;; - `face-explorer-tooltip-mode' -- Minor mode that shows tooltips | ||
| 388 | ;; containing text properties and overlays at the mouse pointer. | ||
| 389 | ;; | ||
| 390 | ;; - `face-explorer-simulate-display-mode' -- Minor mode for make a | ||
| 391 | ;; buffer look like it would on a fictitious display. Using this | ||
| 392 | ;; you can, for example, see how a theme would look in using dark or | ||
| 393 | ;; light background, a 8 color tty, or on a grayscale graphical | ||
| 394 | ;; monitor. | ||
| 395 | ;; | ||
| 396 | ;; | ||
| 397 | ;; Font Lock Regression Suite: | ||
| 398 | ;; | ||
| 399 | ;; A collection of example source files for a large number of | ||
| 400 | ;; programming languages, with ERT tests to ensure that syntax | ||
| 401 | ;; highlighting does not accidentally change. | ||
| 402 | ;; | ||
| 403 | ;; For each source file, font-lock reference files are provided for | ||
| 404 | ;; various Emacs versions. The reference files contains a plain-text | ||
| 405 | ;; representation of source file with syntax highlighting, using the | ||
| 406 | ;; format "faceup". | ||
| 407 | ;; | ||
| 408 | ;; Of course, the collection source file can be used for other kinds | ||
| 409 | ;; of testing, not limited to font-lock regression testing. | ||
| 410 | |||
| 411 | ;;; Code: | ||
| 412 | |||
| 413 | (eval-when-compile | ||
| 414 | (require 'cl)) | ||
| 415 | |||
| 416 | |||
| 417 | (defvar faceup-default-property 'face | ||
| 418 | "The property that should be represented in Faceup without the (prop) part.") | ||
| 419 | |||
| 420 | (defvar faceup-properties '(face) | ||
| 421 | "List of properties that should be converted to the Faceup format. | ||
| 422 | |||
| 423 | Only face-like property use the short format. All other use the | ||
| 424 | non-nesting full format. (See `faceup-face-like-properties'.)" ) | ||
| 425 | |||
| 426 | |||
| 427 | (defvar faceup-face-like-properties '(face font-lock-face) | ||
| 428 | "List of properties that behave like `face'. | ||
| 429 | |||
| 430 | The following properties are assumed about face-like properties: | ||
| 431 | |||
| 432 | * Elements are either symbols or property lists, or lists thereof. | ||
| 433 | |||
| 434 | * A plain element and a list containing the same element are | ||
| 435 | treated as equal | ||
| 436 | |||
| 437 | * Property lists and sequences of property lists are considered | ||
| 438 | equal. For example: | ||
| 439 | |||
| 440 | ((:underline t :foreground \"red\")) | ||
| 441 | |||
| 442 | and | ||
| 443 | |||
| 444 | ((:underline t) (:foreground \"red\")) | ||
| 445 | |||
| 446 | Face-like properties are converted to faceup in a nesting fashion. | ||
| 447 | |||
| 448 | For example, the string AAAXXXAAA (where the property `prop' has | ||
| 449 | the value `(a)' on the A:s and `(a b)' on the X:s) is converted | ||
| 450 | as follows, when treated as a face-like property: | ||
| 451 | |||
| 452 | «(prop):a:AAA«(prop):b:XXX»AAAA» | ||
| 453 | |||
| 454 | When treated as a non-face-like property: | ||
| 455 | |||
| 456 | «(prop):(a):AAA»«(prop):(a b):XXX»«(prop):(a):AAA»") | ||
| 457 | |||
| 458 | |||
| 459 | (defvar faceup-markup-start-char 171) ;; « | ||
| 460 | (defvar faceup-markup-end-char 187) ;; » | ||
| 461 | |||
| 462 | (defvar faceup-face-short-alist | ||
| 463 | '(;; Generic faces (uppercase letters) | ||
| 464 | (bold . "B") | ||
| 465 | (bold-italic . "Q") | ||
| 466 | (default . "D") | ||
| 467 | (error . "E") | ||
| 468 | (highlight . "H") | ||
| 469 | (italic . "I") | ||
| 470 | (underline . "U") | ||
| 471 | (warning . "W") | ||
| 472 | ;; font-lock-specific faces (lowercase letters) | ||
| 473 | (font-lock-builtin-face . "b") | ||
| 474 | (font-lock-comment-delimiter-face . "m") | ||
| 475 | (font-lock-comment-face . "x") | ||
| 476 | (font-lock-constant-face . "c") | ||
| 477 | (font-lock-doc-face . "d") | ||
| 478 | (font-lock-function-name-face . "f") | ||
| 479 | (font-lock-keyword-face . "k") | ||
| 480 | (font-lock-negation-char-face . "n") | ||
| 481 | (font-lock-preprocessor-face . "p") | ||
| 482 | (font-lock-regexp-grouping-backslash . "h") | ||
| 483 | (font-lock-regexp-grouping-construct . "o") | ||
| 484 | (font-lock-string-face . "s") | ||
| 485 | (font-lock-type-face . "t") | ||
| 486 | (font-lock-variable-name-face . "v") | ||
| 487 | (font-lock-warning-face . "w")) | ||
| 488 | "Alist from faces to one-character representation.") | ||
| 489 | |||
| 490 | |||
| 491 | ;; Plain: «W....» | ||
| 492 | ;; Nested: «W...«W...»» | ||
| 493 | |||
| 494 | ;; Overlapping: xxxxxxxxxx | ||
| 495 | ;; yyyyyyyyyyyy | ||
| 496 | ;; «X..«Y..»»«Y...» | ||
| 497 | |||
| 498 | |||
| 499 | (defun faceup-markup-string (s) | ||
| 500 | "Return the faceup version of the string S." | ||
| 501 | (with-temp-buffer | ||
| 502 | (insert s) | ||
| 503 | (faceup-markup-buffer))) | ||
| 504 | |||
| 505 | |||
| 506 | ;;;###autoload | ||
| 507 | (defun faceup-view-buffer () | ||
| 508 | "Display the faceup representation of the current buffer." | ||
| 509 | (interactive) | ||
| 510 | (let ((buffer (get-buffer-create "*FaceUp*"))) | ||
| 511 | (with-current-buffer buffer | ||
| 512 | (delete-region (point-min) (point-max))) | ||
| 513 | (faceup-markup-to-buffer buffer) | ||
| 514 | (display-buffer buffer))) | ||
| 515 | |||
| 516 | |||
| 517 | ;;;###autoload | ||
| 518 | (defun faceup-write-file (&optional file-name confirm) | ||
| 519 | "Save the faceup representation of the current buffer to the file FILE-NAME. | ||
| 520 | |||
| 521 | Unless a name is given, the file will be named xxx.faceup, where | ||
| 522 | xxx is the file name associated with the buffer. | ||
| 523 | |||
| 524 | If optional second arg CONFIRM is non-nil, this function | ||
| 525 | asks for confirmation before overwriting an existing file. | ||
| 526 | Interactively, confirmation is required unless you supply a prefix argument." | ||
| 527 | (interactive | ||
| 528 | (let ((suggested-name (and (buffer-file-name) | ||
| 529 | (concat (buffer-file-name) | ||
| 530 | ".faceup")))) | ||
| 531 | (list (read-file-name "Write faceup file: " | ||
| 532 | default-directory | ||
| 533 | suggested-name | ||
| 534 | nil | ||
| 535 | (file-name-nondirectory suggested-name)) | ||
| 536 | (not current-prefix-arg)))) | ||
| 537 | (unless file-name | ||
| 538 | (setq file-name (concat (buffer-file-name) ".faceup"))) | ||
| 539 | (let ((buffer (current-buffer))) | ||
| 540 | (with-temp-buffer | ||
| 541 | (faceup-markup-to-buffer (current-buffer) buffer) | ||
| 542 | ;; Note: Must set `require-final-newline' inside | ||
| 543 | ;; `with-temp-buffer', otherwise the value will be overridden by | ||
| 544 | ;; the buffers local value. | ||
| 545 | ;; | ||
| 546 | ;; Clear `window-size-change-functions' as a workaround for | ||
| 547 | ;; Emacs bug#19576 (`write-file' saves the wrong buffer if a | ||
| 548 | ;; function in the list change current buffer). | ||
| 549 | (let ((require-final-newline nil) | ||
| 550 | (window-size-change-functions '())) | ||
| 551 | (write-file file-name confirm))))) | ||
| 552 | |||
| 553 | |||
| 554 | (defun faceup-markup-buffer () | ||
| 555 | "Return a string with the content of the buffer using faceup markup." | ||
| 556 | (let ((buf (current-buffer))) | ||
| 557 | (with-temp-buffer | ||
| 558 | (faceup-markup-to-buffer (current-buffer) buf) | ||
| 559 | (buffer-substring-no-properties (point-min) (point-max))))) | ||
| 560 | |||
| 561 | |||
| 562 | ;; Idea: | ||
| 563 | ;; | ||
| 564 | ;; Typically, only one face is used. However, when two faces are used, | ||
| 565 | ;; the one of top is typically shorter. Hence, the faceup variant | ||
| 566 | ;; should treat the inner group of nested ranges the upper (i.e. the | ||
| 567 | ;; one towards the front.) For example: | ||
| 568 | ;; | ||
| 569 | ;; «f:aaaaaaa«U:xxxx»aaaaaa» | ||
| 570 | |||
| 571 | (defun faceup-copy-and-quote (start end to-buffer) | ||
| 572 | "Quote and insert the text between START and END into TO-BUFFER." | ||
| 573 | (let ((not-markup (concat "^" | ||
| 574 | (make-string 1 faceup-markup-start-char) | ||
| 575 | (make-string 1 faceup-markup-end-char)))) | ||
| 576 | (save-excursion | ||
| 577 | (goto-char start) | ||
| 578 | (while (< (point) end) | ||
| 579 | (let ((old (point))) | ||
| 580 | (skip-chars-forward not-markup end) | ||
| 581 | (let ((s (buffer-substring-no-properties old (point)))) | ||
| 582 | (with-current-buffer to-buffer | ||
| 583 | (insert s)))) | ||
| 584 | ;; Quote stray markup characters. | ||
| 585 | (unless (= (point) end) | ||
| 586 | (let ((next-char (following-char))) | ||
| 587 | (with-current-buffer to-buffer | ||
| 588 | (insert faceup-markup-start-char) | ||
| 589 | (insert next-char))) | ||
| 590 | (forward-char)))))) | ||
| 591 | |||
| 592 | |||
| 593 | ;; A face (string or symbol) can be on the top level. | ||
| 594 | ;; | ||
| 595 | ;; A face text property can be a arbitrary deep lisp structure. Each | ||
| 596 | ;; list in the tree structure contains faces (symbols or strings) up | ||
| 597 | ;; to the first keyword, e.g. :foreground, thereafter the list is | ||
| 598 | ;; considered a property list, regardless of the content. A special | ||
| 599 | ;; case are `(foreground-color . COLOR)' and `(background-color | ||
| 600 | ;; . COLOR)', old forms used to represent the foreground and | ||
| 601 | ;; background colors, respectively. | ||
| 602 | ;; | ||
| 603 | ;; Some of this is undocumented, and took some effort to reverse | ||
| 604 | ;; engineer. | ||
| 605 | (defun faceup-normalize-face-property (value) | ||
| 606 | "Normalize VALUES into a list of faces and (KEY VALUE) entries." | ||
| 607 | (cond ((null value) | ||
| 608 | '()) | ||
| 609 | ((symbolp value) | ||
| 610 | (list value)) | ||
| 611 | ((stringp value) | ||
| 612 | (list (intern value))) | ||
| 613 | ((consp value) | ||
| 614 | (cond ((eq (car value) 'foreground-color) | ||
| 615 | (list (list :foreground (cdr value)))) | ||
| 616 | ((eq (car value) 'background-color) | ||
| 617 | (list (list :background (cdr value)))) | ||
| 618 | (t | ||
| 619 | ;; A list | ||
| 620 | (if (keywordp (car value)) | ||
| 621 | ;; Once a keyword has been seen, the rest of the | ||
| 622 | ;; list is treated as a property list, regardless | ||
| 623 | ;; of what it contains. | ||
| 624 | (let ((res '())) | ||
| 625 | (while value | ||
| 626 | (let ((key (pop value)) | ||
| 627 | (val (pop value))) | ||
| 628 | (when (keywordp key) | ||
| 629 | (push (list key val) res)))) | ||
| 630 | res) | ||
| 631 | (append | ||
| 632 | (faceup-normalize-face-property (car value)) | ||
| 633 | (faceup-normalize-face-property (cdr value))))))) | ||
| 634 | (t | ||
| 635 | (error "Unexpected text property %s" value)))) | ||
| 636 | |||
| 637 | |||
| 638 | (defun faceup-get-text-properties (pos) | ||
| 639 | "Alist of properties and values at POS. | ||
| 640 | |||
| 641 | Face-like properties are normalized -- value is a list of | ||
| 642 | faces (symbols) and short (KEY VALUE) lists. The list is | ||
| 643 | reversed to that later elements take precedence over earlier." | ||
| 644 | (let ((res '())) | ||
| 645 | (dolist (prop faceup-properties) | ||
| 646 | (let ((value (get-text-property pos prop))) | ||
| 647 | (when value | ||
| 648 | (when (memq prop faceup-face-like-properties) | ||
| 649 | ;; Normalize face-like properties. | ||
| 650 | (setq value (reverse (faceup-normalize-face-property value)))) | ||
| 651 | (push (cons prop value) res)))) | ||
| 652 | res)) | ||
| 653 | |||
| 654 | |||
| 655 | (defun faceup-markup-to-buffer (to-buffer &optional buffer) | ||
| 656 | "Convert content of BUFFER to faceup form and insert in TO-BUFFER." | ||
| 657 | (save-excursion | ||
| 658 | (if buffer | ||
| 659 | (set-buffer buffer)) | ||
| 660 | ;; Font-lock often only fontifies the visible sections. This | ||
| 661 | ;; ensures that the entire buffer is fontified before converting | ||
| 662 | ;; it. | ||
| 663 | (if (and font-lock-mode | ||
| 664 | ;; Prevent clearing out face attributes explicitly | ||
| 665 | ;; inserted by functions like `list-faces-display'. | ||
| 666 | ;; (Font-lock mode is enabled, for some reason, in those | ||
| 667 | ;; buffers.) | ||
| 668 | (not (and (eq major-mode 'help-mode) | ||
| 669 | (not font-lock-defaults)))) | ||
| 670 | (font-lock-fontify-region (point-min) (point-max))) | ||
| 671 | (let ((last-pos (point-min)) | ||
| 672 | (pos nil) | ||
| 673 | ;; List of (prop . value), representing open faceup blocks. | ||
| 674 | (state '())) | ||
| 675 | (while (setq pos (faceup-next-property-change pos)) | ||
| 676 | ;; Insert content. | ||
| 677 | (faceup-copy-and-quote last-pos pos to-buffer) | ||
| 678 | (setq last-pos pos) | ||
| 679 | (let ((prop-values (faceup-get-text-properties pos))) | ||
| 680 | (let ((next-state '())) | ||
| 681 | (setq state (reverse state)) | ||
| 682 | ;; Find all existing sequences that should continue. | ||
| 683 | (let ((cont t)) | ||
| 684 | (while (and state | ||
| 685 | prop-values | ||
| 686 | cont) | ||
| 687 | (let* ((prop (car (car state))) | ||
| 688 | (value (cdr (car state))) | ||
| 689 | (pair (assq prop prop-values))) | ||
| 690 | (if (memq prop faceup-face-like-properties) | ||
| 691 | ;; Element by element. | ||
| 692 | (if (equal value (car (cdr pair))) | ||
| 693 | (setcdr pair (cdr (cdr pair))) | ||
| 694 | (setq cont nil)) | ||
| 695 | ;; Full value. | ||
| 696 | ;; | ||
| 697 | ;; Note: Comparison is done by `eq', since (at | ||
| 698 | ;; least) the `display' property treats | ||
| 699 | ;; eq-identical values differently than when | ||
| 700 | ;; comparing using `equal'. See "Display Specs | ||
| 701 | ;; That Replace The Text" in the elisp manual. | ||
| 702 | (if (eq value (cdr pair)) | ||
| 703 | (setq prop-values (delq pair prop-values)) | ||
| 704 | (setq cont nil)))) | ||
| 705 | (when cont | ||
| 706 | (push (pop state) next-state)))) | ||
| 707 | ;; End values that should not be included in the next state. | ||
| 708 | (while state | ||
| 709 | (with-current-buffer to-buffer | ||
| 710 | (insert (make-string 1 faceup-markup-end-char))) | ||
| 711 | (pop state)) | ||
| 712 | ;; Start new ranges. | ||
| 713 | (with-current-buffer to-buffer | ||
| 714 | (while prop-values | ||
| 715 | (let ((pair (pop prop-values))) | ||
| 716 | (if (memq (car pair) faceup-face-like-properties) | ||
| 717 | ;; Face-like. | ||
| 718 | (dolist (element (cdr pair)) | ||
| 719 | (insert (make-string 1 faceup-markup-start-char)) | ||
| 720 | (unless (eq (car pair) faceup-default-property) | ||
| 721 | (insert "(") | ||
| 722 | (insert (symbol-name (car pair))) | ||
| 723 | (insert "):")) | ||
| 724 | (if (symbolp element) | ||
| 725 | (let ((short | ||
| 726 | (assq element faceup-face-short-alist))) | ||
| 727 | (if short | ||
| 728 | (insert (cdr short) ":") | ||
| 729 | (insert ":" (symbol-name element) ":"))) | ||
| 730 | (insert ":") | ||
| 731 | (prin1 element (current-buffer)) | ||
| 732 | (insert ":")) | ||
| 733 | (push (cons (car pair) element) next-state)) | ||
| 734 | ;; Not face-like. | ||
| 735 | (insert (make-string 1 faceup-markup-start-char)) | ||
| 736 | (insert "(") | ||
| 737 | (insert (symbol-name (car pair))) | ||
| 738 | (insert "):") | ||
| 739 | (prin1 (cdr pair) (current-buffer)) | ||
| 740 | (insert ":") | ||
| 741 | (push pair next-state))))) | ||
| 742 | ;; Insert content. | ||
| 743 | (setq state next-state)))) | ||
| 744 | ;; Insert whatever is left after the last face change. | ||
| 745 | (faceup-copy-and-quote last-pos (point-max) to-buffer)))) | ||
| 746 | |||
| 747 | |||
| 748 | |||
| 749 | ;; Some basic facts: | ||
| 750 | ;; | ||
| 751 | ;; (get-text-property (point-max) ...) always return nil. To check the | ||
| 752 | ;; last character in the buffer, use (- (point-max) 1). | ||
| 753 | ;; | ||
| 754 | ;; If a text has more than one face, the first one in the list | ||
| 755 | ;; takes precedence, when being viewed in Emacs. | ||
| 756 | ;; | ||
| 757 | ;; (let ((s "ABCDEF")) | ||
| 758 | ;; (set-text-properties 1 4 | ||
| 759 | ;; '(face (font-lock-warning-face font-lock-variable-name-face)) s) | ||
| 760 | ;; (insert s)) | ||
| 761 | ;; | ||
| 762 | ;; => ABCDEF | ||
| 763 | ;; | ||
| 764 | ;; Where DEF is drawn in "warning" face. | ||
| 765 | |||
| 766 | |||
| 767 | (defun faceup-has-any-text-property (pos) | ||
| 768 | "True if any properties in `faceup-properties' are defined at POS." | ||
| 769 | (let ((res nil)) | ||
| 770 | (dolist (prop faceup-properties) | ||
| 771 | (when (get-text-property pos prop) | ||
| 772 | (setq res t))) | ||
| 773 | res)) | ||
| 774 | |||
| 775 | |||
| 776 | (defun faceup-next-single-property-change (pos) | ||
| 777 | "Next position a property in `faceup-properties' changes after POS, or nil." | ||
| 778 | (let ((res nil)) | ||
| 779 | (dolist (prop faceup-properties) | ||
| 780 | (let ((next (next-single-property-change pos prop))) | ||
| 781 | (when next | ||
| 782 | (setq res (if res | ||
| 783 | (min res next) | ||
| 784 | next))))) | ||
| 785 | res)) | ||
| 786 | |||
| 787 | |||
| 788 | (defun faceup-next-property-change (pos) | ||
| 789 | "Next position after POS where one of the tracked properties change. | ||
| 790 | |||
| 791 | If POS is nil, also include `point-min' in the search. | ||
| 792 | If last character contains a tracked property, return `point-max'. | ||
| 793 | |||
| 794 | See `faceup-properties' for a list of tracked properties." | ||
| 795 | (if (eq pos (point-max)) | ||
| 796 | ;; Last search returned `point-max'. There is no more to search | ||
| 797 | ;; for. | ||
| 798 | nil | ||
| 799 | (if (and (null pos) | ||
| 800 | (faceup-has-any-text-property (point-min))) | ||
| 801 | ;; `pos' is `nil' and the character at `point-min' contains a | ||
| 802 | ;; tracked property, return `point-min'. | ||
| 803 | (point-min) | ||
| 804 | (unless pos | ||
| 805 | ;; Start from the beginning. | ||
| 806 | (setq pos (point-min))) | ||
| 807 | ;; Do a normal search. Compensate for that | ||
| 808 | ;; `next-single-property-change' does not include the end of the | ||
| 809 | ;; buffer, even when a property reach it. | ||
| 810 | (let ((res (faceup-next-single-property-change pos))) | ||
| 811 | (if (and (not res) ; No more found. | ||
| 812 | (not (eq pos (point-max))) ; Not already at the end. | ||
| 813 | (not (eq (point-min) (point-max))) ; Not an empty buffer. | ||
| 814 | (faceup-has-any-text-property (- (point-max) 1))) | ||
| 815 | ;; If a property goes all the way to the end of the | ||
| 816 | ;; buffer, return `point-max'. | ||
| 817 | (point-max) | ||
| 818 | res))))) | ||
| 819 | |||
| 820 | |||
| 821 | ;; ---------------------------------------------------------------------- | ||
| 822 | ;; Renderer | ||
| 823 | ;; | ||
| 824 | |||
| 825 | ;; Functions to convert from the faceup textual representation to text | ||
| 826 | ;; with real properties. | ||
| 827 | |||
| 828 | (defun faceup-render-string (faceup) | ||
| 829 | "Return string with properties from FACEUP written with Faceup markup." | ||
| 830 | (with-temp-buffer | ||
| 831 | (insert faceup) | ||
| 832 | (faceup-render-to-string))) | ||
| 833 | |||
| 834 | |||
| 835 | ;;;###autoload | ||
| 836 | (defun faceup-render-view-buffer (&optional buffer) | ||
| 837 | "Convert BUFFER containing Faceup markup to a new buffer and display it." | ||
| 838 | (interactive) | ||
| 839 | (with-current-buffer (or buffer (current-buffer)) | ||
| 840 | (let ((dest-buffer (get-buffer-create "*FaceUp rendering*"))) | ||
| 841 | (with-current-buffer dest-buffer | ||
| 842 | (delete-region (point-min) (point-max))) | ||
| 843 | (faceup-render-to-buffer dest-buffer) | ||
| 844 | (display-buffer dest-buffer)))) | ||
| 845 | |||
| 846 | |||
| 847 | (defun faceup-render-to-string (&optional buffer) | ||
| 848 | "Convert BUFFER containing faceup markup to a string with faces." | ||
| 849 | (unless buffer | ||
| 850 | (setq buffer (current-buffer))) | ||
| 851 | (with-temp-buffer | ||
| 852 | (faceup-render-to-buffer (current-buffer) buffer) | ||
| 853 | (buffer-substring (point-min) (point-max)))) | ||
| 854 | |||
| 855 | |||
| 856 | (defun faceup-render-to-buffer (to-buffer &optional buffer) | ||
| 857 | "Convert BUFFER containing faceup markup into text with faces in TO-BUFFER." | ||
| 858 | (with-current-buffer (or buffer (current-buffer)) | ||
| 859 | (goto-char (point-min)) | ||
| 860 | (let ((last-point (point)) | ||
| 861 | (state '()) ; List of (prop . element) | ||
| 862 | (not-markup (concat | ||
| 863 | "^" | ||
| 864 | (make-string 1 faceup-markup-start-char) | ||
| 865 | (make-string 1 faceup-markup-end-char)))) | ||
| 866 | (while (progn | ||
| 867 | (skip-chars-forward not-markup) | ||
| 868 | (if (not (eq last-point (point))) | ||
| 869 | (let ((text (buffer-substring-no-properties | ||
| 870 | last-point (point))) | ||
| 871 | (prop-elements-alist '())) | ||
| 872 | ;; Accumulate all values for each property. | ||
| 873 | (dolist (prop-element state) | ||
| 874 | (let ((property (car prop-element)) | ||
| 875 | (element (cdr prop-element))) | ||
| 876 | (let ((pair (assq property prop-elements-alist))) | ||
| 877 | (unless pair | ||
| 878 | (setq pair (cons property '())) | ||
| 879 | (push pair prop-elements-alist)) | ||
| 880 | (push element (cdr pair))))) | ||
| 881 | ;; Apply all properties. | ||
| 882 | (dolist (pair prop-elements-alist) | ||
| 883 | (let ((property (car pair)) | ||
| 884 | (elements (reverse (cdr pair)))) | ||
| 885 | ;; Create one of: | ||
| 886 | ;; (property element) or | ||
| 887 | ;; (property (element element ...)) | ||
| 888 | (when (eq (length elements) 1) | ||
| 889 | ;; This ensures that non-face-like | ||
| 890 | ;; properties are restored to their | ||
| 891 | ;; original state. | ||
| 892 | (setq elements (car elements))) | ||
| 893 | (add-text-properties 0 (length text) | ||
| 894 | (list property elements) | ||
| 895 | text))) | ||
| 896 | (with-current-buffer to-buffer | ||
| 897 | (insert text)) | ||
| 898 | (setq last-point (point)))) | ||
| 899 | (not (eobp))) | ||
| 900 | (if (eq (following-char) faceup-markup-start-char) | ||
| 901 | ;; Start marker. | ||
| 902 | (progn | ||
| 903 | (forward-char) | ||
| 904 | (if (or (eq (following-char) faceup-markup-start-char) | ||
| 905 | (eq (following-char) faceup-markup-end-char)) | ||
| 906 | ;; Escaped markup character. | ||
| 907 | (progn | ||
| 908 | (setq last-point (point)) | ||
| 909 | (forward-char)) | ||
| 910 | ;; Markup sequence. | ||
| 911 | (let ((property faceup-default-property)) | ||
| 912 | (when (eq (following-char) ?\( ) | ||
| 913 | (forward-char) ; "(" | ||
| 914 | (let ((p (point))) | ||
| 915 | (forward-sexp) | ||
| 916 | (setq property (intern (buffer-substring p (point))))) | ||
| 917 | (forward-char)) ; ")" | ||
| 918 | (let ((element | ||
| 919 | (if (eq (following-char) ?:) | ||
| 920 | ;; :element: | ||
| 921 | (progn | ||
| 922 | (forward-char) | ||
| 923 | (prog1 | ||
| 924 | (let ((p (point))) | ||
| 925 | (forward-sexp) | ||
| 926 | ;; Note: (read (current-buffer)) | ||
| 927 | ;; doesn't work, as it reads more | ||
| 928 | ;; than a sexp. | ||
| 929 | (read (buffer-substring p (point)))) | ||
| 930 | (forward-char))) | ||
| 931 | ;; X: | ||
| 932 | (prog1 | ||
| 933 | (car (rassoc (buffer-substring-no-properties | ||
| 934 | (point) (+ (point) 1)) | ||
| 935 | faceup-face-short-alist)) | ||
| 936 | (forward-char 2))))) | ||
| 937 | (push (cons property element) state))) | ||
| 938 | (setq last-point (point)))) | ||
| 939 | ;; End marker. | ||
| 940 | (pop state) | ||
| 941 | (forward-char) | ||
| 942 | (setq last-point (point))))))) | ||
| 943 | |||
| 944 | ;; ---------------------------------------------------------------------- | ||
| 945 | |||
| 946 | ;;;###autoload | ||
| 947 | (defun faceup-clean-buffer () | ||
| 948 | "Remove faceup markup from buffer." | ||
| 949 | (interactive) | ||
| 950 | (goto-char (point-min)) | ||
| 951 | (let ((not-markup (concat | ||
| 952 | "^" | ||
| 953 | (make-string 1 faceup-markup-start-char) | ||
| 954 | (make-string 1 faceup-markup-end-char)))) | ||
| 955 | (while (progn (skip-chars-forward not-markup) | ||
| 956 | (not (eobp))) | ||
| 957 | (if (eq (following-char) faceup-markup-end-char) | ||
| 958 | ;; End markers are always on their own. | ||
| 959 | (delete-char 1) | ||
| 960 | ;; Start marker. | ||
| 961 | (delete-char 1) | ||
| 962 | (if (or (eq (following-char) faceup-markup-start-char) | ||
| 963 | (eq (following-char) faceup-markup-end-char)) | ||
| 964 | ;; Escaped markup character, delete the escape and skip | ||
| 965 | ;; the original character. | ||
| 966 | (forward-char) | ||
| 967 | ;; Property name (if present) | ||
| 968 | (if (eq (following-char) ?\( ) | ||
| 969 | (let ((p (point))) | ||
| 970 | (forward-sexp) | ||
| 971 | (delete-region p (point)))) | ||
| 972 | ;; Markup sequence. | ||
| 973 | (if (eq (following-char) ?:) | ||
| 974 | ;; :value: | ||
| 975 | (let ((p (point))) | ||
| 976 | (forward-char) | ||
| 977 | (forward-sexp) | ||
| 978 | (unless (eobp) | ||
| 979 | (forward-char)) | ||
| 980 | (delete-region p (point))) | ||
| 981 | ;; X: | ||
| 982 | (delete-char 1) ; The one-letter form. | ||
| 983 | (delete-char 1))))))) ; The colon. | ||
| 984 | |||
| 985 | |||
| 986 | (defun faceup-clean-string (s) | ||
| 987 | "Remove faceup markup from string S." | ||
| 988 | (with-temp-buffer | ||
| 989 | (insert s) | ||
| 990 | (faceup-clean-buffer) | ||
| 991 | (buffer-substring (point-min) (point-max)))) | ||
| 992 | |||
| 993 | |||
| 994 | ;; ---------------------------------------------------------------------- | ||
| 995 | ;; Regression test support | ||
| 996 | ;; | ||
| 997 | |||
| 998 | (defvar faceup-test-explain nil | ||
| 999 | "When non-nil, tester functions returns a text description on failure. | ||
| 1000 | |||
| 1001 | Of course, this only work for test functions aware of this | ||
| 1002 | variable, like `faceup-test-equal' and functions based on this | ||
| 1003 | function. | ||
| 1004 | |||
| 1005 | This is intended to be used to simplify `ert' explain functions, | ||
| 1006 | which could be defined as: | ||
| 1007 | |||
| 1008 | (defun my-test (args...) ...) | ||
| 1009 | (defun my-test-explain (args...) | ||
| 1010 | (let ((faceup-test-explain t)) | ||
| 1011 | (the-test args...))) | ||
| 1012 | (put 'my-test 'ert-explainer 'my-test-explain) | ||
| 1013 | |||
| 1014 | Alternative, you can use the macro `faceup-defexplainer' as follows: | ||
| 1015 | |||
| 1016 | (defun my-test (args...) ...) | ||
| 1017 | (faceup-defexplainer my-test) | ||
| 1018 | |||
| 1019 | Test functions, like `faceup-test-font-lock-buffer', built on top | ||
| 1020 | of `faceup-test-equal', and other functions that adhere to this | ||
| 1021 | variable, can easily define their own explainer functions.") | ||
| 1022 | |||
| 1023 | ;;;###autoload | ||
| 1024 | (defmacro faceup-defexplainer (function) | ||
| 1025 | "Defines an Ert explainer function for FUNCTION. | ||
| 1026 | |||
| 1027 | FUNCTION must return an explanation when the test fails and | ||
| 1028 | `faceup-test-explain' is set." | ||
| 1029 | (let ((name (intern (concat (symbol-name function) "-explainer")))) | ||
| 1030 | `(progn | ||
| 1031 | (defun ,name (&rest args) | ||
| 1032 | (let ((faceup-test-explain t)) | ||
| 1033 | (apply (quote ,function) args))) | ||
| 1034 | (put (quote ,function) 'ert-explainer (quote ,name))))) | ||
| 1035 | |||
| 1036 | |||
| 1037 | ;; ------------------------------ | ||
| 1038 | ;; Multi-line string support. | ||
| 1039 | ;; | ||
| 1040 | |||
| 1041 | (defun faceup-test-equal (lhs rhs) | ||
| 1042 | "Compares two (multi-line) strings, LHS and RHS, for equality. | ||
| 1043 | |||
| 1044 | This is intended to be used in Ert regression test rules. | ||
| 1045 | |||
| 1046 | When `faceup-test-explain' is non-nil, instead of returning nil | ||
| 1047 | on inequality, a list is returned with a explanation what | ||
| 1048 | differs. Currently, this function reports 1) if the number of | ||
| 1049 | lines in the strings differ. 2) the lines and the line numbers on | ||
| 1050 | which the string differed. | ||
| 1051 | |||
| 1052 | For example: | ||
| 1053 | (let ((a \"ABC\\nDEF\\nGHI\") | ||
| 1054 | (b \"ABC\\nXXX\\nGHI\\nZZZ\") | ||
| 1055 | (faceup-test-explain t)) | ||
| 1056 | (message \"%s\" (faceup-test-equal a b))) | ||
| 1057 | |||
| 1058 | ==> (4 3 number-of-lines-differ (on-line 2 (DEF) (XXX))) | ||
| 1059 | |||
| 1060 | When used in an `ert' rule, the output is as below: | ||
| 1061 | |||
| 1062 | (ert-deftest faceup-test-equal-example () | ||
| 1063 | (let ((a \"ABC\\nDEF\\nGHI\") | ||
| 1064 | (b \"ABC\\nXXX\\nGHI\\nZZZ\")) | ||
| 1065 | (should (faceup-test-equal a b)))) | ||
| 1066 | |||
| 1067 | F faceup-test-equal-example | ||
| 1068 | (ert-test-failed | ||
| 1069 | ((should | ||
| 1070 | (faceup-test-equal a b)) | ||
| 1071 | :form | ||
| 1072 | (faceup-test-equal \"ABC\\nDEF\\nGHI\" \"ABC\\nXXX\\nGHI\\nZZZ\") | ||
| 1073 | :value nil :explanation | ||
| 1074 | (4 3 number-of-lines-differ | ||
| 1075 | (on-line 2 | ||
| 1076 | (\"DEF\") | ||
| 1077 | (\"XXX\")))))" | ||
| 1078 | (if (equal lhs rhs) | ||
| 1079 | t | ||
| 1080 | (if faceup-test-explain | ||
| 1081 | (let ((lhs-lines (split-string lhs "\n")) | ||
| 1082 | (rhs-lines (split-string rhs "\n")) | ||
| 1083 | (explanation '()) | ||
| 1084 | (line 1)) | ||
| 1085 | (unless (= (length lhs-lines) (length rhs-lines)) | ||
| 1086 | (setq explanation (list 'number-of-lines-differ | ||
| 1087 | (length lhs-lines) (length rhs-lines)))) | ||
| 1088 | (while lhs-lines | ||
| 1089 | (let ((one (pop lhs-lines)) | ||
| 1090 | (two (pop rhs-lines))) | ||
| 1091 | (unless (equal one two) | ||
| 1092 | (setq explanation | ||
| 1093 | (cons (list 'on-line line (list one) (list two)) | ||
| 1094 | explanation))) | ||
| 1095 | (setq line (+ line 1)))) | ||
| 1096 | (nreverse explanation)) | ||
| 1097 | nil))) | ||
| 1098 | |||
| 1099 | (faceup-defexplainer faceup-test-equal) | ||
| 1100 | |||
| 1101 | |||
| 1102 | ;; ------------------------------ | ||
| 1103 | ;; Font-lock regression test support. | ||
| 1104 | ;; | ||
| 1105 | |||
| 1106 | (defun faceup-test-font-lock-buffer (mode faceup &optional buffer) | ||
| 1107 | "Verify that BUFFER is fontified as FACEUP for major mode MODE. | ||
| 1108 | |||
| 1109 | If BUFFER is not specified the current buffer is used. | ||
| 1110 | |||
| 1111 | Note that the major mode of the buffer is set to MODE and that | ||
| 1112 | the buffer is fontified. | ||
| 1113 | |||
| 1114 | If MODE is a list, the first element is the major mode, the | ||
| 1115 | remaining are additional functions to call, e.g. minor modes." | ||
| 1116 | (save-excursion | ||
| 1117 | (if buffer | ||
| 1118 | (set-buffer buffer)) | ||
| 1119 | (if (listp mode) | ||
| 1120 | (dolist (m mode) | ||
| 1121 | (funcall m)) | ||
| 1122 | (funcall mode)) | ||
| 1123 | (font-lock-fontify-region (point-min) (point-max)) | ||
| 1124 | (let ((result (faceup-markup-buffer))) | ||
| 1125 | (faceup-test-equal faceup result)))) | ||
| 1126 | |||
| 1127 | (faceup-defexplainer faceup-test-font-lock-buffer) | ||
| 1128 | |||
| 1129 | |||
| 1130 | (defun faceup-test-font-lock-string (mode faceup) | ||
| 1131 | "True if FACEUP is re-fontified as the faceup markup for major mode MODE. | ||
| 1132 | |||
| 1133 | The string FACEUP is stripped from markup, inserted into a | ||
| 1134 | buffer, the requested major mode activated, the buffer is | ||
| 1135 | fontified, the result is again converted to the faceup form, and | ||
| 1136 | compared with the original string." | ||
| 1137 | (with-temp-buffer | ||
| 1138 | (insert faceup) | ||
| 1139 | (faceup-clean-buffer) | ||
| 1140 | (faceup-test-font-lock-buffer mode faceup))) | ||
| 1141 | |||
| 1142 | (faceup-defexplainer faceup-test-font-lock-string) | ||
| 1143 | |||
| 1144 | |||
| 1145 | (defun faceup-test-font-lock-file (mode file &optional faceup-file) | ||
| 1146 | "Verify that FILE is fontified as FACEUP-FILE for major mode MODE. | ||
| 1147 | |||
| 1148 | If FACEUP-FILE is omitted, FILE.faceup is used." | ||
| 1149 | (unless faceup-file | ||
| 1150 | (setq faceup-file (concat file ".faceup"))) | ||
| 1151 | (let ((faceup (with-temp-buffer | ||
| 1152 | (insert-file-contents faceup-file) | ||
| 1153 | (buffer-substring-no-properties (point-min) (point-max))))) | ||
| 1154 | (with-temp-buffer | ||
| 1155 | (insert-file-contents file) | ||
| 1156 | (faceup-test-font-lock-buffer mode faceup)))) | ||
| 1157 | |||
| 1158 | (faceup-defexplainer faceup-test-font-lock-file) | ||
| 1159 | |||
| 1160 | |||
| 1161 | ;; ------------------------------ | ||
| 1162 | ;; Get current file directory. Test cases can use this to locate test | ||
| 1163 | ;; files. | ||
| 1164 | ;; | ||
| 1165 | |||
| 1166 | (defun faceup-this-file-directory () | ||
| 1167 | "The directory of the file where the call to this function is located in. | ||
| 1168 | Intended to be called when a file is loaded." | ||
| 1169 | (expand-file-name | ||
| 1170 | (if load-file-name | ||
| 1171 | ;; File is being loaded. | ||
| 1172 | (file-name-directory load-file-name) | ||
| 1173 | ;; File is being evaluated using, for example, `eval-buffer'. | ||
| 1174 | default-directory))) | ||
| 1175 | |||
| 1176 | |||
| 1177 | ;; ---------------------------------------------------------------------- | ||
| 1178 | ;; The end | ||
| 1179 | ;; | ||
| 1180 | |||
| 1181 | (provide 'faceup) | ||
| 1182 | |||
| 1183 | ;;; faceup.el ends here | ||
diff --git a/test/lisp/emacs-lisp/faceup-resources/faceup-test-mode.el b/test/lisp/emacs-lisp/faceup-resources/faceup-test-mode.el new file mode 100644 index 00000000000..ec2cf272368 --- /dev/null +++ b/test/lisp/emacs-lisp/faceup-resources/faceup-test-mode.el | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | ;;; faceup-test-mode.el --- Dummy major mode for testing `faceup'. | ||
| 2 | |||
| 3 | ;; Copyright (C) 2014-2017 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Anders Lindgren | ||
| 6 | ;; Keywords: languages, faces | ||
| 7 | |||
| 8 | ;; This file is part of GNU Emacs. | ||
| 9 | |||
| 10 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 11 | ;; it under the terms of the GNU General Public License as published by | ||
| 12 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 13 | ;; (at your option) any later version. | ||
| 14 | |||
| 15 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 18 | ;; GNU General Public License for more details. | ||
| 19 | |||
| 20 | ;; You should have received a copy of the GNU General Public License | ||
| 21 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 22 | |||
| 23 | ;;; Commentary: | ||
| 24 | |||
| 25 | ;; Dummy major-mode for testing `faceup', a regression test system for | ||
| 26 | ;; font-lock keywords (syntax highlighting rules for Emacs). | ||
| 27 | ;; | ||
| 28 | ;; This mode use `syntax-propertize' to set the `syntax-table' | ||
| 29 | ;; property on "<" and ">" in "<TEXT>" to make them act like | ||
| 30 | ;; parentheses. | ||
| 31 | ;; | ||
| 32 | ;; This mode also sets the `help-echo' property on the text WARNING, | ||
| 33 | ;; the effect is that Emacs displays a tooltip when you move your | ||
| 34 | ;; mouse on to the text. | ||
| 35 | |||
| 36 | ;;; Code: | ||
| 37 | |||
| 38 | (defvar faceup-test-mode-syntax-table | ||
| 39 | (make-syntax-table) | ||
| 40 | "Syntax table for `faceup-test-mode'.") | ||
| 41 | |||
| 42 | (defvar faceup-test-font-lock-keywords | ||
| 43 | '(("\\_<WARNING\\_>" | ||
| 44 | (0 (progn | ||
| 45 | (add-text-properties (match-beginning 0) | ||
| 46 | (match-end 0) | ||
| 47 | '(help-echo "Baloon tip: Fly smoothly!")) | ||
| 48 | font-lock-warning-face)))) | ||
| 49 | "Highlight rules for `faceup-test-mode'.") | ||
| 50 | |||
| 51 | (defun faceup-test-syntax-propertize (start end) | ||
| 52 | (goto-char start) | ||
| 53 | (funcall | ||
| 54 | (syntax-propertize-rules | ||
| 55 | ("\\(<\\)\\([^<>\n]*\\)\\(>\\)" | ||
| 56 | (1 "() ") | ||
| 57 | (3 ")( "))) | ||
| 58 | start end)) | ||
| 59 | |||
| 60 | (defmacro faceup-test-define-prog-mode (mode name &rest args) | ||
| 61 | "Define a major mode for a programming language. | ||
| 62 | If `prog-mode' is defined, inherit from it." | ||
| 63 | (declare (indent defun)) | ||
| 64 | `(define-derived-mode | ||
| 65 | ,mode ,(and (fboundp 'prog-mode) 'prog-mode) | ||
| 66 | ,name ,@args)) | ||
| 67 | |||
| 68 | (faceup-test-define-prog-mode faceup-test-mode "faceup-test" | ||
| 69 | "Dummy major mode for testing `faceup', a test system for font-lock." | ||
| 70 | (set (make-local-variable 'syntax-propertize-function) | ||
| 71 | #'faceup-test-syntax-propertize) | ||
| 72 | (setq font-lock-defaults '(faceup-test-font-lock-keywords nil))) | ||
| 73 | |||
| 74 | (provide 'faceup-test-mode) | ||
| 75 | |||
| 76 | ;;; faceup-test-mode.el ends here | ||
diff --git a/test/lisp/emacs-lisp/faceup-resources/faceup-test-this-file-directory.el b/test/lisp/emacs-lisp/faceup-resources/faceup-test-this-file-directory.el new file mode 100644 index 00000000000..e9d8b7074c2 --- /dev/null +++ b/test/lisp/emacs-lisp/faceup-resources/faceup-test-this-file-directory.el | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | ;;; faceup-test-this-file-directory.el --- Support file for faceup tests | ||
| 2 | |||
| 3 | ;; Copyright (C) 2014-2017 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Anders Lindgren | ||
| 6 | ;; Keywords: languages, faces | ||
| 7 | |||
| 8 | ;; This file is part of GNU Emacs. | ||
| 9 | |||
| 10 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 11 | ;; it under the terms of the GNU General Public License as published by | ||
| 12 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 13 | ;; (at your option) any later version. | ||
| 14 | |||
| 15 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 18 | ;; GNU General Public License for more details. | ||
| 19 | |||
| 20 | ;; You should have received a copy of the GNU General Public License | ||
| 21 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 22 | |||
| 23 | ;;; Commentary: | ||
| 24 | |||
| 25 | ;; Support file for `faceup-test-basics.el'. This file is used to test | ||
| 26 | ;; `faceup-this-file-directory' in various contexts. | ||
| 27 | |||
| 28 | ;;; Code: | ||
| 29 | |||
| 30 | (defvar faceup-test-this-file-directory (faceup-this-file-directory)) | ||
| 31 | |||
| 32 | ;;; faceup-test-this-file-directory.el ends here | ||
diff --git a/test/lisp/emacs-lisp/faceup-resources/files/test1.txt b/test/lisp/emacs-lisp/faceup-resources/files/test1.txt new file mode 100644 index 00000000000..d971f364c2d --- /dev/null +++ b/test/lisp/emacs-lisp/faceup-resources/files/test1.txt | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | This is a test of `faceup', a regression test system for font-lock | ||
| 2 | keywords. It should use major mode `faceup-test-mode'. | ||
| 3 | |||
| 4 | WARNING: The first word on this line should use | ||
| 5 | `font-lock-warning-face', and a tooltip should be displayed if the | ||
| 6 | mouse pointer is moved over it. | ||
| 7 | |||
| 8 | In this mode "<" and ">" are parentheses, but only when on the same | ||
| 9 | line without any other "<" and ">" characters between them. | ||
| 10 | <OK> <NOT <OK> > | ||
| 11 | < | ||
| 12 | NOT OK | ||
| 13 | > | ||
| 14 | |||
| 15 | test1.txt ends here. | ||
diff --git a/test/lisp/emacs-lisp/faceup-resources/files/test1.txt.faceup b/test/lisp/emacs-lisp/faceup-resources/files/test1.txt.faceup new file mode 100644 index 00000000000..7d4938adf17 --- /dev/null +++ b/test/lisp/emacs-lisp/faceup-resources/files/test1.txt.faceup | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | This is a test of `faceup', a regression test system for font-lock | ||
| 2 | keywords. It should use major mode `faceup-test-mode'. | ||
| 3 | |||
| 4 | «(help-echo):"Baloon tip: Fly smoothly!":«w:WARNING»»: The first word on this line should use | ||
| 5 | `font-lock-warning-face', and a tooltip should be displayed if the | ||
| 6 | mouse pointer is moved over it. | ||
| 7 | |||
| 8 | In this mode «s:"«(syntax-table):(4 . 41):<»"» and «s:"«(syntax-table):(5 . 40):>»"» are parentheses, but only when on the same | ||
| 9 | line without any other «s:"«(syntax-table):(4 . 41):<»"» and «s:"«(syntax-table):(5 . 40):>»"» characters between them. | ||
| 10 | «(syntax-table):(4 . 41):<»OK«(syntax-table):(5 . 40):>» <NOT «(syntax-table):(4 . 41):<»OK«(syntax-table):(5 . 40):>» > | ||
| 11 | < | ||
| 12 | NOT OK | ||
| 13 | > | ||
| 14 | |||
| 15 | test1.txt ends here. | ||
diff --git a/test/lisp/emacs-lisp/faceup-tests/faceup-test-basics.el b/test/lisp/emacs-lisp/faceup-tests/faceup-test-basics.el new file mode 100644 index 00000000000..6009bfa836d --- /dev/null +++ b/test/lisp/emacs-lisp/faceup-tests/faceup-test-basics.el | |||
| @@ -0,0 +1,287 @@ | |||
| 1 | ;;; faceup-test-basics.el --- Tests for the `faceup' package. | ||
| 2 | |||
| 3 | ;; Copyright (C) 2014-2017 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Anders Lindgren | ||
| 6 | ;; Keywords: languages, faces | ||
| 7 | |||
| 8 | ;; This file is part of GNU Emacs. | ||
| 9 | |||
| 10 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 11 | ;; it under the terms of the GNU General Public License as published by | ||
| 12 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 13 | ;; (at your option) any later version. | ||
| 14 | |||
| 15 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 18 | ;; GNU General Public License for more details. | ||
| 19 | |||
| 20 | ;; You should have received a copy of the GNU General Public License | ||
| 21 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 22 | |||
| 23 | ;;; Commentary: | ||
| 24 | |||
| 25 | ;; Basic tests for the `faceup' package. | ||
| 26 | |||
| 27 | ;;; Code: | ||
| 28 | |||
| 29 | (require 'faceup) | ||
| 30 | |||
| 31 | (ert-deftest faceup-functions () | ||
| 32 | "Test primitive functions." | ||
| 33 | (should (equal (faceup-normalize-face-property '()) '())) | ||
| 34 | (should (equal (faceup-normalize-face-property 'a) '(a))) | ||
| 35 | (should (equal (faceup-normalize-face-property '(a)) '(a))) | ||
| 36 | (should (equal (faceup-normalize-face-property '(:x t)) '((:x t)))) | ||
| 37 | (should (equal (faceup-normalize-face-property '(:x t a)) '((:x t)))) | ||
| 38 | (should (equal (faceup-normalize-face-property '(:x t a b)) '((:x t)))) | ||
| 39 | (should (equal (faceup-normalize-face-property '(a :x t)) '(a (:x t)))) | ||
| 40 | (should (equal (faceup-normalize-face-property '(a b :x t)) | ||
| 41 | '(a b (:x t)))) | ||
| 42 | |||
| 43 | (should (equal (faceup-normalize-face-property '(:x t :y nil)) | ||
| 44 | '((:y nil) (:x t)))) | ||
| 45 | (should (equal (faceup-normalize-face-property '(:x t :y nil a)) | ||
| 46 | '((:y nil) (:x t)))) | ||
| 47 | (should (equal (faceup-normalize-face-property '(:x t :y nil a b)) | ||
| 48 | '((:y nil) (:x t)))) | ||
| 49 | (should (equal (faceup-normalize-face-property '(a :x t :y nil)) | ||
| 50 | '(a (:y nil) (:x t)))) | ||
| 51 | (should (equal (faceup-normalize-face-property '(a b :x t :y nil)) | ||
| 52 | '(a b (:y nil) (:x t))))) | ||
| 53 | |||
| 54 | |||
| 55 | (ert-deftest faceup-markup () | ||
| 56 | "Test basic `faceup' features." | ||
| 57 | ;; ---------- | ||
| 58 | ;; Basics | ||
| 59 | (should (equal (faceup-markup-string "") "")) | ||
| 60 | (should (equal (faceup-markup-string "test") "test")) | ||
| 61 | ;; ---------- | ||
| 62 | ;; Escaping | ||
| 63 | (should (equal (faceup-markup-string "«") "««")) | ||
| 64 | (should (equal (faceup-markup-string "«A«B«C«") "««A««B««C««")) | ||
| 65 | (should (equal (faceup-markup-string "»") "«»")) | ||
| 66 | (should (equal (faceup-markup-string "»A»B»C»") "«»A«»B«»C«»")) | ||
| 67 | ;; ---------- | ||
| 68 | ;; Plain property. | ||
| 69 | ;; | ||
| 70 | ;; UU | ||
| 71 | ;; ABCDEF | ||
| 72 | (let ((s "ABCDEF")) | ||
| 73 | (set-text-properties 2 4 '(face underline) s) | ||
| 74 | (should (equal (faceup-markup-string s) "AB«U:CD»EF"))) | ||
| 75 | ;; ---------- | ||
| 76 | ;; Plain property, full text | ||
| 77 | ;; | ||
| 78 | ;; UUUUUU | ||
| 79 | ;; ABCDEF | ||
| 80 | (let ((s "ABCDEF")) | ||
| 81 | (set-text-properties 0 6 '(face underline) s) | ||
| 82 | (should (equal (faceup-markup-string s) "«U:ABCDEF»"))) | ||
| 83 | ;; ---------- | ||
| 84 | ;; Anonymous face. | ||
| 85 | ;; | ||
| 86 | ;; AA | ||
| 87 | ;; ABCDEF | ||
| 88 | (let ((s "ABCDEF")) | ||
| 89 | (set-text-properties 2 4 '(face (:underline t)) s) | ||
| 90 | (should (equal (faceup-markup-string s) "AB«:(:underline t):CD»EF"))) | ||
| 91 | ;; ---------- | ||
| 92 | ;; Anonymous face -- plist with two keys. | ||
| 93 | ;; | ||
| 94 | ;; AA | ||
| 95 | ;; ABCDEF | ||
| 96 | (let ((s "ABCDEF")) | ||
| 97 | (set-text-properties 2 4 '(face (:foo t :bar nil)) s) | ||
| 98 | (should (equal (faceup-markup-string s) | ||
| 99 | "AB«:(:foo t):«:(:bar nil):CD»»EF"))) | ||
| 100 | ;; Ditto, with plist in list. | ||
| 101 | (let ((s "ABCDEF")) | ||
| 102 | (set-text-properties 2 4 '(face ((:foo t :bar nil))) s) | ||
| 103 | (should (equal (faceup-markup-string s) | ||
| 104 | "AB«:(:foo t):«:(:bar nil):CD»»EF"))) | ||
| 105 | ;; ---------- | ||
| 106 | ;; Anonymous face -- Two plists. | ||
| 107 | ;; | ||
| 108 | ;; AA | ||
| 109 | ;; ABCDEF | ||
| 110 | (let ((s "ABCDEF")) | ||
| 111 | (set-text-properties 2 4 '(face ((:foo t) (:bar nil))) s) | ||
| 112 | (should (equal (faceup-markup-string s) | ||
| 113 | "AB«:(:bar nil):«:(:foo t):CD»»EF"))) | ||
| 114 | ;; ---------- | ||
| 115 | ;; Anonymous face -- Nested. | ||
| 116 | ;; | ||
| 117 | ;; AA | ||
| 118 | ;; IIII | ||
| 119 | ;; ABCDEF | ||
| 120 | (let ((s "ABCDEF")) | ||
| 121 | (set-text-properties 1 2 '(face ((:foo t))) s) | ||
| 122 | (set-text-properties 2 4 '(face ((:bar t) (:foo t))) s) | ||
| 123 | (set-text-properties 4 5 '(face ((:foo t))) s) | ||
| 124 | (should (equal (faceup-markup-string s) | ||
| 125 | "A«:(:foo t):B«:(:bar t):CD»E»F"))) | ||
| 126 | ;; ---------- | ||
| 127 | ;; Nested properties. | ||
| 128 | ;; | ||
| 129 | ;; UU | ||
| 130 | ;; IIII | ||
| 131 | ;; ABCDEF | ||
| 132 | (let ((s "ABCDEF")) | ||
| 133 | (set-text-properties 1 2 '(face italic) s) | ||
| 134 | (set-text-properties 2 4 '(face (underline italic)) s) | ||
| 135 | (set-text-properties 4 5 '(face italic) s) | ||
| 136 | (should (equal (faceup-markup-string s) "A«I:B«U:CD»E»F"))) | ||
| 137 | ;; ---------- | ||
| 138 | ;; Overlapping, but not nesting, properties. | ||
| 139 | ;; | ||
| 140 | ;; UUU | ||
| 141 | ;; III | ||
| 142 | ;; ABCDEF | ||
| 143 | (let ((s "ABCDEF")) | ||
| 144 | (set-text-properties 1 2 '(face italic) s) | ||
| 145 | (set-text-properties 2 4 '(face (underline italic)) s) | ||
| 146 | (set-text-properties 4 5 '(face underline) s) | ||
| 147 | (should (equal (faceup-markup-string s) "A«I:B«U:CD»»«U:E»F"))) | ||
| 148 | ;; ---------- | ||
| 149 | ;; Overlapping, but not nesting, properties. | ||
| 150 | ;; | ||
| 151 | ;; III | ||
| 152 | ;; UUU | ||
| 153 | ;; ABCDEF | ||
| 154 | (let ((s "ABCDEF")) | ||
| 155 | (set-text-properties 1 2 '(face italic) s) | ||
| 156 | (set-text-properties 2 4 '(face (italic underline)) s) | ||
| 157 | (set-text-properties 4 5 '(face underline) s) | ||
| 158 | (should (equal (faceup-markup-string s) "A«I:B»«U:«I:CD»E»F"))) | ||
| 159 | ;; ---------- | ||
| 160 | ;; More than one face at the same location. | ||
| 161 | ;; | ||
| 162 | ;; The property to the front takes precedence, it is rendered as the | ||
| 163 | ;; innermost parenthesis pair. | ||
| 164 | (let ((s "ABCDEF")) | ||
| 165 | (set-text-properties 2 4 '(face (underline italic)) s) | ||
| 166 | (should (equal (faceup-markup-string s) "AB«I:«U:CD»»EF"))) | ||
| 167 | (let ((s "ABCDEF")) | ||
| 168 | (set-text-properties 2 4 '(face (italic underline)) s) | ||
| 169 | (should (equal (faceup-markup-string s) "AB«U:«I:CD»»EF"))) | ||
| 170 | ;; ---------- | ||
| 171 | ;; Equal ranges, full text. | ||
| 172 | (let ((s "ABCDEF")) | ||
| 173 | (set-text-properties 0 6 '(face (underline italic)) s) | ||
| 174 | (should (equal (faceup-markup-string s) "«I:«U:ABCDEF»»"))) | ||
| 175 | ;; Ditto, with stray markup characters. | ||
| 176 | (let ((s "AB«CD»EF")) | ||
| 177 | (set-text-properties 0 8 '(face (underline italic)) s) | ||
| 178 | (should (equal (faceup-markup-string s) "«I:«U:AB««CD«»EF»»"))) | ||
| 179 | |||
| 180 | ;; ---------- | ||
| 181 | ;; Multiple properties | ||
| 182 | (let ((faceup-properties '(alpha beta gamma))) | ||
| 183 | ;; One property. | ||
| 184 | (let ((s "ABCDEF")) | ||
| 185 | (set-text-properties 2 4 '(alpha (a l p h a)) s) | ||
| 186 | (should (equal (faceup-markup-string s) "AB«(alpha):(a l p h a):CD»EF"))) | ||
| 187 | |||
| 188 | ;; Two properties, inner enclosed. | ||
| 189 | (let ((s "ABCDEFGHIJ")) | ||
| 190 | (set-text-properties 2 8 '(alpha (a l p h a)) s) | ||
| 191 | (font-lock-append-text-property 4 6 'beta '(b e t a) s) | ||
| 192 | (should (equal (faceup-markup-string s) | ||
| 193 | "AB«(alpha):(a l p h a):CD«(beta):(b e t a):EF»GH»IJ"))) | ||
| 194 | |||
| 195 | ;; Two properties, same end | ||
| 196 | (let ((s "ABCDEFGH")) | ||
| 197 | (set-text-properties 2 6 '(alpha (a)) s) | ||
| 198 | (add-text-properties 4 6 '(beta (b)) s) | ||
| 199 | (should | ||
| 200 | (equal | ||
| 201 | (faceup-markup-string s) | ||
| 202 | "AB«(alpha):(a):CD«(beta):(b):EF»»GH"))) | ||
| 203 | |||
| 204 | ;; Two properties, overlap. | ||
| 205 | (let ((s "ABCDEFGHIJ")) | ||
| 206 | (set-text-properties 2 6 '(alpha (a)) s) | ||
| 207 | (add-text-properties 4 8 '(beta (b)) s) | ||
| 208 | (should | ||
| 209 | (equal | ||
| 210 | (faceup-markup-string s) | ||
| 211 | "AB«(alpha):(a):CD«(beta):(b):EF»»«(beta):(b):GH»IJ"))))) | ||
| 212 | |||
| 213 | |||
| 214 | (ert-deftest faceup-clean () | ||
| 215 | "Test the clean features of `faceup'." | ||
| 216 | (should (equal (faceup-clean-string "") "")) | ||
| 217 | (should (equal (faceup-clean-string "test") "test")) | ||
| 218 | (should (equal (faceup-clean-string "AB«U:CD»EF") "ABCDEF")) | ||
| 219 | (should (equal (faceup-clean-string "«U:ABCDEF»") "ABCDEF")) | ||
| 220 | (should (equal (faceup-clean-string "A«I:B«U:CD»E»F") "ABCDEF")) | ||
| 221 | (should (equal (faceup-clean-string "A«I:B«U:CD»»«U:E»F") "ABCDEF")) | ||
| 222 | (should (equal (faceup-clean-string "AB«I:«U:CD»»EF") "ABCDEF")) | ||
| 223 | (should (equal (faceup-clean-string "«I:«U:ABCDEF»»") "ABCDEF")) | ||
| 224 | (should (equal (faceup-clean-string "«(foo)I:ABC»DEF") "ABCDEF")) | ||
| 225 | (should (equal (faceup-clean-string "«:(:foo t):ABC»DEF") "ABCDEF")) | ||
| 226 | ;; Escaped markup characters. | ||
| 227 | (should (equal (faceup-clean-string "««") "«")) | ||
| 228 | (should (equal (faceup-clean-string "«»") "»")) | ||
| 229 | (should (equal (faceup-clean-string "A«I:B«U:CD»«»»«U:E»F") "ABCD»EF"))) | ||
| 230 | |||
| 231 | |||
| 232 | (ert-deftest faceup-render () | ||
| 233 | "Test the render features of `faceup'." | ||
| 234 | (should (equal (faceup-render-string "") "")) | ||
| 235 | (should (equal (faceup-render-string "««") "«")) | ||
| 236 | (should (equal (faceup-render-string "«»") "»")) | ||
| 237 | (should (equal (faceup-render-string "A«I:B«U:CD»«»»«U:E»F") "ABCD»EF"))) | ||
| 238 | |||
| 239 | |||
| 240 | (defvar faceup-test-resources-directory | ||
| 241 | (concat (file-name-directory | ||
| 242 | (substring (faceup-this-file-directory) 0 -1)) | ||
| 243 | "faceup-resources/") | ||
| 244 | "The `faceup-resources' directory.") | ||
| 245 | |||
| 246 | |||
| 247 | (defvar faceup-test-this-file-directory nil | ||
| 248 | "The result of `faceup-this-file-directory' in various contexts. | ||
| 249 | |||
| 250 | This is set by the file test support file | ||
| 251 | `faceup-test-this-file-directory.el'.") | ||
| 252 | |||
| 253 | |||
| 254 | (ert-deftest faceup-directory () | ||
| 255 | "Test `faceup-this-file-directory'." | ||
| 256 | (let ((file (concat faceup-test-resources-directory | ||
| 257 | "faceup-test-this-file-directory.el")) | ||
| 258 | (load-file-name nil)) | ||
| 259 | ;; Test normal load. | ||
| 260 | (makunbound 'faceup-test-this-file-directory) | ||
| 261 | (load file nil :nomessage) | ||
| 262 | (should (equal faceup-test-this-file-directory | ||
| 263 | faceup-test-resources-directory)) | ||
| 264 | ;; Test `eval-buffer'. | ||
| 265 | (makunbound 'faceup-test-this-file-directory) | ||
| 266 | (save-excursion | ||
| 267 | (find-file file) | ||
| 268 | (eval-buffer)) | ||
| 269 | (should (equal faceup-test-this-file-directory | ||
| 270 | faceup-test-resources-directory)) | ||
| 271 | ;; Test `eval-defun'. | ||
| 272 | (makunbound 'faceup-test-this-file-directory) | ||
| 273 | (save-excursion | ||
| 274 | (find-file file) | ||
| 275 | (save-excursion | ||
| 276 | (goto-char (point-min)) | ||
| 277 | (while (not (eobp)) | ||
| 278 | ;; Note: In batch mode, this prints the result of the | ||
| 279 | ;; evaluation. Unfortunately, this is hard to fix. | ||
| 280 | (eval-defun nil) | ||
| 281 | (forward-sexp)))) | ||
| 282 | (should (equal faceup-test-this-file-directory | ||
| 283 | faceup-test-resources-directory)))) | ||
| 284 | |||
| 285 | (provide 'faceup-test-basics) | ||
| 286 | |||
| 287 | ;;; faceup-test-basics.el ends here | ||
diff --git a/test/lisp/emacs-lisp/faceup-tests/faceup-test-files.el b/test/lisp/emacs-lisp/faceup-tests/faceup-test-files.el new file mode 100644 index 00000000000..0f136862094 --- /dev/null +++ b/test/lisp/emacs-lisp/faceup-tests/faceup-test-files.el | |||
| @@ -0,0 +1,63 @@ | |||
| 1 | ;;; faceup-test-files.el --- Self test of `faceup' using dummy major mode. | ||
| 2 | |||
| 3 | ;; Copyright (C) 2014-2017 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Anders Lindgren | ||
| 6 | ;; Keywords: languages, faces | ||
| 7 | |||
| 8 | ;; This file is part of GNU Emacs. | ||
| 9 | |||
| 10 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 11 | ;; it under the terms of the GNU General Public License as published by | ||
| 12 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 13 | ;; (at your option) any later version. | ||
| 14 | |||
| 15 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 18 | ;; GNU General Public License for more details. | ||
| 19 | |||
| 20 | ;; You should have received a copy of the GNU General Public License | ||
| 21 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 22 | |||
| 23 | ;;; Commentary: | ||
| 24 | |||
| 25 | ;; Self test of `faceup' with a major mode that sets both the | ||
| 26 | ;; `syntax-table' and the `echo-help' property. | ||
| 27 | ;; | ||
| 28 | ;; This file can also be seen as a blueprint of test cases for real | ||
| 29 | ;; major modes. | ||
| 30 | |||
| 31 | ;;; Code: | ||
| 32 | |||
| 33 | (require 'faceup) | ||
| 34 | |||
| 35 | ;; Note: The byte compiler needs the value to load `faceup-test-mode', | ||
| 36 | ;; hence the `eval-and-compile'. | ||
| 37 | (eval-and-compile | ||
| 38 | (defvar faceup-test-files-dir (faceup-this-file-directory) | ||
| 39 | "The directory of this file.")) | ||
| 40 | |||
| 41 | (require 'faceup-test-mode | ||
| 42 | (concat faceup-test-files-dir | ||
| 43 | "../faceup-resources/" | ||
| 44 | "faceup-test-mode.el")) | ||
| 45 | |||
| 46 | (defun faceup-test-files-check-one (file) | ||
| 47 | "Test that FILE is fontified as the .faceup file describes. | ||
| 48 | |||
| 49 | FILE is interpreted as relative to this source directory." | ||
| 50 | (let ((faceup-properties '(face syntax-table help-echo))) | ||
| 51 | (faceup-test-font-lock-file 'faceup-test-mode | ||
| 52 | (concat | ||
| 53 | faceup-test-files-dir | ||
| 54 | "../faceup-resources/" | ||
| 55 | file)))) | ||
| 56 | (faceup-defexplainer faceup-test-files-check-one) | ||
| 57 | |||
| 58 | (ert-deftest faceup-files () | ||
| 59 | (should (faceup-test-files-check-one "files/test1.txt"))) | ||
| 60 | |||
| 61 | (provide 'faceup-test-files) | ||
| 62 | |||
| 63 | ;;; faceup-test-files.el ends here | ||