File: utils.el

package info (click to toggle)
lua-mode 20250310~git.2f6b8d7-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 428 kB
  • sloc: lisp: 3,186; makefile: 40; sh: 21
file content (283 lines) | stat: -rw-r--r-- 10,247 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
;; -*- flycheck-disabled-checkers: (emacs-lisp-checkdoc) ; lexical-binding:t -*-


(require 'lua-mode)
(require 'buttercup)


(defun to-be-fontified-as (text faces)
  (let ((expected-faces (lua-mk-font-lock-faces faces))
        (result-faces (lua-get-line-faces text))
        (lineno 1))
    (when (/= (length expected-faces) (length result-faces))
        (buttercup-fail "\
Fontification check failed for:
%S
  Text contains %d lines, face list contains %d lines"
                        text (length result-faces) (length expected-faces)))
    (while expected-faces
      (unless (equal (car expected-faces) (car result-faces))
        (buttercup-fail "\
Fontification check failed on line %d for:
%S
  Expected faces: %S
  Actual faces:   %S"
                        lineno text (car expected-faces) (car result-faces)))
      (setq expected-faces (cdr expected-faces)
            result-faces (cdr result-faces)
            lineno (1+ lineno)))
    (cons t "Fontification check passed")))


(buttercup-define-matcher :to-be-fontified-as (text faces)
  (to-be-fontified-as (funcall text) (funcall faces)))


(buttercup-define-matcher :to-precede (pos regexp)
  (save-excursion
    (goto-char (funcall pos))
    (let* ((precedes (looking-at (funcall regexp)))
           (substr-begin (min (point-max) (funcall pos)))
           (substr-end (min (point-max) (+ (funcall pos) 100)))
           (found-after (format "%S" (buffer-substring-no-properties
                                      substr-begin substr-end ))))
      (goto-char substr-end)
      (when (eobp) (setq found-after (concat found-after " (end-of-buffer)")))
      (cons precedes (format "Expected %s to see after point at %s: %S.  Found: %s"
                             (if precedes "NOT" "")
                             (funcall pos) (funcall regexp) found-after)))))



(defun get-str-faces (str)
  "Find contiguous spans of non-default faces in STR.

E.g. for properly fontified Lua string \"local x = 100\" it should return
  '(\"local\" font-lock-keyword-face
    \"x\" font-lock-variable-name-face
    \"100\" font-lock-constant-face)
"
  (let* ((pos 0)
         (prop (or (get-text-property pos 'face str)
                   (get-text-property pos 'font-lock-face str)))
         (nextpos 0)
         newprop
         result)
    (while nextpos
      (setq nextpos (next-property-change nextpos str))
      (setq newprop (when nextpos (or (get-text-property nextpos 'face str)
                                      (get-text-property nextpos 'font-lock-face str))))
      (when (not (equal prop newprop))
        (when (listp prop)
          (when (eq (car-safe (last prop)) 'default)
            (setq prop (butlast prop)))
          (when (= 1 (length prop))
            (setq prop (car prop)))
          (when (symbolp prop)
            (when (eq prop 'default)
              (setq prop nil))))
        (when prop
          (push (substring-no-properties str pos nextpos) result)
          (push prop result))
        (setq prop newprop
              pos nextpos)))
    (nreverse result)))

(defun lua-fontify-str (str)
  "Return string fontified according to lua-mode's rules"
  (with-temp-buffer
    (lua-mode)
    (insert str)
    (font-lock-fontify-buffer)
    (buffer-string)))

(defun get-buffer-line-faces ()
  (font-lock-fontify-buffer)
  (mapcar 'get-str-faces
          (split-string (buffer-string) "\n" nil)))


(defun lua-get-line-faces (str)
  "Find contiguous spans of non-default faces in each line of STR.

The result is a list of lists."
  (mapcar
   'get-str-faces
   (split-string (lua-fontify-str str) "\n" nil)))

(defun lua-mk-font-lock-faces (sym)
  "Decorate symbols with font-lock-%s-face recursively.

This is a mere typing/reading aid for lua-mode's font-lock tests."
  (or (cond
       ((symbolp sym) (intern-soft (format "font-lock-%s-face" (symbol-name sym))))
       ((listp sym) (mapcar 'lua-mk-font-lock-faces sym)))
      sym))

(defmacro should-lua-font-lock-equal (strs faces)
  `(should (equal (lua-get-line-faces ,strs)
                  (lua-mk-font-lock-faces ,faces))))

;; suppress fontification messages in emacs23 output
(setq font-lock-verbose nil)


(defun lua-join-lines (strs)
  (mapconcat (lambda (x) (concat x "\n")) strs ""))

(defmacro with-lua-buffer (&rest body)
  (declare (debug (&rest form)))
  `(with-temp-buffer
     ;; font-lock is not activated if buffer name is temporary (starts with a
     ;; space) and if `noninteractive' is non-nil. Ensure tests that use
     ;; font-lock still work.
     (rename-buffer "temp-buffer.lua" t)
     (let (noninteractive)
       (lua-mode)
       (font-lock-mode 1))
     (set (make-local-variable 'lua-process) nil)
     (set (make-local-variable 'lua-process-buffer) nil)
     (pop-to-buffer (current-buffer))
     (unwind-protect
      (progn ,@body)
      (when (buffer-live-p lua-process-buffer)
        (lua-kill-process)))))

(defun lua-get-indented-strs (strs)
  (let ((indent-tabs-mode nil)
        (font-lock-verbose nil))
   (butlast
    (split-string
     (with-lua-buffer
      (let ((inhibit-message t))
        (insert (replace-regexp-in-string "^\\s *" "" (lua-join-lines strs)))
        (font-lock-fontify-buffer)
        (indent-region (point-min) (point-max))
        (buffer-substring-no-properties
         (point-min) (point-max))))
     "\n" nil))))

(defun lua-insert-goto-<> (strs)
  "Insert sequence of strings and put point in place of \"<>\"."
  (insert (lua-join-lines strs))
  (goto-char (point-min))
  (re-search-forward "<>")
  (replace-match "")
  ;; Inserted text may contain multiline constructs which will only be
  ;; recognized after fontification.
  (font-lock-fontify-buffer))

(defmacro lua-buffer-strs (&rest body)
  `(butlast
    (split-string
     (with-lua-buffer
      (progn ,@body)
      (buffer-substring-no-properties (point-min) (point-max)))
     "\n" nil)))

(defun lua--reindent-like (str)
  (let ((strs (split-string str "\n")))
    (equal strs (lua-get-indented-strs strs))))

(defun with-point-at-matcher (&rest args)
  (let* (lua-code
         origin-placeholder
         (origin-marker (make-marker))
         target-placeholder
         (target-marker (make-marker))
         body
         last-elt
         result
         message
         )
    (cl-dolist (elt args)
      (cond
       ((eq last-elt :lua-code)
        (setq lua-code (funcall elt)
              last-elt nil))
       ((eq last-elt :with-point-at)
        (setq origin-placeholder (funcall elt)
              last-elt nil))
       ((eq last-elt :to-end-up-at)
        (setq target-placeholder (funcall elt)
              last-elt nil))
       ((eq last-elt :after-executing)
        ;; No funcall here, funcall when the buffer is set up.
        (setq body elt
              last-elt nil))
       (t
        (setq last-elt (if (functionp elt) (funcall elt) elt)))))

    (with-lua-buffer
     (insert lua-code)

     (goto-char (point-min))
     (set-marker target-marker (search-forward target-placeholder))
     (replace-match "")

     (goto-char (point-min))
     (set-marker origin-marker (search-forward origin-placeholder))
     (replace-match "")

     (funcall body)

     (setq result (equal (point) (marker-position target-marker)))
     (setq message
           (if result
          (format "Expected point not to be here:\n\n%s|%s"
                  (buffer-substring-no-properties (point-min) (point))
                  (buffer-substring-no-properties (point) (point-max)))
        (format "Expected point to be here:\n============\n%s|%s\n============\n\nInstead it was here:\n============\n%s|%s\n============"
                (buffer-substring-no-properties (point-min) (marker-position target-marker))
                (buffer-substring-no-properties (marker-position target-marker) (point-max))
                (buffer-substring-no-properties (point-min) (point))
                (buffer-substring-no-properties (point) (point-max)))))
     (cons result message))))

(buttercup-define-matcher :with-point-at (&rest args)
  (apply #'with-point-at-matcher `(:lua-code ,(car args) :with-point-at ,@(cdr args))))

;;; Shortcut for with-point-at with <1> and <2> placeholders
(buttercup-define-matcher :to-move-point-from-1-to-2 (code-block lua-code)
  (with-point-at-matcher
   :lua-code lua-code
   :with-point-at (lambda () "<1>")
   :after-executing code-block
   :to-end-up-at (lambda () "<2>")))

(defun lua--string-trim (string &optional trim-left trim-right)
  ;; Backport of string-trim for Emacs 24 that doesn't have subr-x lib.
  (let ((sub-start 0) sub-end)
    (or trim-left (setq trim-left "[ \t\n\r]+"))
    (or trim-right (setq trim-right "[ \t\n\r]+"))
    (save-match-data
      (when (string-match (concat "\\`" trim-left) string)
        (setq sub-start (match-end 0)))
      (when (string-match (concat trim-right "\\'") string sub-start)
        (setq sub-end (match-beginning 0))))
    (if (or sub-start sub-end)
        (substring string sub-start sub-end)
      string)))


(buttercup-define-matcher :to-be-reindented-the-same-way (str)
  (let* ((lines (split-string (funcall str) "\n"))
         (indented-lines (lua-get-indented-strs lines)))
    (buttercup--test-expectation (equal lines indented-lines)
      :expect-match-phrase (format "Indentation check failed:\n=========\nExpected:\n---------\n%s\n---------\nActual:\n---------\n%s\n========="
                                   (lua--string-trim (mapconcat 'identity lines "\n"))
                                   (lua--string-trim (mapconcat 'identity indented-lines "\n")))
      :expect-mismatch-phrase (format "Expected `%S' to not be reindented like that"
                                      lines))))

(defmacro lua--parametrize-tests (variables param-values it description-form &rest body)
  `(progn
     ,@(cl-loop
        for params in param-values
        for let-bindings = (cl-loop for var in variables
                                    for param in params
                                    collect `(,var (quote ,param)))
        for description = (eval `(let ,let-bindings ,description-form))
        for test-body = `(let ,let-bindings ,@body)
        collect
        (macroexpand `(it ,description ,test-body)))))