@@ -44,6 +44,11 @@ in the PATH env."
44
44
:risky t
45
45
:type '(choice directory nil ))
46
46
47
+ (defcustom lsp-dart-flutter-command " flutter"
48
+ " Flutter command for running tests."
49
+ :group 'lsp-dart
50
+ :type 'string )
51
+
47
52
(defcustom lsp-dart-server-command nil
48
53
" The analysis_server executable to use."
49
54
:type '(repeat string)
@@ -112,17 +117,24 @@ Defaults to side following treemacs default."
112
117
:type 'list
113
118
:group 'lsp-dart )
114
119
120
+ (defcustom lsp-dart-test-code-lens t
121
+ " Enable the test code lens overlays."
122
+ :type 'boolean
123
+ :group 'lsp-dart )
124
+
115
125
116
126
; ;; Internal
117
127
118
128
(defun lsp-dart--find-sdk-dir ()
119
129
" Find dart sdk by searching for dart executable or flutter cache dir."
120
130
(-when-let (dart (or (executable-find " dart" )
121
- (-when-let (flutter (executable-find " flutter" ))
131
+ (-when-let (flutter (-> lsp-dart-flutter-command
132
+ executable-find
133
+ file-truename))
122
134
(expand-file-name " cache/dart-sdk/bin/dart"
123
135
(file-name-directory flutter)))))
124
136
(-> dart
125
- ( file-truename )
137
+ file-truename
126
138
(locate-dominating-file " bin" ))))
127
139
128
140
(defun lsp-dart--outline-kind->icon (kind )
@@ -268,6 +280,9 @@ Focus on it if IGNORE-FOCUS? is nil."
268
280
PARAMS outline notification data sent from WORKSPACE.
269
281
It updates the outline view if it already exists."
270
282
(lsp-workspace-set-metadata " current-outline" params workspace)
283
+ (when (and lsp-dart-test-code-lens
284
+ (lsp-dart-test-file-p (gethash " uri" params)))
285
+ (lsp-dart-check-test-code-lens params))
271
286
(when (get-buffer-window " *Dart Outline*" )
272
287
(lsp-dart--show-outline t )))
273
288
@@ -326,6 +341,146 @@ PARAMS closing labels notification data sent from WORKSPACE."
326
341
(" dart/textDocument/publishFlutterOutline" 'lsp-dart--handle-flutter-outline ))
327
342
:server-id 'dart_analysis_server ))
328
343
344
+ ; ;; test
345
+
346
+ (defun lsp-dart--test-method-p (kind )
347
+ " Return non-nil if KIND is a test type."
348
+ (or (string= kind " UNIT_TEST_TEST" )
349
+ (string= kind " UNIT_TEST_GROUP" )))
350
+
351
+ (defun lsp-dart--test-flutter-test-file-p (buffer )
352
+ " Return non-nil if the BUFFER appears to be a flutter test file."
353
+ (with-current-buffer buffer
354
+ (save-excursion
355
+ (goto-char (point-min ))
356
+ (re-search-forward " ^import 'package:flutter_test/flutter_test.dart';"
357
+ nil t ))))
358
+
359
+ (defun lsp-dart--last-index-of (regex str &optional ignore-case )
360
+ " Find the last index of a REGEX in a string STR.
361
+ IGNORE-CASE is a optional arg to ignore the case sensitive on regex search."
362
+ (let ((start 0 )
363
+ (case-fold-search ignore-case)
364
+ idx)
365
+ (while (string-match regex str start)
366
+ (setq idx (match-beginning 0 ))
367
+ (setq start (match-end 0 )))
368
+ idx))
369
+
370
+ (defun lsp-dart--test-get-project-root ()
371
+ " Return the dart or flutter project root."
372
+ (locate-dominating-file default-directory " pubspec.yaml" ) )
373
+
374
+ (defmacro lsp-dart--test-from-project-root (&rest body )
375
+ " Execute BODY with cwd set to the project root."
376
+ `(let ((project-root (lsp-dart--test-get-project-root)))
377
+ (if project-root
378
+ (let ((default-directory project-root))
379
+ ,@body )
380
+ (error " Dart or Flutter project not found (pubspec.yaml not found) " ))))
381
+
382
+ (defun lsp-dart--build-command (buffer )
383
+ " Build the dart or flutter build command.
384
+ If the given BUFFER is a flutter test file, return the flutter command
385
+ otherwise the dart command."
386
+ (let ((sdk-dir (or lsp-dart-sdk-dir (lsp-dart--find-sdk-dir))))
387
+ (if (lsp-dart--test-flutter-test-file-p buffer)
388
+ lsp-dart-flutter-command
389
+ (concat (file-name-as-directory sdk-dir) " bin/pub run" ))))
390
+
391
+ (defun lsp-dart--build-test-name (names )
392
+ " Build the test name from a group of test NAMES."
393
+ (when (and names
394
+ (not (seq-empty-p names)))
395
+ (->> names
396
+ (--map (substring it
397
+ (+ (cl-search " (" it) 2 )
398
+ (- (lsp-dart--last-index-of " )" it) 1 )))
399
+ (--reduce (format " %s %s " acc it)))))
400
+
401
+ (defun lsp-dart--escape-test-name (name )
402
+ " Return the dart safe escaped test NAME."
403
+ (let ((escaped-str (regexp-quote name)))
404
+ (seq-doseq (char '(" (" " )" " {" " }" ))
405
+ (setq escaped-str (replace-regexp-in-string char
406
+ (concat " \\ " char)
407
+ escaped-str nil t )))
408
+ escaped-str))
409
+
410
+ (defun lsp-dart--run-test (buffer &optional names kind )
411
+ " Run Dart/Flutter test command in a compilation buffer for BUFFER file.
412
+ If NAMES is non nil, it will run only for KIND the test joining the name
413
+ from NAMES."
414
+ (interactive )
415
+ (lsp-dart--test-from-project-root
416
+ (let* ((test-file (file-relative-name (buffer-file-name buffer)
417
+ (lsp-dart--test-get-project-root)))
418
+ (test-name (lsp-dart--build-test-name names))
419
+ (group-kind? (string= kind " UNIT_TEST_GROUP" ))
420
+ (test-arg (when test-name
421
+ (concat " --name '^"
422
+ (lsp-dart--escape-test-name test-name)
423
+ (if group-kind? " '" " $'" )))))
424
+ (compilation-start (format " %s test %s %s "
425
+ (lsp-dart--build-command buffer)
426
+ (or test-arg " " )
427
+ test-file)
428
+ t ))))
429
+
430
+ (defun lsp-dart--build-test-overlay (buffer names kind range test-range )
431
+ " Build an overlay for a test NAMES of KIND in BUFFER file.
432
+ RANGE is the overlay range to build."
433
+ (-let* ((beg-position (gethash " character" (gethash " start" range)))
434
+ ((beg . end) (lsp--range-to-region range))
435
+ (beg-line (progn (goto-char beg)
436
+ (line-beginning-position )))
437
+ (spaces (make-string beg-position ?\s ))
438
+ (overlay (make-overlay beg-line end buffer)))
439
+ (overlay-put overlay 'lsp-dart-test-code-lens t )
440
+ (overlay-put overlay 'lsp-dart-test-names names)
441
+ (overlay-put overlay 'lsp-dart-test-kind kind)
442
+ (overlay-put overlay 'lsp-dart-test-overlay-test-range (lsp--range-to-region test-range))
443
+ (overlay-put overlay 'before-string
444
+ (concat spaces
445
+ (propertize " Run\n "
446
+ 'help-echo " mouse-1: Run this test"
447
+ 'mouse-face 'lsp-lens-mouse-face
448
+ 'local-map (-doto (make-sparse-keymap )
449
+ (define-key [mouse-1] (lambda ()
450
+ (interactive )
451
+ (lsp-dart--run-test buffer names kind))))
452
+ 'font-lock-face 'lsp-lens-face )))))
453
+
454
+ (defun lsp-dart--add-test-code-lens (buffer items &optional names )
455
+ " Add test code lens to BUFFER for ITEMS.
456
+ NAMES arg is optional and are the group of tests representing a test name."
457
+ (seq-doseq (item items)
458
+ (-let* (((&hash " children" " codeRange" test-range " element"
459
+ (&hash " kind" " name" " range" )) item)
460
+ (test-kind? (lsp-dart--test-method-p kind))
461
+ (concatened-names (if test-kind?
462
+ (append names (list name))
463
+ names)))
464
+ (when test-kind?
465
+ (lsp-dart--build-test-overlay buffer (append names (list name)) kind range test-range))
466
+ (unless (seq-empty-p children)
467
+ (lsp-dart--add-test-code-lens buffer children concatened-names)))))
468
+
469
+ (defun lsp-dart-test-file-p (file-name )
470
+ " Return non-nil if FILE-NAME is a dart test files."
471
+ (string-match " _test.dart" file-name))
472
+
473
+ (defun lsp-dart-check-test-code-lens (params )
474
+ " Check for test adding lens to it.
475
+ PARAMS is the notification data from outline."
476
+ (-let* (((&hash " uri" " outline" (&hash " children" )) params)
477
+ (buffer (lsp--buffer-for-file (lsp--uri-to-path uri))))
478
+ (when buffer
479
+ (with-current-buffer buffer
480
+ (remove-overlays (point-min ) (point-max ) 'lsp-dart-test-code-lens t )
481
+ (save-excursion
482
+ (lsp-dart--add-test-code-lens buffer children))))))
483
+
329
484
330
485
; ;; Public interface
331
486
@@ -341,6 +496,33 @@ PARAMS closing labels notification data sent from WORKSPACE."
341
496
(interactive " P" )
342
497
(lsp-dart--show-flutter-outline ignore-focus?) )
343
498
499
+ ;;;### autoload
500
+ (defun lsp-dart-run-test-at-point ()
501
+ " Run test checking for the previous overlay at point.
502
+ Run test of the overlay which has the smallest range of
503
+ all test overlays in the current buffer."
504
+ (interactive )
505
+ (-some--> (overlays-in (point-min ) (point-max ))
506
+ (--filter (when (overlay-get it 'lsp-dart-test-code-lens )
507
+ (-let* (((beg . end) (overlay-get it 'lsp-dart-test-overlay-test-range )))
508
+ (and (>= (point ) beg)
509
+ (<= (point ) end)))) it)
510
+ (--min-by (-let* (((beg1 . end1) (overlay-get it 'lsp-dart-test-overlay-test-range ))
511
+ ((beg2 . end2) (overlay-get other 'lsp-dart-test-overlay-test-range )))
512
+ (and (< beg1 beg2)
513
+ (> end1 end2))) it)
514
+ (lsp-dart--run-test (current-buffer )
515
+ (overlay-get it 'lsp-dart-test-names )
516
+ (overlay-get it 'lsp-dart-test-kind ))))
517
+
518
+ ;;;### autoload
519
+ (defun lsp-dart-run-test-file ()
520
+ " Run dart/Flutter test command only for current buffer."
521
+ (interactive )
522
+ (if (lsp-dart-test-file-p (buffer-file-name ))
523
+ (lsp-dart--run-test (current-buffer ))
524
+ (user-error " Current buffer is not a Dart/Flutter test file" )))
525
+
344
526
345
527
;;;### autoload (with-eval-after-load 'lsp-mode (require 'lsp-dart))
346
528
0 commit comments